// 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 <utility>
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/path_service.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_path_override.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace tpm_firmware_update {
using ::testing::Optional;
TEST(TPMFirmwareUpdateTest, DecodeSettingsProto) {
enterprise_management::TPMFirmwareUpdateSettingsProto settings;
settings.set_allow_user_initiated_powerwash(true);
settings.set_allow_user_initiated_preserve_device_state(true);
settings.set_auto_update_mode(
enterprise_management::
TPMFirmwareUpdateSettingsProto_AutoUpdateMode_USER_ACKNOWLEDGMENT);
auto dict = DecodeSettingsProto(settings);
ASSERT_TRUE(dict.is_dict());
EXPECT_THAT(dict.GetDict().FindBool("allow-user-initiated-powerwash"),
Optional(true));
EXPECT_THAT(
dict.GetDict().FindBool("allow-user-initiated-preserve-device-state"),
Optional(true));
int update_mode_value =
dict.GetDict().FindInt("auto-update-mode").value_or(0);
EXPECT_EQ(2, update_mode_value);
}
class TPMFirmwareUpdateTest : public testing::Test {
public:
enum class Availability {
kPending,
kUnavailable,
kUnavailableROCAVulnerable,
kAvailable,
};
TPMFirmwareUpdateTest() {
feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
feature_list_->InitAndEnableFeature(features::kTPMFirmwareUpdate);
CHECK(temp_dir_.CreateUniqueTempDir());
base::FilePath update_location_path =
temp_dir_.GetPath().AppendASCII("tpm_firmware_update_location");
path_override_location_ = std::make_unique<base::ScopedPathOverride>(
chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_LOCATION,
update_location_path, update_location_path.IsAbsolute(), false);
base::FilePath srk_vulnerable_roca_path = temp_dir_.GetPath().AppendASCII(
"tpm_firmware_update_srk_vulnerable_roca");
path_override_srk_vulnerable_roca_ =
std::make_unique<base::ScopedPathOverride>(
chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_SRK_VULNERABLE_ROCA,
srk_vulnerable_roca_path, srk_vulnerable_roca_path.IsAbsolute(),
false);
cros_settings_test_helper_.ReplaceDeviceSettingsProviderWithStub();
SetUpdateAvailability(Availability::kAvailable);
}
void SetUpdateAvailability(Availability availability) {
base::FilePath srk_vulnerable_roca_path;
ASSERT_TRUE(base::PathService::Get(
chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_SRK_VULNERABLE_ROCA,
&srk_vulnerable_roca_path));
switch (availability) {
case Availability::kPending:
case Availability::kUnavailable:
base::DeleteFile(srk_vulnerable_roca_path);
break;
case Availability::kAvailable:
case Availability::kUnavailableROCAVulnerable:
ASSERT_TRUE(base::ImportantFileWriter::WriteFileAtomically(
srk_vulnerable_roca_path, ""));
break;
}
base::FilePath update_location_path;
ASSERT_TRUE(base::PathService::Get(
chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_LOCATION,
&update_location_path));
switch (availability) {
case Availability::kPending:
base::DeleteFile(update_location_path);
break;
case Availability::kUnavailable:
case Availability::kUnavailableROCAVulnerable:
ASSERT_TRUE(base::ImportantFileWriter::WriteFileAtomically(
update_location_path, ""));
break;
case Availability::kAvailable:
const char kUpdatePath[] = "/lib/firmware/tpm/firmware.bin";
ASSERT_TRUE(base::ImportantFileWriter::WriteFileAtomically(
update_location_path, kUpdatePath));
break;
}
}
std::unique_ptr<base::test::ScopedFeatureList> feature_list_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<base::ScopedPathOverride> path_override_location_;
std::unique_ptr<base::ScopedPathOverride> path_override_srk_vulnerable_roca_;
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
ScopedCrosSettingsTestHelper cros_settings_test_helper_;
system::ScopedFakeStatisticsProvider statistics_provider_;
};
class TPMFirmwareUpdateModesTest : public TPMFirmwareUpdateTest {
public:
TPMFirmwareUpdateModesTest() {
callback_ = base::BindOnce(&TPMFirmwareUpdateModesTest::RecordResponse,
base::Unretained(this));
statistics_provider_.SetVpdStatus(
system::StatisticsProvider::VpdStatus::kValid);
cros_settings_test_helper_.InstallAttributes()->set_device_locked(false);
}
void RecordResponse(const std::set<Mode>& modes) {
callback_received_ = true;
callback_modes_ = modes;
}
void SetConsumerOwned() {
cros_settings_test_helper_.InstallAttributes()->SetConsumerOwned();
cros_settings_test_helper_.InstallAttributes()->set_device_locked(true);
}
const std::set<Mode> kAllModes{Mode::kPowerwash, Mode::kPreserveDeviceState};
bool callback_received_ = false;
std::set<Mode> callback_modes_;
base::OnceCallback<void(const std::set<Mode>&)> callback_;
};
TEST_F(TPMFirmwareUpdateModesTest, FeatureDisabled) {
feature_list_.reset();
feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
feature_list_->InitAndDisableFeature(features::kTPMFirmwareUpdate);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
TEST_F(TPMFirmwareUpdateModesTest, FRERequired) {
statistics_provider_.SetMachineStatistic(system::kCheckEnrollmentKey, "1");
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
TEST_F(TPMFirmwareUpdateModesTest, FRERequiredDueToInvalidRwVpdStatus) {
statistics_provider_.SetVpdStatus(
system::StatisticsProvider::VpdStatus::kRwInvalid);
base::test::TestFuture<std::set<Mode>> future;
GetAvailableUpdateModes(future.GetCallback<const std::set<Mode>&>(),
base::TimeDelta());
const auto& modes = future.Get();
EXPECT_TRUE(modes.empty());
}
TEST_F(TPMFirmwareUpdateModesTest, Pending) {
SetUpdateAvailability(Availability::kPending);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
TEST_F(TPMFirmwareUpdateModesTest, ConsumerOwned) {
SetConsumerOwned();
statistics_provider_.SetVpdStatus(
system::StatisticsProvider::VpdStatus::kInvalid);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(kAllModes, callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesTest, Available) {
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(kAllModes, callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesTest, AvailableWithInvalidVpdStatus) {
statistics_provider_.SetVpdStatus(
system::StatisticsProvider::VpdStatus::kInvalid);
base::test::TestFuture<std::set<Mode>> future;
GetAvailableUpdateModes(future.GetCallback<const std::set<Mode>&>(),
base::TimeDelta());
const auto& modes = future.Get();
EXPECT_EQ(kAllModes, modes);
}
TEST_F(TPMFirmwareUpdateModesTest, AvailableWithInvalidRoVpdStatus) {
statistics_provider_.SetVpdStatus(
system::StatisticsProvider::VpdStatus::kRoInvalid);
base::test::TestFuture<std::set<Mode>> future;
GetAvailableUpdateModes(future.GetCallback<const std::set<Mode>&>(),
base::TimeDelta());
const auto& modes = future.Get();
EXPECT_EQ(kAllModes, modes);
}
TEST_F(TPMFirmwareUpdateModesTest, AvailableAfterWaiting) {
SetUpdateAvailability(Availability::kPending);
GetAvailableUpdateModes(std::move(callback_), base::Seconds(5));
task_environment_.RunUntilIdle();
EXPECT_FALSE(callback_received_);
// When testing that file appearance triggers the callback, we can't rely on
// a single execution of TaskEnvironment::RunUntilIdle(). This is
// because TaskEnvironment doesn't know about file system events that
// haven't fired and propagated to a task scheduler thread yet so may return
// early before the file system event is received. An event is expected here
// though, so keep spinning the loop until the callback is received. This
// isn't ideal, but better than flakiness due to file system events racing
// with a single invocation of RunUntilIdle().
SetUpdateAvailability(Availability::kAvailable);
while (!callback_received_) {
task_environment_.RunUntilIdle();
}
EXPECT_EQ(kAllModes, callback_modes_);
// Trigger timeout and validate there are no further callbacks or crashes.
callback_received_ = false;
task_environment_.FastForwardBy(base::Seconds(5));
task_environment_.RunUntilIdle();
EXPECT_FALSE(callback_received_);
}
TEST_F(TPMFirmwareUpdateModesTest, NoUpdateVulnerableSRK) {
SetUpdateAvailability(Availability::kUnavailableROCAVulnerable);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(std::set<Mode>{Mode::kCleanup}, callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesTest, NoUpdateNonVulnerableSRK) {
SetUpdateAvailability(Availability::kUnavailable);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(std::set<Mode>(), callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesTest, Timeout) {
SetUpdateAvailability(Availability::kPending);
GetAvailableUpdateModes(std::move(callback_), base::Seconds(5));
task_environment_.RunUntilIdle();
EXPECT_FALSE(callback_received_);
task_environment_.FastForwardBy(base::Seconds(5));
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
class TPMFirmwareUpdateModesEnterpriseTest : public TPMFirmwareUpdateModesTest {
public:
TPMFirmwareUpdateModesEnterpriseTest() {
cros_settings_test_helper_.InstallAttributes()->SetCloudManaged(
"example.com", "fake-device-id");
cros_settings_test_helper_.InstallAttributes()->set_device_locked(true);
}
void SetPolicy(const std::set<Mode>& modes) {
base::Value::Dict dict;
dict.Set(kSettingsKeyAllowPowerwash, modes.count(Mode::kPowerwash) > 0);
dict.Set(kSettingsKeyAllowPreserveDeviceState,
modes.count(Mode::kPreserveDeviceState) > 0);
cros_settings_test_helper_.Set(kTPMFirmwareUpdateSettings,
base::Value(std::move(dict)));
}
};
TEST_F(TPMFirmwareUpdateModesEnterpriseTest, DeviceSettingPending) {
SetPolicy(kAllModes);
cros_settings_test_helper_.SetTrustedStatus(
CrosSettingsProvider::TEMPORARILY_UNTRUSTED);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_FALSE(callback_received_);
cros_settings_test_helper_.SetTrustedStatus(CrosSettingsProvider::TRUSTED);
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(kAllModes, callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesEnterpriseTest, DeviceSettingUntrusted) {
cros_settings_test_helper_.SetTrustedStatus(
CrosSettingsProvider::PERMANENTLY_UNTRUSTED);
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
TEST_F(TPMFirmwareUpdateModesEnterpriseTest, DeviceSettingNotSet) {
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
TEST_F(TPMFirmwareUpdateModesEnterpriseTest, DeviceSettingDisallowed) {
SetPolicy({});
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(callback_modes_.empty());
}
TEST_F(TPMFirmwareUpdateModesEnterpriseTest, DeviceSettingPowerwashAllowed) {
SetPolicy({Mode::kPowerwash});
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(std::set<Mode>({Mode::kPowerwash}), callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesEnterpriseTest,
DeviceSettingPreserveDeviceStateAllowed) {
SetPolicy({Mode::kPreserveDeviceState});
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(std::set<Mode>({Mode::kPreserveDeviceState}), callback_modes_);
}
TEST_F(TPMFirmwareUpdateModesEnterpriseTest, VulnerableSRK) {
SetUpdateAvailability(Availability::kUnavailableROCAVulnerable);
SetPolicy({Mode::kPreserveDeviceState});
GetAvailableUpdateModes(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_EQ(std::set<Mode>({Mode::kCleanup}), callback_modes_);
}
class TPMFirmwareAutoUpdateTest : public TPMFirmwareUpdateTest {
public:
TPMFirmwareAutoUpdateTest() {
callback_ = base::BindOnce(&TPMFirmwareAutoUpdateTest::RecordResponse,
base::Unretained(this));
}
void RecordResponse(bool update_available) {
callback_received_ = true;
update_available_ = update_available;
}
bool callback_received_ = false;
bool update_available_;
base::OnceCallback<void(bool)> callback_;
};
TEST_F(TPMFirmwareAutoUpdateTest, AutoUpdateAvaiable) {
UpdateAvailable(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_TRUE(update_available_);
}
TEST_F(TPMFirmwareAutoUpdateTest, VulnerableSRKNoStatePreservingUpdate) {
SetUpdateAvailability(Availability::kUnavailableROCAVulnerable);
UpdateAvailable(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_FALSE(update_available_);
}
TEST_F(TPMFirmwareAutoUpdateTest, NoUpdate) {
SetUpdateAvailability(Availability::kUnavailable);
UpdateAvailable(std::move(callback_), base::TimeDelta());
task_environment_.RunUntilIdle();
EXPECT_TRUE(callback_received_);
EXPECT_FALSE(update_available_);
}
} // namespace tpm_firmware_update
} // namespace ash