You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
12 KiB

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/strings.h>
#include <gtest/gtest.h>
#include <verity/hash_tree_builder.h>
#include "../fec_private.h"
#include "fec/io.h"
class FecUnitTest : public ::testing::Test {
protected:
void SetUp() override {
// Construct a 1 MiB image as file system.
image_.reserve(1024 * 1024);
for (unsigned i = 0; i <= 255; i++) {
std::vector<uint8_t> tmp_vec(4096, i);
image_.insert(image_.end(), tmp_vec.begin(), tmp_vec.end());
}
}
void BuildHashtree(const std::string &hash_name) {
// Build the hashtree.
HashTreeBuilder builder(4096, HashTreeBuilder::HashFunction(hash_name));
// Use a random salt.
salt_ = std::vector<uint8_t>(64, 10);
ASSERT_TRUE(builder.Initialize(image_.size(), salt_));
ASSERT_TRUE(builder.Update(image_.data(), image_.size()));
ASSERT_TRUE(builder.BuildHashTree());
root_hash_ = builder.root_hash();
TemporaryFile temp_file;
ASSERT_TRUE(builder.WriteHashTreeToFd(temp_file.fd, 0));
android::base::ReadFileToString(temp_file.path, &hashtree_content_);
}
// Builds the verity metadata and appends the bytes to the image.
void BuildAndAppendsVerityMetadata() {
BuildHashtree("sha256");
// Append the hashtree to the end of image.
image_.insert(image_.end(), hashtree_content_.begin(),
hashtree_content_.end());
// The metadata table has the format: "1 block_device, block_device,
// BLOCK_SIZE, BLOCK_SIZE, data_blocks, data_blocks, 'sha256',
// root_hash, salt".
std::vector<std::string> table = {
"1",
"fake_block_device",
"fake_block_device",
"4096",
"4096",
"256",
"256",
"sha256",
HashTreeBuilder::BytesArrayToString(root_hash_),
HashTreeBuilder::BytesArrayToString(salt_),
};
verity_table_ = android::base::Join(table, ' ');
verity_header_ = {
0xb001b001, 0, {}, static_cast<unsigned int>(verity_table_.size())
};
// Construct the verity metadata with header, table, and padding.
constexpr auto VERITY_META_SIZE = 8 * 4096;
image_.insert(image_.end(),
reinterpret_cast<uint8_t *>(&verity_header_),
reinterpret_cast<uint8_t *>(&verity_header_) +
sizeof(verity_header_));
image_.insert(image_.end(), verity_table_.data(),
verity_table_.data() + verity_table_.size());
std::vector<uint8_t> padding(
VERITY_META_SIZE - sizeof(verity_header_) - verity_table_.size(),
0);
image_.insert(image_.end(), padding.begin(), padding.end());
}
static void BuildAndAppendsEccImage(const std::string &image_name,
const std::string &fec_name) {
std::vector<std::string> cmd = { "fec", "--encode", "--roots",
"2", image_name, fec_name };
ASSERT_EQ(0, std::system(android::base::Join(cmd, ' ').c_str()));
}
void AddAvbHashtreeFooter(const std::string &image_name,
std::string algorithm = "sha256") {
salt_ = std::vector<uint8_t>(64, 10);
std::vector<std::string> cmd = {
"avbtool", "add_hashtree_footer",
"--salt", HashTreeBuilder::BytesArrayToString(salt_),
"--hash_algorithm", algorithm,
"--image", image_name,
};
ASSERT_EQ(0, std::system(android::base::Join(cmd, ' ').c_str()));
BuildHashtree(algorithm);
}
std::vector<uint8_t> image_;
std::vector<uint8_t> salt_;
std::vector<uint8_t> root_hash_;
std::string hashtree_content_;
verity_header verity_header_;
std::string verity_table_;
};
TEST_F(FecUnitTest, LoadVerityImage_ParseVerity) {
TemporaryFile verity_image;
BuildAndAppendsVerityMetadata();
ASSERT_TRUE(android::base::WriteFully(verity_image.fd, image_.data(),
image_.size()));
struct fec_handle *handle = nullptr;
ASSERT_EQ(0, fec_open(&handle, verity_image.path, O_RDONLY, FEC_FS_EXT4, 2));
std::unique_ptr<fec_handle> guard(handle);
ASSERT_EQ(image_.size(), handle->size);
ASSERT_EQ(1024 * 1024, handle->data_size); // filesystem size
ASSERT_EQ(1024 * 1024 + hashtree_content_.size(),
handle->verity.metadata_start);
ASSERT_EQ(verity_header_.length, handle->verity.header.length);
ASSERT_EQ(verity_table_, handle->verity.table);
// check the hashtree.
ASSERT_EQ(salt_, handle->hashtree().salt);
ASSERT_EQ(1024 * 1024, handle->hashtree().hash_start);
// the fec hashtree only stores the hash of the lowest level.
ASSERT_EQ(std::vector<uint8_t>(hashtree_content_.begin() + 4096,
hashtree_content_.end()),
handle->hashtree().hash_data);
uint64_t hash_size =
verity_get_size(handle->hashtree().data_blocks * FEC_BLOCKSIZE, nullptr,
nullptr, SHA256_DIGEST_LENGTH);
ASSERT_EQ(hashtree_content_.size(), hash_size);
}
TEST_F(FecUnitTest, LoadVerityImage_ParseEcc) {
TemporaryFile verity_image;
BuildAndAppendsVerityMetadata();
ASSERT_TRUE(android::base::WriteFully(verity_image.fd, image_.data(),
image_.size()));
TemporaryFile ecc_image;
BuildAndAppendsEccImage(verity_image.path, ecc_image.path);
std::string ecc_content;
ASSERT_TRUE(android::base::ReadFileToString(ecc_image.path, &ecc_content));
ASSERT_TRUE(android::base::WriteStringToFd(ecc_content, verity_image.fd));
struct fec_handle *handle = nullptr;
ASSERT_EQ(0, fec_open(&handle, verity_image.path, O_RDONLY, FEC_FS_EXT4, 2));
std::unique_ptr<fec_handle> guard(handle);
ASSERT_EQ(1024 * 1024, handle->data_size); // filesystem size
ASSERT_EQ(1024 * 1024 + hashtree_content_.size(),
handle->verity.metadata_start);
fec_verity_metadata verity_metadata{};
ASSERT_EQ(0, fec_verity_get_metadata(handle, &verity_metadata));
ASSERT_FALSE(verity_metadata.disabled);
ASSERT_EQ(1024 * 1024, verity_metadata.data_size);
ASSERT_EQ(verity_table_, verity_metadata.table);
fec_ecc_metadata ecc_metadata{};
ASSERT_EQ(0, fec_ecc_get_metadata(handle, &ecc_metadata));
ASSERT_TRUE(ecc_metadata.valid);
ASSERT_EQ(handle->verity.metadata_start + 8 * 4096, ecc_metadata.start);
ASSERT_EQ(2, ecc_metadata.roots);
// 256 (data) + 3 (hashtree) + 8 (verity meta)
ASSERT_EQ(267, ecc_metadata.blocks);
}
TEST_F(FecUnitTest, VerityImage_FecRead) {
TemporaryFile verity_image;
BuildAndAppendsVerityMetadata();
ASSERT_TRUE(android::base::WriteFully(verity_image.fd, image_.data(),
image_.size()));
TemporaryFile ecc_image;
BuildAndAppendsEccImage(verity_image.path, ecc_image.path);
std::string ecc_content;
ASSERT_TRUE(android::base::ReadFileToString(ecc_image.path, &ecc_content));
ASSERT_TRUE(android::base::WriteStringToFd(ecc_content, verity_image.fd));
// Corrupt the last block
uint64_t corrupt_offset = 4096 * 255;
ASSERT_EQ(corrupt_offset, lseek64(verity_image.fd, corrupt_offset, 0));
std::vector<uint8_t> corruption(100, 10);
ASSERT_TRUE(android::base::WriteFully(verity_image.fd, corruption.data(),
corruption.size()));
std::vector<uint8_t> read_data(1024, 0);
struct fec_handle *handle = nullptr;
ASSERT_EQ(0,
fec_open(&handle, verity_image.path, O_RDONLY, FEC_FS_EXT4, 2));
std::unique_ptr<fec_handle> guard(handle);
ASSERT_EQ(1024, fec_pread(handle, read_data.data(), 1024, corrupt_offset));
ASSERT_EQ(std::vector<uint8_t>(1024, 255), read_data);
}
TEST_F(FecUnitTest, LoadAvbImage_HashtreeFooter) {
TemporaryFile avb_image;
ASSERT_TRUE(
android::base::WriteFully(avb_image.fd, image_.data(), image_.size()));
AddAvbHashtreeFooter(avb_image.path);
struct fec_handle *handle = nullptr;
ASSERT_EQ(0, fec_open(&handle, avb_image.path, O_RDWR, FEC_FS_EXT4, 2));
std::unique_ptr<fec_handle> guard(handle);
ASSERT_EQ(1024 * 1024, handle->data_size); // filesystem size
ASSERT_TRUE(handle->avb.valid);
// check the hashtree.
ASSERT_EQ(salt_, handle->hashtree().salt);
ASSERT_EQ(1024 * 1024, handle->hashtree().hash_start);
// the fec hashtree only stores the hash of the lowest level.
ASSERT_EQ(std::vector<uint8_t>(hashtree_content_.begin() + 4096,
hashtree_content_.end()),
handle->hashtree().hash_data);
uint64_t hash_size =
verity_get_size(handle->hashtree().data_blocks * FEC_BLOCKSIZE, nullptr,
nullptr, SHA256_DIGEST_LENGTH);
ASSERT_EQ(hashtree_content_.size(), hash_size);
fec_ecc_metadata ecc_metadata{};
ASSERT_EQ(0, fec_ecc_get_metadata(handle, &ecc_metadata));
ASSERT_TRUE(ecc_metadata.valid);
ASSERT_EQ(1024 * 1024 + hash_size, ecc_metadata.start);
ASSERT_EQ(259, ecc_metadata.blocks);
}
TEST_F(FecUnitTest, LoadAvbImage_CorrectHashtree) {
TemporaryFile avb_image;
ASSERT_TRUE(
android::base::WriteFully(avb_image.fd, image_.data(), image_.size()));
AddAvbHashtreeFooter(avb_image.path);
uint64_t corrupt_offset = 1024 * 1024 + 2 * 4096 + 50;
ASSERT_EQ(corrupt_offset, lseek64(avb_image.fd, corrupt_offset, 0));
std::vector<uint8_t> corruption(20, 5);
ASSERT_TRUE(android::base::WriteFully(avb_image.fd, corruption.data(),
corruption.size()));
struct fec_handle *handle = nullptr;
ASSERT_EQ(0, fec_open(&handle, avb_image.path, O_RDWR, FEC_FS_EXT4, 2));
std::unique_ptr<fec_handle> guard(handle);
ASSERT_EQ(1024 * 1024, handle->data_size); // filesystem size
fec_ecc_metadata ecc_metadata{};
ASSERT_EQ(0, fec_ecc_get_metadata(handle, &ecc_metadata));
ASSERT_TRUE(ecc_metadata.valid);
}
TEST_F(FecUnitTest, AvbImage_FecRead) {
TemporaryFile avb_image;
ASSERT_TRUE(
android::base::WriteFully(avb_image.fd, image_.data(), image_.size()));
AddAvbHashtreeFooter(avb_image.path, "sha1");
uint64_t corrupt_offset = 4096 * 10;
ASSERT_EQ(corrupt_offset, lseek64(avb_image.fd, corrupt_offset, 0));
std::vector<uint8_t> corruption(50, 99);
ASSERT_TRUE(android::base::WriteFully(avb_image.fd, corruption.data(),
corruption.size()));
std::vector<uint8_t> read_data(1024, 0);
struct fec_handle *handle = nullptr;
ASSERT_EQ(0, fec_open(&handle, avb_image.path, O_RDWR, FEC_FS_EXT4, 2));
std::unique_ptr<fec_handle> guard(handle);
// Verify the hashtree has the expected content.
ASSERT_EQ(std::vector<uint8_t>(hashtree_content_.begin() + 4096,
hashtree_content_.end()),
handle->hashtree().hash_data);
// Verify the corruption gets corrected.
ASSERT_EQ(1024, fec_pread(handle, read_data.data(), 1024, corrupt_offset));
ASSERT_EQ(std::vector<uint8_t>(1024, 10), read_data);
}