229 lines
9.7 KiB
C++
229 lines
9.7 KiB
C++
|
//
|
||
|
// Copyright (C) 2018 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 "update_engine/payload_generator/payload_generation_config.h"
|
||
|
|
||
|
#include <base/logging.h>
|
||
|
#include <base/strings/string_number_conversions.h>
|
||
|
#include <base/strings/string_split.h>
|
||
|
#include <brillo/secure_blob.h>
|
||
|
#include <fec/io.h>
|
||
|
#include <libavb/libavb.h>
|
||
|
#include <verity/hash_tree_builder.h>
|
||
|
|
||
|
#include "update_engine/common/utils.h"
|
||
|
#include "update_engine/payload_consumer/verity_writer_android.h"
|
||
|
#include "update_engine/payload_generator/extent_ranges.h"
|
||
|
|
||
|
namespace chromeos_update_engine {
|
||
|
|
||
|
namespace {
|
||
|
bool AvbDescriptorCallback(const AvbDescriptor* descriptor, void* user_data) {
|
||
|
PartitionConfig* part = static_cast<PartitionConfig*>(user_data);
|
||
|
AvbDescriptor desc;
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
avb_descriptor_validate_and_byteswap(descriptor, &desc));
|
||
|
if (desc.tag != AVB_DESCRIPTOR_TAG_HASHTREE)
|
||
|
return true;
|
||
|
|
||
|
AvbHashtreeDescriptor hashtree;
|
||
|
TEST_AND_RETURN_FALSE(avb_hashtree_descriptor_validate_and_byteswap(
|
||
|
reinterpret_cast<const AvbHashtreeDescriptor*>(descriptor), &hashtree));
|
||
|
// We only support version 1 right now, will need to introduce a new
|
||
|
// payload minor version to support new dm verity version.
|
||
|
TEST_AND_RETURN_FALSE(hashtree.dm_verity_version == 1);
|
||
|
part->verity.hash_tree_algorithm =
|
||
|
reinterpret_cast<const char*>(hashtree.hash_algorithm);
|
||
|
|
||
|
const uint8_t* salt = reinterpret_cast<const uint8_t*>(descriptor) +
|
||
|
sizeof(AvbHashtreeDescriptor) +
|
||
|
hashtree.partition_name_len;
|
||
|
part->verity.hash_tree_salt.assign(salt, salt + hashtree.salt_len);
|
||
|
|
||
|
TEST_AND_RETURN_FALSE(hashtree.data_block_size ==
|
||
|
part->fs_interface->GetBlockSize());
|
||
|
part->verity.hash_tree_data_extent =
|
||
|
ExtentForBytes(hashtree.data_block_size, 0, hashtree.image_size);
|
||
|
|
||
|
TEST_AND_RETURN_FALSE(hashtree.hash_block_size ==
|
||
|
part->fs_interface->GetBlockSize());
|
||
|
part->verity.hash_tree_extent = ExtentForBytes(
|
||
|
hashtree.hash_block_size, hashtree.tree_offset, hashtree.tree_size);
|
||
|
|
||
|
if (!part->disable_fec_computation) {
|
||
|
part->verity.fec_data_extent =
|
||
|
ExtentForBytes(hashtree.data_block_size, 0, hashtree.fec_offset);
|
||
|
part->verity.fec_extent = ExtentForBytes(
|
||
|
hashtree.data_block_size, hashtree.fec_offset, hashtree.fec_size);
|
||
|
part->verity.fec_roots = hashtree.fec_num_roots;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Generate hash tree and FEC based on the verity config and verify that it
|
||
|
// matches the hash tree and FEC stored in the image.
|
||
|
bool VerifyVerityConfig(const PartitionConfig& part) {
|
||
|
const size_t block_size = part.fs_interface->GetBlockSize();
|
||
|
if (part.verity.hash_tree_extent.num_blocks() != 0) {
|
||
|
auto hash_function =
|
||
|
HashTreeBuilder::HashFunction(part.verity.hash_tree_algorithm);
|
||
|
TEST_AND_RETURN_FALSE(hash_function != nullptr);
|
||
|
HashTreeBuilder hash_tree_builder(block_size, hash_function);
|
||
|
uint64_t data_size =
|
||
|
part.verity.hash_tree_data_extent.num_blocks() * block_size;
|
||
|
uint64_t tree_size = hash_tree_builder.CalculateSize(data_size);
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
tree_size == part.verity.hash_tree_extent.num_blocks() * block_size);
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
hash_tree_builder.Initialize(data_size, part.verity.hash_tree_salt));
|
||
|
|
||
|
brillo::Blob buffer;
|
||
|
for (uint64_t offset = part.verity.hash_tree_data_extent.start_block() *
|
||
|
block_size,
|
||
|
data_end = offset + data_size;
|
||
|
offset < data_end;) {
|
||
|
constexpr uint64_t kBufferSize = 1024 * 1024;
|
||
|
size_t bytes_to_read = std::min(kBufferSize, data_end - offset);
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
utils::ReadFileChunk(part.path, offset, bytes_to_read, &buffer));
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
hash_tree_builder.Update(buffer.data(), buffer.size()));
|
||
|
offset += buffer.size();
|
||
|
buffer.clear();
|
||
|
}
|
||
|
TEST_AND_RETURN_FALSE(hash_tree_builder.BuildHashTree());
|
||
|
TEST_AND_RETURN_FALSE(utils::ReadFileChunk(
|
||
|
part.path,
|
||
|
part.verity.hash_tree_extent.start_block() * block_size,
|
||
|
tree_size,
|
||
|
&buffer));
|
||
|
TEST_AND_RETURN_FALSE(hash_tree_builder.CheckHashTree(buffer));
|
||
|
}
|
||
|
|
||
|
if (part.verity.fec_extent.num_blocks() != 0) {
|
||
|
TEST_AND_RETURN_FALSE(VerityWriterAndroid::EncodeFEC(
|
||
|
part.path,
|
||
|
part.verity.fec_data_extent.start_block() * block_size,
|
||
|
part.verity.fec_data_extent.num_blocks() * block_size,
|
||
|
part.verity.fec_extent.start_block() * block_size,
|
||
|
part.verity.fec_extent.num_blocks() * block_size,
|
||
|
part.verity.fec_roots,
|
||
|
block_size,
|
||
|
true /* verify_mode */));
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
} // namespace
|
||
|
|
||
|
bool ImageConfig::LoadVerityConfig() {
|
||
|
for (PartitionConfig& part : partitions) {
|
||
|
// Parse AVB devices.
|
||
|
if (part.size > sizeof(AvbFooter)) {
|
||
|
uint64_t footer_offset = part.size - sizeof(AvbFooter);
|
||
|
brillo::Blob buffer;
|
||
|
TEST_AND_RETURN_FALSE(utils::ReadFileChunk(
|
||
|
part.path, footer_offset, sizeof(AvbFooter), &buffer));
|
||
|
if (memcmp(buffer.data(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) == 0) {
|
||
|
LOG(INFO) << "Parsing verity config from AVB footer for " << part.name;
|
||
|
AvbFooter footer;
|
||
|
TEST_AND_RETURN_FALSE(avb_footer_validate_and_byteswap(
|
||
|
reinterpret_cast<const AvbFooter*>(buffer.data()), &footer));
|
||
|
buffer.clear();
|
||
|
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
footer.vbmeta_offset + sizeof(AvbVBMetaImageHeader) <= part.size);
|
||
|
TEST_AND_RETURN_FALSE(utils::ReadFileChunk(
|
||
|
part.path, footer.vbmeta_offset, footer.vbmeta_size, &buffer));
|
||
|
TEST_AND_RETURN_FALSE(avb_descriptor_foreach(
|
||
|
buffer.data(), buffer.size(), AvbDescriptorCallback, &part));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Parse VB1.0 devices with FEC metadata, devices with hash tree without
|
||
|
// FEC will be skipped for now.
|
||
|
if (part.verity.IsEmpty() && part.size > FEC_BLOCKSIZE) {
|
||
|
brillo::Blob fec_metadata;
|
||
|
TEST_AND_RETURN_FALSE(utils::ReadFileChunk(part.path,
|
||
|
part.size - FEC_BLOCKSIZE,
|
||
|
sizeof(fec_header),
|
||
|
&fec_metadata));
|
||
|
const fec_header* header =
|
||
|
reinterpret_cast<const fec_header*>(fec_metadata.data());
|
||
|
if (header->magic == FEC_MAGIC) {
|
||
|
LOG(INFO)
|
||
|
<< "Parsing verity config from Verified Boot 1.0 metadata for "
|
||
|
<< part.name;
|
||
|
const size_t block_size = part.fs_interface->GetBlockSize();
|
||
|
// FEC_VERITY_DISABLE skips verifying verity hash tree, because we will
|
||
|
// verify it ourselves later.
|
||
|
fec::io fh(part.path, O_RDONLY, FEC_VERITY_DISABLE);
|
||
|
TEST_AND_RETURN_FALSE(fh);
|
||
|
fec_verity_metadata verity_data;
|
||
|
if (fh.get_verity_metadata(verity_data)) {
|
||
|
auto verity_table = base::SplitString(verity_data.table,
|
||
|
" ",
|
||
|
base::KEEP_WHITESPACE,
|
||
|
base::SPLIT_WANT_ALL);
|
||
|
TEST_AND_RETURN_FALSE(verity_table.size() == 10);
|
||
|
size_t data_block_size = 0;
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
base::StringToSizeT(verity_table[3], &data_block_size));
|
||
|
TEST_AND_RETURN_FALSE(block_size == data_block_size);
|
||
|
size_t hash_block_size = 0;
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
base::StringToSizeT(verity_table[4], &hash_block_size));
|
||
|
TEST_AND_RETURN_FALSE(block_size == hash_block_size);
|
||
|
uint64_t num_data_blocks = 0;
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
base::StringToUint64(verity_table[5], &num_data_blocks));
|
||
|
part.verity.hash_tree_data_extent =
|
||
|
ExtentForRange(0, num_data_blocks);
|
||
|
uint64_t hash_start_block = 0;
|
||
|
TEST_AND_RETURN_FALSE(
|
||
|
base::StringToUint64(verity_table[6], &hash_start_block));
|
||
|
part.verity.hash_tree_algorithm = verity_table[7];
|
||
|
TEST_AND_RETURN_FALSE(base::HexStringToBytes(
|
||
|
verity_table[9], &part.verity.hash_tree_salt));
|
||
|
auto hash_function =
|
||
|
HashTreeBuilder::HashFunction(part.verity.hash_tree_algorithm);
|
||
|
TEST_AND_RETURN_FALSE(hash_function != nullptr);
|
||
|
HashTreeBuilder hash_tree_builder(block_size, hash_function);
|
||
|
uint64_t tree_size =
|
||
|
hash_tree_builder.CalculateSize(num_data_blocks * block_size);
|
||
|
part.verity.hash_tree_extent =
|
||
|
ExtentForRange(hash_start_block, tree_size / block_size);
|
||
|
}
|
||
|
fec_ecc_metadata ecc_data;
|
||
|
if (!part.disable_fec_computation && fh.get_ecc_metadata(ecc_data) &&
|
||
|
ecc_data.valid) {
|
||
|
TEST_AND_RETURN_FALSE(block_size == FEC_BLOCKSIZE);
|
||
|
part.verity.fec_data_extent = ExtentForRange(0, ecc_data.blocks);
|
||
|
part.verity.fec_extent =
|
||
|
ExtentForBytes(block_size, ecc_data.start, header->fec_size);
|
||
|
part.verity.fec_roots = ecc_data.roots;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!part.verity.IsEmpty()) {
|
||
|
TEST_AND_RETURN_FALSE(VerifyVerityConfig(part));
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
} // namespace chromeos_update_engine
|