blob: d26ce39bcd4e25c0fb81c3eb4e61dcf547d800be [file] [log] [blame]
// 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/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include <gtest/gtest.h>
#include "init/startup/mock_platform_impl.h"
#include "init/startup/security_manager.h"
using testing::_;
using testing::ByMove;
using testing::DoAll;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::StrictMock;
namespace {
// Define in test to catch source changes in flags.
constexpr auto kWriteFlags = O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
constexpr auto kReadFlags = O_RDONLY | O_NOFOLLOW | O_CLOEXEC;
MATCHER_P(IntPtrCheck, expected, "") {
return *arg == expected;
}
// Helper function to create directory and write to file.
bool CreateDirAndWriteFile(const base::FilePath& path,
const std::string& contents) {
return base::CreateDirectory(path.DirName()) &&
base::WriteFile(path, contents.c_str(), contents.length()) ==
contents.length();
}
} // namespace
class SecurityManagerTest : public ::testing::Test {
protected:
SecurityManagerTest() {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
base_dir_ = temp_dir_.GetPath();
mock_platform_ = std::make_unique<StrictMock<startup::MockPlatform>>();
}
std::unique_ptr<startup::MockPlatform> mock_platform_;
base::ScopedTempDir temp_dir_;
base::FilePath base_dir_;
};
class SecurityManagerLoadPinTest : public SecurityManagerTest {
protected:
SecurityManagerLoadPinTest() {}
void SetUp() override {
SecurityManagerTest::SetUp();
loadpin_verity_path_ =
base_dir_.Append("sys/kernel/security/loadpin/dm-verity");
CreateDirAndWriteFile(loadpin_verity_path_, kNull);
trusted_verity_digests_path_ =
base_dir_.Append("opt/google/dlc/_trusted_verity_digests");
CreateDirAndWriteFile(trusted_verity_digests_path_, kRootDigest);
dev_null_path_ = base_dir_.Append("dev/null");
CreateDirAndWriteFile(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, Before_v4_4) {
base::FilePath policies_dir =
base_dir_.Append("usr/share/cros/startup/process_management_policies");
base::FilePath mgmt_policies = base_dir_.Append(
"sys/kernel/security/chromiumos/process_management_policies/"
"add_whitelist_policy");
ASSERT_TRUE(CreateDirAndWriteFile(mgmt_policies, ""));
base::FilePath safesetid_mgmt_policies =
base_dir_.Append("sys/kernel/security/safesetid/whitelist_policy");
ASSERT_TRUE(CreateDirAndWriteFile(safesetid_mgmt_policies, "#AllowList"));
base::FilePath allow_1 = policies_dir.Append("allow_1.txt");
ASSERT_TRUE(CreateDirAndWriteFile(allow_1, "254:607\n607:607"));
startup::ConfigureProcessMgmtSecurity(base_dir_);
std::string allow;
base::ReadFileToString(mgmt_policies, &allow);
EXPECT_EQ(allow, "254:607\n607:607\n");
}
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(CreateDirAndWriteFile(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(CreateDirAndWriteFile(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(CreateDirAndWriteFile(allow_2, full2));
startup::ConfigureProcessMgmtSecurity(base_dir_);
std::string allow;
base::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(CreateDirAndWriteFile(mgmt_policies, "#AllowList"));
base::FilePath allow_1 = policies_dir.Append("allow_1.txt");
std::string result1 = "254:607\n607:607";
ASSERT_TRUE(CreateDirAndWriteFile(allow_1, result1));
base::FilePath allow_2 = policies_dir.Append("allow_2.txt");
std::string result2 = "20104:224\n20104:217\n217:217";
ASSERT_TRUE(CreateDirAndWriteFile(allow_2, result2));
startup::ConfigureProcessMgmtSecurity(base_dir_);
std::string allow;
base::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(CreateDirAndWriteFile(mgmt_policies, "#AllowList"));
EXPECT_EQ(startup::ConfigureProcessMgmtSecurity(base_dir_), false);
std::string allow;
base::ReadFileToString(mgmt_policies, &allow);
EXPECT_EQ(allow, "#AllowList");
}
TEST_F(SecurityManagerLoadPinTest, LoadPinAttributeUnsupported) {
ASSERT_TRUE(base::DeleteFile(loadpin_verity_path_));
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(),
O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
// int fd = loadpin_verity.get();
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, _))
.WillOnce(Return(ByMove(std::move(loadpin_verity))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(_, _, _)).Times(0);
EXPECT_TRUE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}
TEST_F(SecurityManagerLoadPinTest, FailureToOpenLoadPinVerity) {
ASSERT_TRUE(base::DeleteFile(loadpin_verity_path_));
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(), kWriteFlags)));
// int fd = loadpin_verity.get();
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, kWriteFlags))
.WillOnce(DoAll(
// Override the `errno` to be non-`ENOENT`.
InvokeWithoutArgs([] { errno = EACCES; }),
Return(ByMove(std::move(loadpin_verity)))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(_, _, _)).Times(0);
// The call should fail as failure to open LoadPin verity file.
EXPECT_FALSE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}
TEST_F(SecurityManagerLoadPinTest, ValidDigests) {
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(), kWriteFlags)));
int fd = loadpin_verity.get();
base::ScopedFD trusted_verity_digests(HANDLE_EINTR(
open(trusted_verity_digests_path_.value().c_str(), kReadFlags)));
int digests_fd = trusted_verity_digests.get();
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, kWriteFlags))
.WillOnce(Return(ByMove(std::move(loadpin_verity))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(trusted_verity_digests_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(trusted_verity_digests))));
// `trusted_verity_digests` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(fd, _, IntPtrCheck(digests_fd)))
.WillOnce(Return(0));
EXPECT_TRUE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}
TEST_F(SecurityManagerLoadPinTest, MissingDigests) {
ASSERT_TRUE(base::DeleteFile(trusted_verity_digests_path_));
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(), kWriteFlags)));
int fd = loadpin_verity.get();
base::ScopedFD trusted_verity_digests(HANDLE_EINTR(
open(trusted_verity_digests_path_.value().c_str(), kReadFlags)));
base::ScopedFD dev_null(
HANDLE_EINTR(open(dev_null_path_.value().c_str(), kReadFlags)));
int dev_null_fd = dev_null.get();
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, kWriteFlags))
.WillOnce(Return(ByMove(std::move(loadpin_verity))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(trusted_verity_digests_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(trusted_verity_digests))));
// `trusted_verity_digests` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(dev_null_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(dev_null))));
// `dev_null` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(fd, _, IntPtrCheck(dev_null_fd)))
.WillOnce(Return(0));
EXPECT_TRUE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}
TEST_F(SecurityManagerLoadPinTest, FailureToReadDigests) {
ASSERT_TRUE(base::DeleteFile(trusted_verity_digests_path_));
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(), kWriteFlags)));
int fd = loadpin_verity.get();
base::ScopedFD trusted_verity_digests(HANDLE_EINTR(
open(trusted_verity_digests_path_.value().c_str(), kReadFlags)));
base::ScopedFD dev_null(
HANDLE_EINTR(open(dev_null_path_.value().c_str(), kReadFlags)));
int dev_null_fd = dev_null.get();
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, kWriteFlags))
.WillOnce(Return(ByMove(std::move(loadpin_verity))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(trusted_verity_digests_path_, kReadFlags))
.WillOnce(DoAll(
// Override the `errno` to be non-`ENOENT`.
InvokeWithoutArgs([] { errno = EACCES; }),
Return(ByMove(std::move(trusted_verity_digests)))));
// `trusted_verity_digests` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(dev_null_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(dev_null))));
// `dev_null` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(fd, _, IntPtrCheck(dev_null_fd)))
.WillOnce(Return(0));
EXPECT_TRUE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}
TEST_F(SecurityManagerLoadPinTest, FailureToReadInvalidDigestsDevNull) {
ASSERT_TRUE(base::DeleteFile(trusted_verity_digests_path_));
ASSERT_TRUE(base::DeleteFile(dev_null_path_));
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(), kWriteFlags)));
base::ScopedFD trusted_verity_digests(HANDLE_EINTR(
open(trusted_verity_digests_path_.value().c_str(), kReadFlags)));
base::ScopedFD dev_null(
HANDLE_EINTR(open(dev_null_path_.value().c_str(), kReadFlags)));
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, kWriteFlags))
.WillOnce(Return(ByMove(std::move(loadpin_verity))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(trusted_verity_digests_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(trusted_verity_digests))));
// `trusted_verity_digests` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(dev_null_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(dev_null))));
// `dev_null` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(_, _, _)).Times(0);
EXPECT_FALSE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}
TEST_F(SecurityManagerLoadPinTest, FailureToFeedLoadPin) {
base::ScopedFD loadpin_verity(
HANDLE_EINTR(open(loadpin_verity_path_.value().c_str(), kWriteFlags)));
int fd = loadpin_verity.get();
base::ScopedFD trusted_verity_digests(HANDLE_EINTR(
open(trusted_verity_digests_path_.value().c_str(), kReadFlags)));
int digests_fd = trusted_verity_digests.get();
EXPECT_CALL(*mock_platform_, Open(loadpin_verity_path_, kWriteFlags))
.WillOnce(Return(ByMove(std::move(loadpin_verity))));
// `loadpin_verity` is moved, do not use.
EXPECT_CALL(*mock_platform_, Open(trusted_verity_digests_path_, kReadFlags))
.WillOnce(Return(ByMove(std::move(trusted_verity_digests))));
// `trusted_verity_digests` is moved, do not use.
EXPECT_CALL(*mock_platform_, Ioctl(fd, _, IntPtrCheck(digests_fd)))
.WillOnce(Return(-1));
EXPECT_FALSE(
startup::SetupLoadPinVerityDigests(base_dir_, mock_platform_.get()));
}