| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <sys/sysmacros.h> |
| #include <sys/types.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/logging.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/values.h> |
| #include <brillo/files/file_util.h> |
| #include <gtest/gtest.h> |
| #include <libstorage/platform/mock_platform.h> |
| #include <libstorage/platform/platform.h> |
| #include <linux/loadpin.h> |
| |
| #include "init/startup/fake_startup_dep_impl.h" |
| #include "init/startup/security_manager.h" |
| #include "init/startup/startup_dep_impl.h" |
| |
| using testing::_; |
| using testing::ByMove; |
| using testing::DoAll; |
| using testing::InvokeWithoutArgs; |
| using testing::Return; |
| using testing::StrEq; |
| |
| namespace { |
| |
| constexpr char kStatefulPartition[] = "mnt/stateful_partition"; |
| constexpr char kPreserveSysKeyFile[] = "unencrypted/preserve/system.key"; |
| |
| MATCHER_P(IntPtrCheck, expected, "") { |
| return *arg == expected; |
| } |
| |
| bool ExceptionsTestFunc(libstorage::Platform* platform, |
| const base::FilePath& root, |
| const std::string& path) { |
| base::FilePath allow = root.Append("allow_file"); |
| std::string data; |
| platform->ReadFileToString(allow, &data); |
| data.append(path); |
| data.append("\n"); |
| return platform->WriteStringToFile(allow, data); |
| } |
| |
| } // namespace |
| |
| class SecurityManagerTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| platform_ = std::make_unique<libstorage::MockPlatform>(); |
| startup_dep_ = std::make_unique<startup::FakeStartupDep>(platform_.get()); |
| } |
| |
| std::unique_ptr<startup::FakeStartupDep> startup_dep_; |
| |
| std::unique_ptr<libstorage::MockPlatform> platform_; |
| base::FilePath base_dir_{"/"}; |
| }; |
| |
| class SecurityManagerLoadPinTest : public SecurityManagerTest { |
| protected: |
| void SetUp() override { |
| SecurityManagerTest::SetUp(); |
| |
| loadpin_verity_path_ = |
| base_dir_.Append("sys/kernel/security/loadpin/dm-verity"); |
| platform_->WriteStringToFile(loadpin_verity_path_, kNull); |
| |
| trusted_verity_digests_path_ = |
| base_dir_.Append("opt/google/dlc/_trusted_verity_digests"); |
| platform_->WriteStringToFile(trusted_verity_digests_path_, kRootDigest); |
| |
| dev_null_path_ = base_dir_.Append("dev/null"); |
| platform_->WriteStringToFile(dev_null_path_, kNull); |
| } |
| |
| base::FilePath loadpin_verity_path_; |
| base::FilePath trusted_verity_digests_path_; |
| base::FilePath dev_null_path_; |
| const std::string kRootDigest = |
| "fb066d299c657b127ecc2c11f841cabf14c717eb6f03630ef788e6e1cca17f52"; |
| const std::string kNull = "\0"; |
| }; |
| |
| TEST_F(SecurityManagerTest, After_v4_14) { |
| base::FilePath policies_dir = |
| base_dir_.Append("usr/share/cros/startup/process_management_policies"); |
| base::FilePath mgmt_policies = |
| base_dir_.Append("sys/kernel/security/safesetid/whitelist_policy"); |
| ASSERT_TRUE(platform_->WriteStringToFile(mgmt_policies, "#AllowList")); |
| base::FilePath allow_1 = policies_dir.Append("allow_1.txt"); |
| std::string result1 = "254:607\n607:607"; |
| std::string full1 = "254:607\n607:607\n#Comment\n\n#Ignore"; |
| ASSERT_TRUE(platform_->WriteStringToFile(allow_1, full1)); |
| base::FilePath allow_2 = policies_dir.Append("allow_2.txt"); |
| std::string result2 = "20104:224\n20104:217\n217:217"; |
| std::string full2 = "#Comment\n\n20104:224\n20104:217\n#Ignore\n217:217"; |
| ASSERT_TRUE(platform_->WriteStringToFile(allow_2, full2)); |
| |
| startup::ConfigureProcessMgmtSecurity(platform_.get(), base_dir_); |
| |
| std::string allow; |
| platform_->ReadFileToString(mgmt_policies, &allow); |
| |
| EXPECT_NE(allow.find(result1), std::string::npos); |
| EXPECT_NE(allow.find(result2), std::string::npos); |
| |
| std::vector<std::string> allow_vec = base::SplitString( |
| allow, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| std::vector<std::string> expected = {"254:607", "607:607", "20104:224", |
| "20104:217", "217:217"}; |
| sort(allow_vec.begin(), allow_vec.end()); |
| sort(expected.begin(), expected.end()); |
| EXPECT_EQ(allow_vec, expected); |
| } |
| |
| TEST_F(SecurityManagerTest, After_v5_9) { |
| base::FilePath policies_dir = |
| base_dir_.Append("usr/share/cros/startup/process_management_policies"); |
| base::FilePath mgmt_policies = |
| base_dir_.Append("sys/kernel/security/safesetid/uid_allowlist_policy"); |
| ASSERT_TRUE(platform_->WriteStringToFile(mgmt_policies, "#AllowList")); |
| base::FilePath allow_1 = policies_dir.Append("allow_1.txt"); |
| std::string result1 = "254:607\n607:607"; |
| ASSERT_TRUE(platform_->WriteStringToFile(allow_1, result1)); |
| base::FilePath allow_2 = policies_dir.Append("allow_2.txt"); |
| std::string result2 = "20104:224\n20104:217\n217:217"; |
| ASSERT_TRUE(platform_->WriteStringToFile(allow_2, result2)); |
| |
| startup::ConfigureProcessMgmtSecurity(platform_.get(), base_dir_); |
| |
| std::string allow; |
| platform_->ReadFileToString(mgmt_policies, &allow); |
| |
| EXPECT_NE(allow.find(result1), std::string::npos); |
| EXPECT_NE(allow.find(result2), std::string::npos); |
| } |
| |
| TEST_F(SecurityManagerTest, EmptyAfter_v5_9) { |
| base::FilePath mgmt_policies = |
| base_dir_.Append("sys/kernel/security/safesetid/uid_allowlist_policy"); |
| ASSERT_TRUE(platform_->WriteStringToFile(mgmt_policies, "#AllowList")); |
| |
| EXPECT_FALSE( |
| startup::ConfigureProcessMgmtSecurity(platform_.get(), base_dir_)); |
| |
| std::string allow; |
| platform_->ReadFileToString(mgmt_policies, &allow); |
| |
| EXPECT_EQ(allow, "#AllowList"); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, LoadPinAttributeUnsupported) { |
| ASSERT_TRUE(platform_->DeleteFile(loadpin_verity_path_)); |
| |
| EXPECT_CALL(*platform_, Ioctl(_, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, _)) |
| .Times(0); |
| |
| // The call should succeed as there is no LoadPin verity file. |
| EXPECT_TRUE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, FailureToOpenLoadPinVerity) { |
| EXPECT_CALL(*platform_, OpenFile(loadpin_verity_path_, StrEq("w"))) |
| .WillOnce(Return(nullptr)); |
| EXPECT_CALL(*platform_, Ioctl(_, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, _)) |
| .Times(0); |
| |
| // The call should fail as failure to open LoadPin verity file. |
| EXPECT_FALSE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, ValidDigests) { |
| EXPECT_CALL(*platform_, Ioctl(_, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, _)) |
| .WillOnce(Return(0)); |
| EXPECT_TRUE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, MissingDigests) { |
| ASSERT_TRUE(platform_->DeleteFile(trusted_verity_digests_path_)); |
| EXPECT_CALL(*platform_, Ioctl(_, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, _)) |
| .WillOnce(Return(0)); |
| EXPECT_TRUE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, FailureToReadDigests) { |
| // List all the expected calls to OpenFile. |
| EXPECT_CALL(*platform_, OpenFile(loadpin_verity_path_, StrEq("w"))); |
| EXPECT_CALL(*platform_, OpenFile(trusted_verity_digests_path_, StrEq("r"))) |
| .WillOnce( |
| DoAll(InvokeWithoutArgs([] { errno = EACCES; }), Return(nullptr))); |
| EXPECT_CALL(*platform_, OpenFile(dev_null_path_, StrEq("r"))); |
| EXPECT_CALL(*platform_, Ioctl(_, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, _)) |
| .WillOnce(Return(0)); |
| |
| EXPECT_TRUE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, FailureToReadInvalidDigestsDevNull) { |
| EXPECT_CALL(*platform_, OpenFile(loadpin_verity_path_, StrEq("w"))); |
| EXPECT_CALL(*platform_, OpenFile(trusted_verity_digests_path_, StrEq("r"))) |
| .WillOnce( |
| DoAll(InvokeWithoutArgs([] { errno = EACCES; }), Return(nullptr))); |
| EXPECT_CALL(*platform_, OpenFile(dev_null_path_, StrEq("r"))) |
| .WillOnce( |
| DoAll(InvokeWithoutArgs([] { errno = EACCES; }), Return(nullptr))); |
| |
| EXPECT_FALSE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| TEST_F(SecurityManagerLoadPinTest, FailureToFeedLoadPin) { |
| EXPECT_CALL(*platform_, Ioctl(_, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, _)) |
| .WillOnce(Return(-1)); |
| |
| EXPECT_FALSE(startup::SetupLoadPinVerityDigests(platform_.get(), base_dir_, |
| startup_dep_.get())); |
| } |
| |
| class SysKeyTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| stateful = base_dir.Append(kStatefulPartition); |
| platform_ = std::make_unique<libstorage::MockPlatform>(); |
| startup_dep_ = std::make_unique<startup::FakeStartupDep>(platform_.get()); |
| } |
| |
| base::FilePath base_dir{"/"}; |
| base::FilePath stateful; |
| std::unique_ptr<libstorage::MockPlatform> platform_; |
| std::unique_ptr<startup::FakeStartupDep> startup_dep_; |
| }; |
| |
| TEST_F(SysKeyTest, NoEarlySysKeyFile) { |
| base::FilePath no_early = stateful.Append(".no_early_system_key"); |
| ASSERT_TRUE(platform_->WriteStringToFile(no_early, "1")); |
| |
| std::string res; |
| startup::CreateSystemKey(platform_.get(), base_dir, stateful, |
| startup_dep_.get(), &res); |
| |
| EXPECT_EQ(res, "Opt not to create a system key in advance."); |
| } |
| |
| TEST_F(SysKeyTest, AlreadySysKey) { |
| startup_dep_->SetMountEncOutputForArg("info", "NVRAM: available."); |
| |
| std::string res; |
| startup::CreateSystemKey(platform_.get(), base_dir, stateful, |
| startup_dep_.get(), &res); |
| std::string expected = |
| "Checking if a system key already exists in NVRAM...\n"; |
| expected.append("NVRAM: available.\n"); |
| expected.append("There is already a system key in NVRAM.\n"); |
| EXPECT_EQ(res, expected); |
| } |
| |
| TEST_F(SysKeyTest, NeedSysKeyBadRandomWrite) { |
| base::FilePath backup = stateful.Append(kPreserveSysKeyFile); |
| EXPECT_CALL(*platform_, WriteFile(backup, _)).WillOnce(Return(false)); |
| startup_dep_->SetMountEncOutputForArg("info", "not found."); |
| |
| std::string res; |
| startup::CreateSystemKey(platform_.get(), base_dir, stateful, |
| startup_dep_.get(), &res); |
| |
| std::string expected = |
| "Checking if a system key already exists in NVRAM...\n"; |
| expected.append("not found.\n"); |
| expected.append("No system key found in NVRAM. Start creating one.\n"); |
| expected.append("Failed to generate or back up system key material.\n"); |
| EXPECT_EQ(res, expected); |
| } |
| |
| TEST_F(SysKeyTest, NeedSysKeySuccessful) { |
| base::FilePath backup = stateful.Append(kPreserveSysKeyFile); |
| startup_dep_->SetMountEncOutputForArg("info", "not found."); |
| startup_dep_->SetMountEncOutputForArg("set", "MountEncrypted set output.\n"); |
| std::string res; |
| startup::CreateSystemKey(platform_.get(), base_dir, stateful, |
| startup_dep_.get(), &res); |
| |
| std::string expected = |
| "Checking if a system key already exists in NVRAM...\n"; |
| expected.append("not found.\n"); |
| expected.append("No system key found in NVRAM. Start creating one.\n"); |
| expected.append("MountEncrypted set output.\n"); |
| expected.append("Successfully created a system key."); |
| EXPECT_EQ(res, expected); |
| } |
| |
| class ExceptionsTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| platform_ = std::make_unique<libstorage::FakePlatform>(); |
| allow_file_ = base_dir.Append("allow_file"); |
| ASSERT_TRUE(platform_->WriteStringToFile(allow_file_, "")); |
| excepts_dir_ = base_dir.Append("excepts_dir"); |
| } |
| |
| std::unique_ptr<libstorage::FakePlatform> platform_; |
| base::FilePath base_dir{"/"}; |
| base::FilePath allow_file_; |
| base::FilePath excepts_dir_; |
| }; |
| |
| TEST_F(ExceptionsTest, ExceptionsDirNoExist) { |
| startup::ExceptionsProjectSpecific(platform_.get(), base_dir, excepts_dir_, |
| &ExceptionsTestFunc); |
| std::string allow_contents; |
| platform_->ReadFileToString(allow_file_, &allow_contents); |
| EXPECT_EQ(allow_contents, ""); |
| } |
| |
| TEST_F(ExceptionsTest, ExceptionsDirEmpty) { |
| platform_->CreateDirectory(excepts_dir_); |
| startup::ExceptionsProjectSpecific(platform_.get(), base_dir, excepts_dir_, |
| &ExceptionsTestFunc); |
| std::string allow_contents; |
| platform_->ReadFileToString(allow_file_, &allow_contents); |
| EXPECT_EQ(allow_contents, ""); |
| } |
| |
| TEST_F(ExceptionsTest, ExceptionsDirMultiplePaths) { |
| base::FilePath test_path_1_1 = base_dir.Append("test_1_1"); |
| base::FilePath test_path_1_2 = base_dir.Append("test_1_2"); |
| base::FilePath test_path_1_ignore = base_dir.Append("should_ignore"); |
| std::string test_str_1 = std::string("\n") |
| .append(test_path_1_1.value()) |
| .append("\n#ignore\n\n#") |
| .append(test_path_1_ignore.value()) |
| .append("\n") |
| .append(test_path_1_2.value()) |
| .append("\n"); |
| base::FilePath test_path_2_1 = base_dir.Append("test_2_1"); |
| base::FilePath test_path_2_2 = base_dir.Append("test_2_2"); |
| base::FilePath test_path_2_ignore = base_dir.Append("should_ignore"); |
| std::string test_str_2 = std::string("#") |
| .append(test_path_2_ignore.value()) |
| .append("\n") |
| .append(test_path_2_1.value()) |
| .append("\n\n#\n") |
| .append(test_path_2_2.value()); |
| base::FilePath test_1 = excepts_dir_.Append("test_1"); |
| base::FilePath test_2 = excepts_dir_.Append("test_2"); |
| ASSERT_TRUE(platform_->WriteStringToFile(test_1, test_str_1)); |
| ASSERT_TRUE(platform_->WriteStringToFile(test_2, test_str_2)); |
| |
| startup::ExceptionsProjectSpecific(platform_.get(), base_dir, excepts_dir_, |
| &ExceptionsTestFunc); |
| |
| std::string allow_contents; |
| platform_->ReadFileToString(allow_file_, &allow_contents); |
| EXPECT_NE(allow_contents.find(test_path_1_1.value()), std::string::npos); |
| EXPECT_NE(allow_contents.find(test_path_1_2.value()), std::string::npos); |
| EXPECT_EQ(allow_contents.find(test_path_1_ignore.value()), std::string::npos); |
| EXPECT_NE(allow_contents.find(test_path_2_1.value()), std::string::npos); |
| EXPECT_NE(allow_contents.find(test_path_2_2.value()), std::string::npos); |
| EXPECT_EQ(allow_contents.find(test_path_1_ignore.value()), std::string::npos); |
| EXPECT_TRUE(platform_->DirectoryExists(test_path_1_1)); |
| EXPECT_TRUE(platform_->DirectoryExists(test_path_1_2)); |
| EXPECT_FALSE(platform_->DirectoryExists(test_path_1_ignore)); |
| EXPECT_TRUE(platform_->DirectoryExists(test_path_2_1)); |
| EXPECT_TRUE(platform_->DirectoryExists(test_path_2_2)); |
| EXPECT_FALSE(platform_->DirectoryExists(test_path_2_ignore)); |
| } |