chromium/chrome/browser/ash/tpm_firmware_update_unittest.cc

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