blob: 6e684fe4cc7f9c56482ec77bdcb100a2e5e02b9e [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_impl.h"
#include <dirent.h>
#include <stdint.h>
#include <stdlib.h>
#include <algorithm>
#include <list>
#include <vector>
#include "mock_verity_mounter.h"
#include "verity_mounter.h"
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/numerics/safe_conversions.h>
#include <base/strings/string_number_conversions.h>
#include <base/values.h>
#include <crypto/secure_hash.h>
#include <crypto/sha2.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace imageloader {
using testing::_;
namespace {
// Test data always uses the dev key.
constexpr uint8_t kDevPublicKey[] = {
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00, 0x04, 0x7a, 0xaa, 0x2b, 0xf9, 0x3d, 0x7a, 0xbe, 0x35, 0x9a,
0xfc, 0x9f, 0x39, 0x2d, 0x2d, 0x37, 0x07, 0xd4, 0x19, 0x67, 0x67, 0x30,
0xbb, 0x5c, 0x74, 0x22, 0xd5, 0x02, 0x07, 0xaf, 0x6b, 0x12, 0x9d, 0x12,
0xf0, 0x34, 0xfd, 0x1a, 0x7f, 0x02, 0xd8, 0x46, 0x2b, 0x25, 0xca, 0xa0,
0x6e, 0x2b, 0x54, 0x41, 0xee, 0x92, 0xa2, 0x0f, 0xa2, 0x2a, 0xc0, 0x30,
0xa6, 0x8c, 0xd1, 0x16, 0x0a, 0x48, 0xca};
constexpr char kImageLoaderJSON[] =
"{\"image-sha256-hash\":"
"\"6B1E52D97EE63DDFDB3375FDC7F840D54116667E02745D31427C108B3F6885FD\","
"\"table-sha256-hash\":"
"\"24ECDCEC354FB7D020A237908A525CC96F6F77B95E906D5A0E49644D02692F2F\","
"\"version\":\"22.0.0.256\","
"\"manifest-version\":1}";
constexpr uint8_t kImageLoaderSig[] = {
0x30, 0x46, 0x02, 0x21, 0x00, 0x86, 0x4f, 0x25, 0x51, 0x52, 0xb1, 0x79,
0xa3, 0x3e, 0x9b, 0x08, 0x70, 0x10, 0x61, 0xca, 0x63, 0x37, 0x0c, 0xa2,
0x9f, 0x4f, 0x21, 0xd7, 0x37, 0x05, 0x4e, 0x8d, 0x73, 0x39, 0x18, 0x98,
0x94, 0x02, 0x21, 0x00, 0xf1, 0x64, 0x4f, 0x9d, 0x39, 0x58, 0xca, 0xef,
0xe7, 0x4c, 0xa2, 0x5e, 0x00, 0xcb, 0x33, 0x43, 0x7b, 0x9d, 0x72, 0x3e,
0x67, 0x39, 0x2c, 0xfb, 0x3a, 0xcb, 0x80, 0x2b, 0xc4, 0xca, 0xab, 0x8d};
constexpr uint8_t kImageLoaderBadSig[] = {
0x30, 0x44, 0x02, 0x20, 0x0a, 0x75, 0x49, 0xaf, 0x01, 0x3b, 0x48, 0x51,
0x45, 0x74, 0x8b, 0x41, 0x64, 0x21, 0x83, 0xce, 0xf1, 0x78, 0x1d, 0xd0,
0xa8, 0xd6, 0xae, 0x84, 0xf3, 0xc1, 0x3c, 0x3a, 0xee, 0xb4, 0x35, 0xb7,
0x02, 0x20, 0x34, 0xeb, 0xdc, 0x68, 0x2d, 0x8b, 0x4f, 0x64, 0x94, 0x64,
0xa3, 0xd5, 0xde, 0xab, 0xf9, 0xa0, 0xbd, 0xcc, 0xc1, 0x2f, 0x78, 0xd4,
0xe8, 0xed, 0x6a, 0x45, 0x38, 0x53, 0x54, 0xd2, 0xb1, 0x97};
// This is the name of the component used in the test data.
constexpr char kTestComponentName[] = "PepperFlashPlayer";
// This is the version of flash player used in the test data.
constexpr char kTestDataVersion[] = "22.0.0.158";
// This is the version of the updated flash player in the test data.
constexpr char kTestUpdatedVersion[] = "22.0.0.256";
} // namespace {}
class ImageLoaderTest : public testing::Test {
public:
ImageLoaderConfig GetConfig(const char* path) {
std::vector<uint8_t> key(std::begin(kDevPublicKey),
std::end(kDevPublicKey));
auto ops = base::MakeUnique<MockVerityMounter>();
ImageLoaderConfig config(key, path, "/foo", std::move(ops));
return config;
}
base::FilePath GetComponentPath() {
return GetComponentPath(kTestDataVersion);
}
base::FilePath GetComponentPath(const std::string& version) {
const char* src_dir = getenv("CROS_WORKON_SRCROOT");
CHECK(src_dir != nullptr);
base::FilePath component_path(src_dir);
component_path =
component_path.Append("src")
.Append("platform")
.Append("imageloader")
.Append("test")
.Append(version + "_chromeos_intel64_PepperFlashPlayer");
return component_path;
}
void GetFilesInDir(const base::FilePath& dir, std::list<std::string>* files) {
base::FileEnumerator file_enum(dir, false, base::FileEnumerator::FILES);
for (base::FilePath name = file_enum.Next(); !name.empty();
name = file_enum.Next()) {
files->emplace_back(name.BaseName().value());
}
}
};
// Test the RegisterComponent public interface.
TEST_F(ImageLoaderTest, RegisterComponentAndGetVersion) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
// Delete the directory so that the ImageLoader can recreate it with the
// correct permissions.
base::DeleteFile(temp_dir, /*recursive=*/true);
ImageLoaderImpl loader(GetConfig(temp_dir.value().c_str()));
ASSERT_TRUE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
GetComponentPath().value()));
base::FilePath comp_dir = temp_dir.Append(kTestComponentName);
ASSERT_TRUE(base::DirectoryExists(comp_dir));
base::FilePath hint_file = comp_dir.Append("latest-version");
ASSERT_TRUE(base::PathExists(hint_file));
std::string hint_file_contents;
ASSERT_TRUE(
base::ReadFileToStringWithMaxSize(hint_file, &hint_file_contents, 4096));
EXPECT_EQ(kTestDataVersion, hint_file_contents);
base::FilePath version_dir = comp_dir.Append(kTestDataVersion);
ASSERT_TRUE(base::DirectoryExists(version_dir));
std::list<std::string> files;
GetFilesInDir(version_dir, &files);
EXPECT_THAT(files, testing::UnorderedElementsAre(
"imageloader.json", "imageloader.sig.1", "table",
"image.squash", "manifest.fingerprint"));
// Reject a component if the version already exists.
EXPECT_FALSE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
GetComponentPath().value()));
EXPECT_EQ(kTestDataVersion, loader.GetComponentVersion(kTestComponentName));
// Now copy a new version into place.
EXPECT_TRUE(
loader.RegisterComponent(kTestComponentName, kTestUpdatedVersion,
GetComponentPath(kTestUpdatedVersion).value()));
std::string hint_file_contents2;
ASSERT_TRUE(
base::ReadFileToStringWithMaxSize(hint_file, &hint_file_contents2, 4096));
EXPECT_EQ(kTestUpdatedVersion, hint_file_contents2);
base::FilePath version_dir2 = comp_dir.Append(kTestUpdatedVersion);
ASSERT_TRUE(base::DirectoryExists(version_dir2));
std::list<std::string> files2;
GetFilesInDir(version_dir2, &files2);
EXPECT_THAT(files2, testing::UnorderedElementsAre("imageloader.json",
"imageloader.sig.1",
"table", "image.squash"));
EXPECT_EQ(kTestUpdatedVersion,
loader.GetComponentVersion(kTestComponentName));
// Reject rollback to an older version.
EXPECT_FALSE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
GetComponentPath().value()));
EXPECT_EQ(kTestUpdatedVersion,
loader.GetComponentVersion(kTestComponentName));
}
TEST_F(ImageLoaderTest, ECVerify) {
ImageLoaderImpl loader(GetConfig("/nonexistant"));
EXPECT_TRUE(loader.ECVerify(
base::StringPiece(kImageLoaderJSON),
base::StringPiece(reinterpret_cast<const char*>(kImageLoaderSig),
sizeof(kImageLoaderSig))));
EXPECT_FALSE(loader.ECVerify(
base::StringPiece(kImageLoaderJSON),
base::StringPiece(reinterpret_cast<const char*>(kImageLoaderBadSig),
sizeof(kImageLoaderBadSig))));
}
TEST_F(ImageLoaderTest, ManifestFingerPrint) {
ImageLoaderImpl loader(GetConfig("/nonexistant"));
const std::string valid_manifest =
"1.3464353b1ed78574e05f3ffe84b52582572b2fe7202f3824a3761e54ace8bb1";
EXPECT_TRUE(loader.IsValidFingerprintFile(valid_manifest));
const std::string invalid_unicode_manifest = "Ё Ђ Ѓ Є Ѕ І Ї Ј Љ ";
EXPECT_FALSE(loader.IsValidFingerprintFile(invalid_unicode_manifest));
EXPECT_FALSE(loader.IsValidFingerprintFile("\x49\x34\x19-43.*+abc"));
}
TEST_F(ImageLoaderTest, CopyValidComponent) {
ImageLoaderImpl loader(GetConfig("/nonexistant"));
const int image_size = 4096 * 4;
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
std::vector<char> image(image_size, 0xBB);
base::FilePath component_dest = temp_dir.Append("copied-component");
ASSERT_TRUE(loader.CopyComponentDirectory(GetComponentPath(), component_dest,
kTestDataVersion));
// 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(GetComponentPath(), &original_files);
GetFilesInDir(component_dest, &copied_files);
EXPECT_THAT(original_files,
testing::UnorderedElementsAre(
"imageloader.json", "imageloader.sig.1", "manifest.json",
"table", "image.squash", "manifest.fingerprint"));
EXPECT_THAT(copied_files,
testing::UnorderedElementsAre(
"imageloader.json", "imageloader.sig.1", "table",
"image.squash", "manifest.fingerprint"));
}
TEST_F(ImageLoaderTest, CopyComponentWithBadManifest) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
base::FilePath bad_component_dir = temp_dir.Append("bad-component");
ASSERT_TRUE(base::CopyDirectory(GetComponentPath(), bad_component_dir,
true /* recursive */));
base::FilePath manifest = bad_component_dir.Append("imageloader.json");
const char data[] = "c";
ASSERT_TRUE(base::AppendToFile(manifest, data, sizeof(data)));
ImageLoaderImpl loader(GetConfig("/nonexistant"));
EXPECT_FALSE(loader.CopyComponentDirectory(
bad_component_dir, temp_dir.Append("copied-component"),
kTestDataVersion));
}
TEST_F(ImageLoaderTest, CopyValidImage) {
const int image_size = 4096 * 4;
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
base::FilePath image_path = temp_dir.Append("image");
std::vector<char> image(image_size,
0xBB); // large enough to test streaming read.
ASSERT_TRUE(base::WriteFile(image_path, image.data(), image.size()) ==
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());
ImageLoaderImpl loader(GetConfig("/nonexistant"));
base::FilePath image_dest = temp_dir.Append("image.copied");
ASSERT_TRUE(loader.CopyAndHashFile(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_TRUE(memcmp(image.data(), resulting_image.data(), image_size) == 0);
}
TEST_F(ImageLoaderTest, CopyInvalidImage) {
const int image_size = 4096 * 4;
// It doesn't matter what the hash is, because this is testing a mismatch.
std::string hash_str =
"5342065E5D9889739B281D96FD985270A13F2B68A29DD47142ABFA0C2C659AA1";
std::vector<uint8_t> hash;
ASSERT_TRUE(base::HexStringToBytes(hash_str, &hash));
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
base::FilePath image_src = temp_dir.Append("bad_image.squash");
base::FilePath image_dest = temp_dir.Append("image.squash");
std::vector<char> file(image_size, 0xAA);
ASSERT_EQ(image_size, base::WriteFile(image_src, file.data(), image_size));
ImageLoaderImpl loader(GetConfig("/nonexistant"));
EXPECT_FALSE(loader.CopyAndHashFile(image_src, image_dest, hash));
}
TEST_F(ImageLoaderTest, ParseManifest) {
ImageLoaderImpl loader(GetConfig("/nonexistant"));
ImageLoaderImpl::Manifest manifest;
std::string imageloader_sig(reinterpret_cast<const char*>(kImageLoaderSig),
sizeof(kImageLoaderSig));
ASSERT_TRUE(loader.VerifyAndParseManifest(kImageLoaderJSON, imageloader_sig,
&manifest));
EXPECT_EQ(1, manifest.manifest_version);
EXPECT_EQ(kTestUpdatedVersion, manifest.version);
EXPECT_EQ(32, manifest.image_sha256.size());
EXPECT_EQ(32, manifest.table_sha256.size());
std::string bad_manifest = "{\"foo\":\"128.0.0.9\"}";
ImageLoaderImpl::Manifest manifest2;
EXPECT_FALSE(
loader.VerifyAndParseManifest(
bad_manifest,
reinterpret_cast<const char*>(kImageLoaderSig),
&manifest2));
ImageLoaderImpl::Manifest manifest3;
EXPECT_FALSE(
loader.VerifyAndParseManifest(
kImageLoaderJSON,
reinterpret_cast<const char*>(kImageLoaderBadSig),
&manifest3));
}
TEST_F(ImageLoaderTest, MountValidImage) {
std::vector<uint8_t> key(std::begin(kDevPublicKey), std::end(kDevPublicKey));
auto mount_mock = base::MakeUnique<MockVerityMounter>();
ON_CALL(*mount_mock, Mount(_, _, _)).WillByDefault(testing::Return(true));
EXPECT_CALL(*mount_mock, Mount(_, _, _)).Times(2);
base::ScopedTempDir scoped_temp_dir;
base::ScopedTempDir scoped_mount_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(scoped_mount_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
// Delete the directory so that the ImageLoader can recreate it with the
// correct permissions.
base::DeleteFile(temp_dir, /*recursive=*/true);
ImageLoaderConfig config(key, temp_dir.value().c_str(),
scoped_mount_dir.path().value().c_str(),
std::move(mount_mock));
ImageLoaderImpl loader(std::move(config));
// We previously tested RegisterComponent, so assume this works if it reports
// true.
ASSERT_TRUE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
GetComponentPath().value()));
const std::string expected_path =
scoped_mount_dir.path().value() + "/PepperFlashPlayer/22.0.0.158";
EXPECT_EQ(expected_path, loader.LoadComponent(kTestComponentName));
// Let's also test mounting the component at a fixed point.
const std::string expected_path2 =
scoped_mount_dir.path().value() + "/FixedMountPoint";
EXPECT_TRUE(loader.LoadComponent(kTestComponentName, expected_path2));
}
TEST_F(ImageLoaderTest, MountInvalidImage) {
std::vector<uint8_t> key(std::begin(kDevPublicKey), std::end(kDevPublicKey));
auto mount_mock = base::MakeUnique<MockVerityMounter>();
ON_CALL(*mount_mock, Mount(_, _, _)).WillByDefault(testing::Return(true));
EXPECT_CALL(*mount_mock, Mount(_, _, _)).Times(0);
base::ScopedTempDir scoped_temp_dir;
base::ScopedTempDir scoped_mount_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(scoped_mount_dir.CreateUniqueTempDir());
const base::FilePath& temp_dir = scoped_temp_dir.path();
// Delete the directory so that the ImageLoader can recreate it with the
// correct permissions.
base::DeleteFile(temp_dir, /*recursive=*/true);
ImageLoaderConfig config(key, temp_dir.value().c_str(),
scoped_mount_dir.path().value().c_str(),
std::move(mount_mock));
ImageLoaderImpl loader(std::move(config));
// We previously tested RegisterComponent, so assume this works if it reports
// true.
ASSERT_TRUE(loader.RegisterComponent(kTestComponentName, kTestDataVersion,
GetComponentPath().value()));
base::FilePath table = temp_dir.Append(kTestComponentName)
.Append(kTestDataVersion)
.Append("table");
std::string contents = "corrupt";
ASSERT_EQ(static_cast<int>(contents.size()),
base::WriteFile(table, contents.data(), contents.size()));
ASSERT_EQ("", loader.LoadComponent(kTestComponentName));
}
TEST_F(ImageLoaderTest, SetupTable) {
std::string base_table = "0 40 verity payload=ROOT_DEV hashtree=HASH_DEV "
"hashstart=40 alg=sha256 root_hexdigest="
"34663b9920632778d38a0943a5472cae196bd4bf1d7dfa191506e7a8e7ec84d2 "
"salt=fcfc9b5a329e44be73a323188ae75ca644122d920161f672f6935623831d07e2";
// Make sure excess newlines are rejected.
std::string bad_table = base_table + "\n\n";
EXPECT_FALSE(VerityMounter::SetupTable(&bad_table, "/dev/loop6"));
// Make sure it does the right replacements on a simple base table.
std::string good_table = base_table;
EXPECT_TRUE(VerityMounter::SetupTable(&good_table, "/dev/loop6"));
std::string known_good_table =
"0 40 verity payload=/dev/loop6 hashtree=/dev/loop6 "
"hashstart=40 alg=sha256 root_hexdigest="
"34663b9920632778d38a0943a5472cae196bd4bf1d7dfa191506e7a8e7ec84d2 "
"salt=fcfc9b5a329e44be73a323188ae75ca644122d920161f672f6935623831d07e2 "
"error_behavior=eio";
EXPECT_EQ(known_good_table, good_table);
// Make sure the newline is stripped.
std::string good_table_newline = base_table + "\n";
EXPECT_TRUE(VerityMounter::SetupTable(&good_table_newline, "/dev/loop6"));
EXPECT_EQ(known_good_table, good_table_newline);
// Make sure error_behavior isn't appended twice.
std::string good_table_error = base_table + " error_behavior=eio\n";
EXPECT_TRUE(VerityMounter::SetupTable(&good_table_error, "/dev/loop6"));
EXPECT_EQ(known_good_table, good_table_error);
}
} // namespace imageloader