blob: 9b90897e553615d9bbbd9561f05fd93d6a95ba8d [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "imageloader/component.h"
#include <stdint.h>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <crypto/secure_hash.h>
#include <crypto/sha2.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "imageloader/imageloader_impl.h"
#include "imageloader/mock_helper_process_proxy.h"
#include "imageloader/test_utilities.h"
namespace imageloader {
using testing::_;
class ComponentTest : public testing::Test {
public:
ComponentTest() {
keys_.push_back(std::vector<uint8_t>(std::begin(kDevPublicKey),
std::end(kDevPublicKey)));
CHECK(scoped_temp_dir_.CreateUniqueTempDir());
temp_dir_ = scoped_temp_dir_.GetPath();
CHECK(base::SetPosixFilePermissions(temp_dir_, kComponentDirPerms));
}
bool TestCopyWithCorruptFile(const std::string& component_name,
const std::string& file_name) {
base::FilePath bad_component_dir = temp_dir_.Append(component_name);
if (!base::CreateDirectory(bad_component_dir))
return false;
if (!base::SetPosixFilePermissions(bad_component_dir, kComponentDirPerms))
return false;
std::unique_ptr<Component> component =
Component::Create(GetTestComponentPath(), keys_);
if (!component || !component->CopyTo(bad_component_dir))
return false;
std::unique_ptr<Component> bad_component =
Component::Create(bad_component_dir, keys_);
if (!bad_component)
return false;
base::FilePath file = bad_component_dir.Append(file_name);
const char data[] = "c";
if (!base::AppendToFile(file, data, sizeof(data)))
return false;
base::FilePath bad_component_dest =
temp_dir_.Append(component_name + "invalid");
if (!base::CreateDirectory(bad_component_dest))
return false;
if (!base::SetPosixFilePermissions(bad_component_dest, kComponentDirPerms))
return false;
return bad_component->CopyTo(bad_component_dest) == false;
}
bool TestInitComponentWithCorruptFile(const std::string& component_name,
const std::string& file_name) {
base::FilePath bad_component_dir = temp_dir_.Append(component_name);
if (!base::CreateDirectory(bad_component_dir))
return false;
if (!base::SetPosixFilePermissions(bad_component_dir, kComponentDirPerms))
return false;
std::unique_ptr<Component> component =
Component::Create(GetTestComponentPath(), keys_);
if (!component || !component->CopyTo(bad_component_dir))
return false;
base::FilePath file = bad_component_dir.Append(file_name);
// Read the file out and change the last byte.
std::string file_contents;
if (!base::ReadFileToString(file, &file_contents))
return false;
file_contents[file_contents.size() - 1] =
~file_contents[file_contents.size() - 1];
if (!base::WriteFile(file, file_contents.data(), file_contents.size())) {
return false;
}
std::unique_ptr<Component> bad_component =
Component::Create(bad_component_dir, keys_);
return bad_component == nullptr;
}
bool CompareFileContents(const base::FilePath& src,
const base::FilePath& dest,
const std::list<std::string>& filenames) {
for (const std::string& name : filenames) {
std::string source_file_contents;
std::string dest_file_contents;
if (!base::ReadFileToString(src.Append(name), &source_file_contents))
return false;
if (!base::ReadFileToString(dest.Append(name), &dest_file_contents))
return false;
if (source_file_contents != dest_file_contents) {
LOG(ERROR) << "File contents does not match for file: " << name;
return false;
}
}
return true;
}
Keys keys_;
base::ScopedTempDir scoped_temp_dir_;
base::FilePath temp_dir_;
};
TEST_F(ComponentTest, InitComponentAndCheckManifest) {
std::unique_ptr<Component> component =
Component::Create(GetTestComponentPath(), keys_);
ASSERT_NE(nullptr, component);
EXPECT_EQ(1, component->manifest().manifest_version());
EXPECT_EQ(kTestDataVersion, component->manifest().version());
// Don't hardcode the sha256 hashes, but run some validity checks.
EXPECT_EQ(crypto::kSHA256Length, component->manifest().image_sha256().size());
EXPECT_EQ(crypto::kSHA256Length, component->manifest().table_sha256().size());
EXPECT_NE(component->manifest().image_sha256(),
component->manifest().table_sha256());
}
TEST_F(ComponentTest, TestCopyAndMountComponentExt4) {
std::unique_ptr<Component> component =
Component::Create(GetTestDataPath("ext4_component"), keys_);
ASSERT_NE(nullptr, component);
const base::FilePath copied_dir = temp_dir_.Append("dest");
ASSERT_TRUE(base::CreateDirectory(copied_dir));
ASSERT_TRUE(base::SetPosixFilePermissions(copied_dir, kComponentDirPerms));
ASSERT_TRUE(component->CopyTo(copied_dir));
std::unique_ptr<Component> copied_component =
Component::Create(copied_dir, keys_);
ASSERT_NE(nullptr, copied_component);
const base::FilePath mount_dir = temp_dir_.Append("mount");
ASSERT_TRUE(base::CreateDirectory(copied_dir));
ASSERT_TRUE(base::SetPosixFilePermissions(copied_dir, kComponentDirPerms));
// Note: this fails to test the actual mounting process since it is just a
// mock here. The platform_ImageLoader autotest tests the real helper
// process running as a dbus service.
auto helper_mock = std::make_unique<MockHelperProcessProxy>();
EXPECT_CALL(*helper_mock, SendMountCommand(_, _, FileSystem::kExt4, _))
.Times(1);
ON_CALL(*helper_mock, SendMountCommand(_, _, _, _))
.WillByDefault(testing::Return(true));
ASSERT_TRUE(copied_component->Mount(helper_mock.get(), mount_dir));
}
TEST_F(ComponentTest, TestCopyAndMountComponentSquashfs) {
std::unique_ptr<Component> component =
Component::Create(GetTestComponentPath(), keys_);
ASSERT_NE(nullptr, component);
const base::FilePath copied_dir = temp_dir_.Append("dest");
ASSERT_TRUE(base::CreateDirectory(copied_dir));
ASSERT_TRUE(base::SetPosixFilePermissions(copied_dir, kComponentDirPerms));
ASSERT_TRUE(component->CopyTo(copied_dir));
std::unique_ptr<Component> copied_component =
Component::Create(copied_dir, keys_);
ASSERT_NE(nullptr, copied_component);
const base::FilePath mount_dir = temp_dir_.Append("mount");
ASSERT_TRUE(base::CreateDirectory(copied_dir));
ASSERT_TRUE(base::SetPosixFilePermissions(copied_dir, kComponentDirPerms));
// Note: this fails to test the actual mounting process since it is just a
// mock here. The platform_ImageLoader autotest tests the real helper
// process running as a dbus service.
auto helper_mock = std::make_unique<MockHelperProcessProxy>();
EXPECT_CALL(*helper_mock, SendMountCommand(_, _, FileSystem::kSquashFS, _))
.Times(1);
ON_CALL(*helper_mock, SendMountCommand(_, _, _, _))
.WillByDefault(testing::Return(true));
ASSERT_TRUE(copied_component->Mount(helper_mock.get(), mount_dir));
}
TEST_F(ComponentTest, CheckFilesAfterCopy) {
std::unique_ptr<Component> component =
Component::Create(GetTestComponentPath(), keys_);
ASSERT_NE(nullptr, component);
const base::FilePath copied_dir = temp_dir_.Append("dest");
ASSERT_TRUE(base::CreateDirectory(copied_dir));
ASSERT_TRUE(base::SetPosixFilePermissions(copied_dir, kComponentDirPerms));
ASSERT_TRUE(component->CopyTo(copied_dir));
std::unique_ptr<Component> copied_component =
Component::Create(copied_dir, keys_);
ASSERT_NE(nullptr, copied_component);
// Check that all the files are present, except for the manifest.json which
// should be discarded.
std::list<std::string> original_files;
std::list<std::string> copied_files;
GetFilesInDir(GetTestComponentPath(), &original_files);
GetFilesInDir(copied_dir, &copied_files);
EXPECT_THAT(original_files,
testing::UnorderedElementsAre(
"imageloader.json", "imageloader.sig.1", "manifest.json",
"table", "image.squash", "manifest.fingerprint"));
ASSERT_THAT(copied_files,
testing::UnorderedElementsAre(
"imageloader.json", "imageloader.sig.1", "table",
"image.squash", "manifest.fingerprint"));
EXPECT_TRUE(
CompareFileContents(GetTestComponentPath(), copied_dir, copied_files));
}
TEST_F(ComponentTest, IsValidFingerprintFile) {
const std::string valid_manifest =
"1.3464353b1ed78574e05f3ffe84b52582572b2fe7202f3824a3761e54ace8bb1";
EXPECT_TRUE(Component::IsValidFingerprintFile(valid_manifest));
const std::string invalid_unicode_manifest = "Ё Ђ Ѓ Є Ѕ І Ї Ј Љ ";
EXPECT_FALSE(Component::IsValidFingerprintFile(invalid_unicode_manifest));
EXPECT_FALSE(Component::IsValidFingerprintFile("\x49\x34\x19-43.*+abc"));
}
TEST_F(ComponentTest, InitComponentWithBadFiles) {
EXPECT_TRUE(
TestInitComponentWithCorruptFile("bad-component1", "imageloader.json"));
EXPECT_TRUE(
TestInitComponentWithCorruptFile("bad-component2", "imageloader.sig.1"));
}
// Now corrupt the manifest of an already initialized component to verify that
// the copy operation fails.
TEST_F(ComponentTest, CopyWithBadFiles) {
EXPECT_TRUE(TestCopyWithCorruptFile("bad-component1", "image.squash"));
EXPECT_TRUE(TestCopyWithCorruptFile("bad-component2", "table"));
EXPECT_TRUE(
TestCopyWithCorruptFile("bad-component3", "manifest.fingerprint"));
}
TEST_F(ComponentTest, CopyValidImage) {
const int image_size = 4096 * 4;
base::FilePath image_path = temp_dir_.Append("image");
std::vector<char> image(image_size,
0xBB); // large enough to test streaming read.
ASSERT_EQ(image_size,
base::WriteFile(image_path, image.data(), image.size()));
std::vector<uint8_t> hash(crypto::kSHA256Length);
std::unique_ptr<crypto::SecureHash> sha256(
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
sha256->Update(image.data(), image.size());
sha256->Finish(hash.data(), hash.size());
Component component(GetTestComponentPath(), 1);
base::FilePath image_dest = temp_dir_.Append("image.copied");
ASSERT_TRUE(component.CopyComponentFile(image_path, image_dest, hash));
// Check if the image file actually exists and has the correct contents.
std::string resulting_image;
ASSERT_TRUE(base::ReadFileToStringWithMaxSize(image_dest, &resulting_image,
image_size));
EXPECT_EQ(0, memcmp(image.data(), resulting_image.data(), image_size));
}
} // namespace imageloader