blob: 0125ddc060cf4bd3ea8e06c5a586f2dad976695d [file] [log] [blame] [edit]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "minios/utils.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/test/mock_log.h>
#include <brillo/secure_blob.h>
#include <brillo/udev/mock_udev.h>
#include <brillo/udev/mock_udev_device.h>
#include <brillo/udev/mock_udev_enumerate.h>
#include <brillo/udev/mock_udev_list_entry.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libcrossystem/crossystem.h>
#include <libcrossystem/crossystem_fake.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <vpd/fake_vpd.h>
#include <vpd/vpd.h>
#include "minios/log_store_manifest.h"
#include "minios/mock_cgpt_util.h"
#include "minios/mock_process_manager.h"
namespace minios {
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrEq;
using ::testing::StrictMock;
const brillo::SecureBlob kValidKey{"thisisa32bytestring1234567890abc"};
const brillo::SecureBlob kTestData{
"test data to verify encryption and decryption"};
class UtilTest : public ::testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
file_path_ = tmp_dir_.GetPath().Append("file");
content_ = "line1\nline2\n" + std::string(100, 'a') + "\nb";
ASSERT_TRUE(base::WriteFile(file_path_, content_));
ASSERT_TRUE(
CreateDirectory(tmp_dir_.GetPath().Append("sys/firmware/vpd/ro")));
}
protected:
base::ScopedTempDir tmp_dir_;
base::FilePath file_path_;
std::string content_;
};
TEST_F(UtilTest, ReadFileContentOffsets) {
auto [success, content] = ReadFileContentWithinRange(
file_path_, /*start_offset=*/0, /*end_offset=*/1, /*num_cols=*/1);
EXPECT_TRUE(success);
EXPECT_EQ(content, "l\n");
}
TEST_F(UtilTest, ReadFileContentOffsets2) {
auto [success, content] = ReadFileContentWithinRange(
file_path_, /*start_offset=*/0, /*end_offset=*/1, /*num_cols=*/2);
EXPECT_TRUE(success);
EXPECT_EQ(content, "l");
}
TEST_F(UtilTest, ReadFileContentOffsets3) {
auto [success, content] = ReadFileContentWithinRange(
file_path_, /*start_offset=*/4, /*end_offset=*/6, /*num_cols=*/1);
EXPECT_TRUE(success);
EXPECT_EQ(content, "1\n");
}
TEST_F(UtilTest, ReadFileContentOffsets4) {
auto [success, content] = ReadFileContentWithinRange(
file_path_, /*start_offset=*/2, /*end_offset=*/7, /*num_cols=*/2);
EXPECT_TRUE(success);
EXPECT_EQ(content, "ne\n1\nl");
}
TEST_F(UtilTest, ReadFileContentMissingFile) {
auto [success, content, bytes_read] =
ReadFileContent(base::FilePath("/a/b/foobar"), 1, 1, 1);
EXPECT_FALSE(success);
}
TEST_F(UtilTest, ReadFileContentWrappedTextCutoff) {
auto [success, content, bytes_read] = ReadFileContent(
file_path_, /*offset=*/0, /*num_lines=*/3, /*num_cols=*/4);
EXPECT_TRUE(success);
EXPECT_EQ(content, "line\n1\nline\n");
EXPECT_LT(bytes_read, content.size());
}
TEST_F(UtilTest, ReadFileContentWrappedTextPerfectAlignmentColumns) {
auto [success, content, bytes_read] = ReadFileContent(
file_path_, /*offset=*/0, /*num_lines=*/5, /*num_cols=*/5);
EXPECT_TRUE(success);
// There should be no double newlining.
EXPECT_EQ(content, "line1\nline2\naaaaa\naaaaa\naaaaa\n");
EXPECT_LT(bytes_read, content.size());
}
TEST_F(UtilTest, ReadFileContentWrappedTextExceedsColumnLimit) {
auto [success, content, bytes_read] = ReadFileContent(
file_path_, /*offset=*/0, /*num_lines=*/5, /*num_cols=*/6);
EXPECT_TRUE(success);
EXPECT_EQ(content, "line1\nline2\naaaaaa\naaaaaa\naaaaaa\n");
EXPECT_LT(bytes_read, content.size());
}
TEST_F(UtilTest, ReadFileContentZeroLimits) {
auto [success, content, bytes_read] = ReadFileContent(
file_path_, /*offset=*/0, /*num_lines=*/0, /*num_cols=*/0);
EXPECT_TRUE(success);
EXPECT_EQ(content, "");
EXPECT_EQ(bytes_read, 0);
}
TEST_F(UtilTest, ReadFileContent) {
auto [success, content, bytes_read] =
ReadFileContent(file_path_, /*offset=*/0, /*num_lines=*/4,
/*num_cols=*/200);
EXPECT_TRUE(success);
EXPECT_EQ(content, content_);
EXPECT_EQ(bytes_read, content_.size());
}
TEST_F(UtilTest, ReadFileContentStartOffset) {
auto [success, content, bytes_read] =
ReadFileContent(file_path_, /*offset=*/12, /*num_lines=*/3,
/*num_cols=*/1);
EXPECT_TRUE(success);
EXPECT_EQ(content, "a\na\na\n");
EXPECT_EQ(bytes_read, 3);
}
TEST_F(UtilTest, GetKeyboardLayoutFailure) {
auto mock_process_manager_ = std::make_shared<MockProcessManager>();
EXPECT_CALL(*mock_process_manager_, RunCommandWithOutput(_, _, _, _))
.WillOnce(
testing::DoAll(testing::SetArgPointee<2>(""), testing::Return(true)));
EXPECT_EQ(GetKeyboardLayout(mock_process_manager_), "us");
// Badly formatted.
EXPECT_CALL(*mock_process_manager_, RunCommandWithOutput(_, _, _, _))
.WillOnce(testing::DoAll(testing::SetArgPointee<2>("xkbeng:::"),
testing::Return(true)));
EXPECT_EQ(GetKeyboardLayout(mock_process_manager_), "us");
// Failed.
EXPECT_CALL(*mock_process_manager_, RunCommandWithOutput(_, _, _, _))
.WillOnce(testing::DoAll(testing::Return(false)));
EXPECT_EQ(GetKeyboardLayout(mock_process_manager_), "us");
}
TEST_F(UtilTest, GetKeyboardLayout) {
auto mock_process_manager_ = std::make_shared<MockProcessManager>();
EXPECT_CALL(*mock_process_manager_, RunCommandWithOutput(_, _, _, _))
.WillOnce(testing::DoAll(testing::SetArgPointee<2>("xkb:en::eng"),
testing::Return(true)));
EXPECT_EQ(GetKeyboardLayout(mock_process_manager_), "en");
}
TEST(UtilsTest, AlertLogTagCreationTest) {
auto category = "test_category";
auto default_component = "CoreServicesAlert";
EXPECT_EQ(base::StringPrintf("[%s<%s>] ", default_component, category),
AlertLogTag(category));
}
TEST(UtilsTest, AlertLogTagLogTest) {
base::test::MockLog mock_log;
mock_log.StartCapturingLogs();
auto category = "test_category";
auto test_msg = "Test Error Message: ";
auto test_id = 10;
auto expected_log = base::StringPrintf(
"%s%s%d", AlertLogTag(category).c_str(), test_msg, test_id);
EXPECT_CALL(mock_log,
Log(::logging::LOGGING_ERROR, _, _, _, HasSubstr(expected_log)));
LOG(ERROR) << AlertLogTag(category) << test_msg << test_id;
}
TEST(UtilsTest, MountStatefulPartitionTest) {
auto mock_process_manager_ =
std::make_shared<StrictMock<MockProcessManager>>();
std::vector<std::string> expected_args = {
"/usr/bin/stateful_partition_for_recovery", "--mount"};
EXPECT_CALL(*mock_process_manager_, RunCommand(expected_args, _))
.WillOnce(::testing::Return(0));
EXPECT_TRUE(MountStatefulPartition(mock_process_manager_));
// Verify error results.
EXPECT_CALL(*mock_process_manager_, RunCommand)
.WillOnce(::testing::Return(1));
EXPECT_FALSE(MountStatefulPartition(mock_process_manager_));
EXPECT_FALSE(MountStatefulPartition(nullptr));
}
TEST(UtilsTest, UnmountPathTest) {
auto mock_process_manager_ =
std::make_shared<StrictMock<MockProcessManager>>();
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
std::vector<std::string> expected_args = {"/bin/busybox", "umount",
temp_dir.GetPath().value()};
EXPECT_CALL(*mock_process_manager_, RunCommand(expected_args, _))
.WillOnce(::testing::Return(0));
EXPECT_TRUE(UnmountPath(mock_process_manager_, temp_dir.GetPath()));
// Verify invalid process manager.
EXPECT_FALSE(UnmountPath(nullptr, temp_dir.GetPath()));
}
TEST(UtilsTest, CompressLogsTest) {
auto mock_process_manager = std::make_shared<MockProcessManager>();
const auto archive_path = "/path/to/store/archive";
std::vector<std::string> expected_cmd = {"/bin/tar",
"-czhf",
archive_path,
"/var/log/update_engine.log",
"/var/log/upstart.log",
"/var/log/minios.log"};
EXPECT_CALL(*mock_process_manager, RunCommand(expected_cmd, _));
CompressLogs(mock_process_manager, base::FilePath{archive_path});
}
TEST(UtilsTest, KernelSizeTest) {
auto mock_process_manager = std::make_shared<MockProcessManager>();
const auto device_path = "/dev/device0p1";
std::vector<std::string> expected_cmd = {"/usr/bin/futility", "show", "-P",
device_path};
const std::string futility_output =
std::string{"kernel_partition::/dev/nvme0n1p9\n"} +
std::string{"kernel::keyblock::size::1\n"} +
std::string{"kernel::preamble::size::10\n"} +
std::string{"kernel::preamble::body::load_address::0x100000\n"} +
std::string{"kernel::body::size::100\n"};
EXPECT_CALL(*mock_process_manager,
RunCommandWithOutput(expected_cmd, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(0), SetArgPointee<2>(futility_output),
testing::Return(true)));
EXPECT_THAT(KernelSize(mock_process_manager, base::FilePath{device_path}),
Optional(111));
}
TEST(UtilsTest, KernelSizeFailuresTest) {
auto mock_process_manager = std::make_shared<MockProcessManager>();
const auto device_path = "/dev/device0p1";
std::vector<std::string> expected_cmd = {"/usr/bin/futility", "show", "-P",
device_path};
// Test out empty string.
std::string futility_output = "";
EXPECT_CALL(*mock_process_manager,
RunCommandWithOutput(expected_cmd, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(0), SetArgPointee<2>(futility_output),
testing::Return(true)));
EXPECT_EQ(KernelSize(mock_process_manager, base::FilePath{device_path}),
std::nullopt);
// Missing kernel body size.
futility_output = std::string{"kernel::keyblock::size::2232\n"} +
std::string{"kernel::preamble::size::63304\n"};
mock_process_manager = std::make_unique<MockProcessManager>();
EXPECT_CALL(*mock_process_manager,
RunCommandWithOutput(expected_cmd, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(0), SetArgPointee<2>(futility_output),
testing::Return(true)));
EXPECT_EQ(KernelSize(mock_process_manager, base::FilePath{device_path}),
std::nullopt);
// 0 kernel body size.
futility_output = std::string{"kernel::keyblock::size::2232\n"} +
std::string{"kernel::preamble::size::63304\n"} +
std::string{"kernel::body::size::0\n"};
mock_process_manager = std::make_unique<MockProcessManager>();
EXPECT_CALL(*mock_process_manager,
RunCommandWithOutput(expected_cmd, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(0), SetArgPointee<2>(futility_output),
testing::Return(true)));
EXPECT_EQ(KernelSize(mock_process_manager, base::FilePath{device_path}),
std::nullopt);
// Non number value for keyblock size.
futility_output = std::string{"keyblock::size::bad_val\n"} +
std::string{"kernel::preamble::size::63304\n"} +
std::string{"kernel::preamble::body::size::50\n"};
mock_process_manager = std::make_unique<MockProcessManager>();
EXPECT_CALL(*mock_process_manager,
RunCommandWithOutput(expected_cmd, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(0), SetArgPointee<2>(futility_output),
testing::Return(true)));
EXPECT_EQ(KernelSize(mock_process_manager, base::FilePath{device_path}),
std::nullopt);
}
TEST(UtilsTest, GetRemovableDevices) {
auto device_list_entry =
std::make_unique<NiceMock<brillo::MockUdevListEntry>>();
auto device = std::make_unique<StrictMock<brillo::MockUdevDevice>>();
auto mock_udev_enumerate =
std::make_unique<StrictMock<brillo::MockUdevEnumerate>>();
auto mock_udev = std::make_unique<StrictMock<brillo::MockUdev>>();
constexpr auto& device_node = "/dev/sda1";
EXPECT_CALL(*mock_udev_enumerate, AddMatchSubsystem(StrEq("block")))
.WillOnce(Return(true));
EXPECT_CALL(*mock_udev_enumerate,
AddMatchProperty(StrEq("ID_FS_USAGE"), StrEq("filesystem")))
.WillOnce(Return(true));
EXPECT_CALL(*mock_udev_enumerate, ScanDevices()).WillOnce(Return(true));
// Setup device to be removable.
EXPECT_CALL(*device, GetSysAttributeValue(_)).WillOnce(Return("1"));
EXPECT_CALL(*device, GetDeviceNode()).WillOnce(Return(device_node));
EXPECT_CALL(*mock_udev_enumerate, GetListEntry())
.WillOnce(Return(std::move(device_list_entry)));
EXPECT_CALL(*mock_udev, CreateDeviceFromSysPath(_))
.WillOnce(Return(std::move(device)));
EXPECT_CALL(*mock_udev, CreateEnumerate())
.WillOnce(Return(std::move(mock_udev_enumerate)));
std::vector<base::FilePath> removable_devices;
EXPECT_TRUE(GetRemovableDevices(removable_devices, std::move(mock_udev)));
// Expect to get back the one device path we have setup.
EXPECT_THAT(removable_devices, ElementsAre(base::FilePath{device_node}));
}
TEST(UtilsTest, GetLogStoreKeyTest) {
auto vpd = std::make_shared<vpd::Vpd>(std::make_unique<vpd::FakeVpd>());
const auto kValidHexKey =
brillo::SecureBlobToSecureHex(kValidKey).to_string();
ASSERT_TRUE(
vpd->WriteValues(vpd::VpdRw, {{"minios_log_store_key", kValidHexKey}}));
const auto log_store_key = GetLogStoreKey(vpd);
ASSERT_TRUE(log_store_key.has_value());
EXPECT_EQ(log_store_key.value(), kValidKey);
}
TEST(UtilsTest, GetLogStoreKeyFailureTest) {
auto vpd = std::make_shared<vpd::Vpd>(std::make_unique<vpd::FakeVpd>());
ASSERT_TRUE(
vpd->WriteValues(vpd::VpdRw, {{"minios_log_store_key", "short_key"}}));
const auto log_store_key = GetLogStoreKey(vpd);
EXPECT_FALSE(log_store_key.has_value());
}
TEST(UtilsTest, LogStoreKeyValidTest) {
const auto& kShortKey = brillo::SecureBlob{"short"};
const auto& kLongKey =
brillo::SecureBlob{"thisisa32bytestring1234567890abc_____"};
const auto& kEmptyKey = brillo::SecureBlob{""};
EXPECT_TRUE(IsLogStoreKeyValid(kValidKey));
EXPECT_FALSE(IsLogStoreKeyValid(kShortKey));
EXPECT_FALSE(IsLogStoreKeyValid(kLongKey));
EXPECT_FALSE(IsLogStoreKeyValid(kEmptyKey));
}
TEST(UtilsTest, SaveLogKeyTest) {
const auto kValidHexKey =
brillo::SecureBlobToSecureHex(kValidKey).to_string();
auto vpd = std::make_shared<vpd::Vpd>(std::make_unique<vpd::FakeVpd>());
EXPECT_TRUE(SaveLogStoreKey(vpd, kValidKey));
EXPECT_THAT(vpd->GetValue(vpd::VpdRw, "minios_log_store_key"),
Optional(kValidHexKey));
}
TEST(UtilsTest, ClearLogStoreKeyTest) {
// Zero string of hex key size.
const std::string kExpectedNullKey(kLogStoreKeySizeBytes * 2, '0');
auto vpd = std::make_shared<vpd::Vpd>(std::make_unique<vpd::FakeVpd>());
EXPECT_TRUE(ClearLogStoreKey(vpd));
EXPECT_THAT(vpd->GetValue(vpd::VpdRw, "minios_log_store_key"),
Optional(kExpectedNullKey));
}
TEST(UtilsTest, EncryptDecryptTest) {
const auto& encrypted_archive = EncryptLogArchive(kTestData, kValidKey);
EXPECT_TRUE(encrypted_archive.has_value());
const auto& archive = DecryptLogArchive(encrypted_archive.value(), kValidKey);
const auto& empty_decrypt_contents =
DecryptLogArchive(EncryptedLogFile{}, kValidKey);
EXPECT_TRUE(archive.has_value());
EXPECT_EQ(kTestData, archive);
EXPECT_EQ(empty_decrypt_contents, std::nullopt);
}
TEST(UtilsTest, ReadFileToSecureBlobTest) {
base::ScopedTempDir tmp_dir_;
ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
const auto file_path = tmp_dir_.GetPath().Append("file");
ASSERT_TRUE(base::WriteFile(file_path, kTestData));
const auto& file_contents = ReadFileToSecureBlob(file_path);
EXPECT_TRUE(file_contents.has_value());
EXPECT_EQ(file_contents.value(), kTestData);
}
TEST(UtilsTest, WriteSecureBlobToFileTest) {
base::ScopedTempDir tmp_dir_;
ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
const auto file_path = tmp_dir_.GetPath().Append("file");
EXPECT_TRUE(WriteSecureBlobToFile(file_path, kTestData));
const auto& file_contents = ReadFileToSecureBlob(file_path);
EXPECT_TRUE(file_contents.has_value());
EXPECT_EQ(file_contents.value(), kTestData);
}
TEST(UtilsTest, GetPartitionSizeTest) {
const auto mock_cgpt_util = std::make_shared<MockCgptUtil>();
EXPECT_CALL(*mock_cgpt_util, GetSize(1)).WillOnce(Return(10));
EXPECT_CALL(*mock_cgpt_util, GetSize(2)).WillOnce(Return(std::nullopt));
EXPECT_THAT(GetPartitionSize(1, mock_cgpt_util), Optional(10 * kBlockSize));
EXPECT_EQ(GetPartitionSize(2, mock_cgpt_util), std::nullopt);
}
TEST(UtilsTest, GetMiniOsPriorityPartitionTest) {
const auto stub_crossystem = std::make_shared<crossystem::Crossystem>(
std::make_unique<crossystem::fake::CrossystemFake>());
EXPECT_EQ(GetMiniOsPriorityPartition(stub_crossystem), std::nullopt);
stub_crossystem->VbSetSystemPropertyString(
crossystem::Crossystem::kMiniosPriorityProperty, "A");
EXPECT_THAT(GetMiniOsPriorityPartition(stub_crossystem), Optional(9));
stub_crossystem->VbSetSystemPropertyString(
crossystem::Crossystem::kMiniosPriorityProperty, "B");
EXPECT_THAT(GetMiniOsPriorityPartition(stub_crossystem), Optional(10));
stub_crossystem->VbSetSystemPropertyString(
crossystem::Crossystem::kMiniosPriorityProperty, "C");
EXPECT_EQ(GetMiniOsPriorityPartition(stub_crossystem), std::nullopt);
}
TEST(UtilsTest, ExtractArchiveTest) {
auto mock_process_manager = std::make_shared<MockProcessManager>();
base::FilePath archive_path;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(
base::CreateTemporaryFileInDir(temp_dir.GetPath(), &archive_path));
std::vector<std::string> expected_cmd = {"/bin/tar", "-xzf",
archive_path.value(), "-C",
archive_path.DirName().value()};
EXPECT_CALL(*mock_process_manager, RunCommand(expected_cmd, _))
.WillOnce(Return(0));
EXPECT_TRUE(ExtractArchive(mock_process_manager, archive_path,
base::FilePath{archive_path}.DirName(), {}));
}
TEST(UtilsTest, ExtractArchiveStripTest) {
auto mock_process_manager = std::make_shared<MockProcessManager>();
base::FilePath archive_path;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(
base::CreateTemporaryFileInDir(temp_dir.GetPath(), &archive_path));
std::vector<std::string> expected_cmd = {"/bin/tar",
"-xzf",
archive_path.value(),
"-C",
temp_dir.GetPath().value(),
"--strip-components=2"};
EXPECT_CALL(*mock_process_manager, RunCommand(expected_cmd, _))
.WillOnce(Return(0));
EXPECT_TRUE(ExtractArchive(mock_process_manager, archive_path,
temp_dir.GetPath(), {"--strip-components=2"}));
}
} // namespace minios