blob: d9650f33eaf03b658e96501aa5e0bd3731daf6c6 [file] [log] [blame]
/*
* 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 "avb_util.h"
#include <unistd.h>
#include <array>
#include <sstream>
#include <android-base/file.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include "util.h"
using android::base::Basename;
using android::base::ReadFileToString;
using android::base::StartsWith;
using android::base::unique_fd;
namespace android {
namespace fs_mgr {
std::string GetAvbPropertyDescriptor(const std::string& key,
const std::vector<VBMetaData>& vbmeta_images) {
size_t value_size;
for (const auto& vbmeta : vbmeta_images) {
const char* value = avb_property_lookup(vbmeta.data(), vbmeta.size(), key.data(),
key.size(), &value_size);
if (value != nullptr) {
return {value, value_size};
}
}
return "";
}
// Constructs dm-verity arguments for sending DM_TABLE_LOAD ioctl to kernel.
// See the following link for more details:
// https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
bool ConstructVerityTable(const FsAvbHashtreeDescriptor& hashtree_desc,
const std::string& blk_device, android::dm::DmTable* table) {
// Loads androidboot.veritymode from kernel cmdline.
std::string verity_mode;
if (!fs_mgr_get_boot_config("veritymode", &verity_mode)) {
verity_mode = "enforcing"; // Defaults to enforcing when it's absent.
}
// Converts veritymode to the format used in kernel.
std::string dm_verity_mode;
if (verity_mode == "enforcing") {
dm_verity_mode = "restart_on_corruption";
} else if (verity_mode == "logging") {
dm_verity_mode = "ignore_corruption";
} else if (verity_mode != "eio") { // Default dm_verity_mode is eio.
LERROR << "Unknown androidboot.veritymode: " << verity_mode;
return false;
}
std::ostringstream hash_algorithm;
hash_algorithm << hashtree_desc.hash_algorithm;
android::dm::DmTargetVerity target(
0, hashtree_desc.image_size / 512, hashtree_desc.dm_verity_version, blk_device,
blk_device, hashtree_desc.data_block_size, hashtree_desc.hash_block_size,
hashtree_desc.image_size / hashtree_desc.data_block_size,
hashtree_desc.tree_offset / hashtree_desc.hash_block_size, hash_algorithm.str(),
hashtree_desc.root_digest, hashtree_desc.salt);
if (hashtree_desc.fec_size > 0) {
target.UseFec(blk_device, hashtree_desc.fec_num_roots,
hashtree_desc.fec_offset / hashtree_desc.data_block_size,
hashtree_desc.fec_offset / hashtree_desc.data_block_size);
}
if (!dm_verity_mode.empty()) {
target.SetVerityMode(dm_verity_mode);
}
// Always use ignore_zero_blocks.
target.IgnoreZeroBlocks();
LINFO << "Built verity table: '" << target.GetParameterString() << "'";
return table->AddTarget(std::make_unique<android::dm::DmTargetVerity>(target));
}
bool HashtreeDmVeritySetup(FstabEntry* fstab_entry, const FsAvbHashtreeDescriptor& hashtree_desc,
bool wait_for_verity_dev) {
android::dm::DmTable table;
if (!ConstructVerityTable(hashtree_desc, fstab_entry->blk_device, &table) || !table.valid()) {
LERROR << "Failed to construct verity table.";
return false;
}
table.set_readonly(true);
const std::string mount_point(Basename(fstab_entry->mount_point));
const std::string device_name(GetVerityDeviceName(*fstab_entry));
android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
if (!dm.CreateDevice(device_name, table)) {
LERROR << "Couldn't create verity device!";
return false;
}
std::string dev_path;
if (!dm.GetDmDevicePathByName(device_name, &dev_path)) {
LERROR << "Couldn't get verity device path!";
return false;
}
// Marks the underlying block device as read-only.
SetBlockDeviceReadOnly(fstab_entry->blk_device);
// Updates fstab_rec->blk_device to verity device name.
fstab_entry->blk_device = dev_path;
// Makes sure we've set everything up properly.
if (wait_for_verity_dev && !WaitForFile(dev_path, 1s)) {
return false;
}
return true;
}
std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
bool found = false;
const uint8_t* desc_partition_name;
auto hashtree_desc = std::make_unique<FsAvbHashtreeDescriptor>();
for (const auto& vbmeta : vbmeta_images) {
size_t num_descriptors;
std::unique_ptr<const AvbDescriptor* [], decltype(&avb_free)> descriptors(
avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
if (!descriptors || num_descriptors < 1) {
continue;
}
for (size_t n = 0; n < num_descriptors && !found; n++) {
AvbDescriptor desc;
if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
LWARNING << "Descriptor[" << n << "] is invalid";
continue;
}
if (desc.tag == AVB_DESCRIPTOR_TAG_HASHTREE) {
desc_partition_name =
(const uint8_t*)descriptors[n] + sizeof(AvbHashtreeDescriptor);
if (!avb_hashtree_descriptor_validate_and_byteswap(
(AvbHashtreeDescriptor*)descriptors[n], hashtree_desc.get())) {
continue;
}
if (hashtree_desc->partition_name_len != partition_name.length()) {
continue;
}
// Notes that desc_partition_name is not NUL-terminated.
std::string hashtree_partition_name((const char*)desc_partition_name,
hashtree_desc->partition_name_len);
if (hashtree_partition_name == partition_name) {
found = true;
}
}
}
if (found) break;
}
if (!found) {
LERROR << "Hashtree descriptor not found: " << partition_name;
return nullptr;
}
hashtree_desc->partition_name = partition_name;
const uint8_t* desc_salt = desc_partition_name + hashtree_desc->partition_name_len;
hashtree_desc->salt = BytesToHex(desc_salt, hashtree_desc->salt_len);
const uint8_t* desc_digest = desc_salt + hashtree_desc->salt_len;
hashtree_desc->root_digest = BytesToHex(desc_digest, hashtree_desc->root_digest_len);
return hashtree_desc;
}
bool LoadAvbHashtreeToEnableVerity(FstabEntry* fstab_entry, bool wait_for_verity_dev,
const std::vector<VBMetaData>& vbmeta_images,
const std::string& ab_suffix,
const std::string& ab_other_suffix) {
// Derives partition_name from blk_device to query the corresponding AVB HASHTREE descriptor
// to setup dm-verity. The partition_names in AVB descriptors are without A/B suffix.
std::string partition_name = DeriveAvbPartitionName(*fstab_entry, ab_suffix, ab_other_suffix);
if (partition_name.empty()) {
LERROR << "partition name is empty, cannot lookup AVB descriptors";
return false;
}
std::unique_ptr<FsAvbHashtreeDescriptor> hashtree_descriptor =
GetHashtreeDescriptor(partition_name, vbmeta_images);
if (!hashtree_descriptor) {
return false;
}
// Converts HASHTREE descriptor to verity table to load into kernel.
// When success, the new device path will be returned, e.g., /dev/block/dm-2.
return HashtreeDmVeritySetup(fstab_entry, *hashtree_descriptor, wait_for_verity_dev);
}
// Converts a AVB partition_name (without A/B suffix) to a device partition name.
// e.g., "system" => "system_a",
// "system_other" => "system_b".
//
// If the device is non-A/B, converts it to a partition name without suffix.
// e.g., "system" => "system",
// "system_other" => "system".
std::string AvbPartitionToDevicePatition(const std::string& avb_partition_name,
const std::string& ab_suffix,
const std::string& ab_other_suffix) {
bool is_other_slot = false;
std::string sanitized_partition_name(avb_partition_name);
auto other_suffix = sanitized_partition_name.rfind("_other");
if (other_suffix != std::string::npos) {
sanitized_partition_name.erase(other_suffix); // converts system_other => system
is_other_slot = true;
}
auto append_suffix = is_other_slot ? ab_other_suffix : ab_suffix;
return sanitized_partition_name + append_suffix;
}
// Converts fstab_entry.blk_device (with ab_suffix) to a AVB partition name.
// e.g., "/dev/block/by-name/system_a", slot_select => "system",
// "/dev/block/by-name/system_b", slot_select_other => "system_other".
//
// Or for a logical partition (with ab_suffix):
// e.g., "system_a", slot_select => "system",
// "system_b", slot_select_other => "system_other".
std::string DeriveAvbPartitionName(const FstabEntry& fstab_entry, const std::string& ab_suffix,
const std::string& ab_other_suffix) {
std::string partition_name;
if (fstab_entry.fs_mgr_flags.logical) {
partition_name = fstab_entry.logical_partition_name;
} else {
partition_name = Basename(fstab_entry.blk_device);
}
if (fstab_entry.fs_mgr_flags.slot_select) {
auto found = partition_name.rfind(ab_suffix);
if (found != std::string::npos) {
partition_name.erase(found); // converts system_a => system
}
} else if (fstab_entry.fs_mgr_flags.slot_select_other) {
auto found = partition_name.rfind(ab_other_suffix);
if (found != std::string::npos) {
partition_name.erase(found); // converts system_b => system
}
partition_name += "_other"; // converts system => system_other
}
return partition_name;
}
off64_t GetTotalSize(int fd) {
off64_t saved_current = lseek64(fd, 0, SEEK_CUR);
if (saved_current == -1) {
PERROR << "Failed to get current position";
return -1;
}
// lseek64() returns the resulting offset location from the beginning of the file.
off64_t total_size = lseek64(fd, 0, SEEK_END);
if (total_size == -1) {
PERROR << "Failed to lseek64 to end of the partition";
return -1;
}
// Restores the original offset.
if (lseek64(fd, saved_current, SEEK_SET) == -1) {
PERROR << "Failed to lseek64 to the original offset: " << saved_current;
}
return total_size;
}
std::unique_ptr<AvbFooter> GetAvbFooter(int fd) {
std::array<uint8_t, AVB_FOOTER_SIZE> footer_buf;
auto footer = std::make_unique<AvbFooter>();
off64_t footer_offset = GetTotalSize(fd) - AVB_FOOTER_SIZE;
ssize_t num_read =
TEMP_FAILURE_RETRY(pread64(fd, footer_buf.data(), AVB_FOOTER_SIZE, footer_offset));
if (num_read < 0 || num_read != AVB_FOOTER_SIZE) {
PERROR << "Failed to read AVB footer at offset: " << footer_offset;
return nullptr;
}
if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf.data(), footer.get())) {
PERROR << "AVB footer verification failed at offset " << footer_offset;
return nullptr;
}
return footer;
}
bool ValidatePublicKeyBlob(const uint8_t* key, size_t length,
const std::string& expected_key_blob) {
if (expected_key_blob.empty()) { // no expectation of the key, return true.
return true;
}
if (expected_key_blob.size() != length) {
return false;
}
if (0 == memcmp(key, expected_key_blob.data(), length)) {
return true;
}
return false;
}
bool ValidatePublicKeyBlob(const std::string& key_blob_to_validate,
const std::vector<std::string>& allowed_key_paths) {
std::string allowed_key_blob;
if (key_blob_to_validate.empty()) {
LWARNING << "Failed to validate an empty key";
return false;
}
for (const auto& path : allowed_key_paths) {
if (ReadFileToString(path, &allowed_key_blob)) {
if (key_blob_to_validate == allowed_key_blob) return true;
}
}
return false;
}
VBMetaVerifyResult VerifyVBMetaSignature(const VBMetaData& vbmeta,
const std::string& expected_public_key_blob,
std::string* out_public_key_data) {
const uint8_t* pk_data;
size_t pk_len;
::AvbVBMetaVerifyResult vbmeta_ret;
vbmeta_ret = avb_vbmeta_image_verify(vbmeta.data(), vbmeta.size(), &pk_data, &pk_len);
if (out_public_key_data != nullptr) {
out_public_key_data->clear();
if (pk_len > 0) {
out_public_key_data->append(reinterpret_cast<const char*>(pk_data), pk_len);
}
}
switch (vbmeta_ret) {
case AVB_VBMETA_VERIFY_RESULT_OK:
if (pk_data == nullptr || pk_len <= 0) {
LERROR << vbmeta.partition()
<< ": Error verifying vbmeta image: failed to get public key";
return VBMetaVerifyResult::kError;
}
if (!ValidatePublicKeyBlob(pk_data, pk_len, expected_public_key_blob)) {
LERROR << vbmeta.partition() << ": Error verifying vbmeta image: public key used to"
<< " sign data does not match key in chain descriptor";
return VBMetaVerifyResult::kErrorVerification;
}
return VBMetaVerifyResult::kSuccess;
case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
LERROR << vbmeta.partition() << ": Error verifying vbmeta image: "
<< avb_vbmeta_verify_result_to_string(vbmeta_ret);
return VBMetaVerifyResult::kErrorVerification;
case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
// No way to continue this case.
LERROR << vbmeta.partition() << ": Error verifying vbmeta image: invalid vbmeta header";
break;
case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION:
// No way to continue this case.
LERROR << vbmeta.partition()
<< ": Error verifying vbmeta image: unsupported AVB version";
break;
default:
LERROR << "Unknown vbmeta image verify return value: " << vbmeta_ret;
break;
}
return VBMetaVerifyResult::kError;
}
std::unique_ptr<VBMetaData> VerifyVBMetaData(int fd, const std::string& partition_name,
const std::string& expected_public_key_blob,
std::string* out_public_key_data,
VBMetaVerifyResult* out_verify_result) {
uint64_t vbmeta_offset = 0;
uint64_t vbmeta_size = VBMetaData::kMaxVBMetaSize;
bool is_vbmeta_partition = StartsWith(partition_name, "vbmeta");
if (out_verify_result) {
*out_verify_result = VBMetaVerifyResult::kError;
}
if (!is_vbmeta_partition) {
std::unique_ptr<AvbFooter> footer = GetAvbFooter(fd);
if (!footer) {
return nullptr;
}
vbmeta_offset = footer->vbmeta_offset;
vbmeta_size = footer->vbmeta_size;
}
if (vbmeta_size > VBMetaData::kMaxVBMetaSize) {
LERROR << "VbMeta size in footer exceeds kMaxVBMetaSize";
return nullptr;
}
auto vbmeta = std::make_unique<VBMetaData>(vbmeta_size, partition_name);
ssize_t num_read = TEMP_FAILURE_RETRY(pread64(fd, vbmeta->data(), vbmeta_size, vbmeta_offset));
// Allows partial read for vbmeta partition, because its vbmeta_size is kMaxVBMetaSize.
if (num_read < 0 || (!is_vbmeta_partition && static_cast<uint64_t>(num_read) != vbmeta_size)) {
PERROR << partition_name << ": Failed to read vbmeta at offset " << vbmeta_offset
<< " with size " << vbmeta_size;
return nullptr;
}
auto verify_result =
VerifyVBMetaSignature(*vbmeta, expected_public_key_blob, out_public_key_data);
if (out_verify_result != nullptr) {
*out_verify_result = verify_result;
}
if (verify_result == VBMetaVerifyResult::kSuccess ||
verify_result == VBMetaVerifyResult::kErrorVerification) {
return vbmeta;
}
return nullptr;
}
bool RollbackDetected(const std::string& partition_name ATTRIBUTE_UNUSED,
uint64_t rollback_index ATTRIBUTE_UNUSED) {
// TODO(bowgotsai): Support rollback protection.
return false;
}
std::vector<ChainInfo> GetChainPartitionInfo(const VBMetaData& vbmeta, bool* fatal_error) {
CHECK(fatal_error != nullptr);
std::vector<ChainInfo> chain_partitions;
size_t num_descriptors;
std::unique_ptr<const AvbDescriptor* [], decltype(&avb_free)> descriptors(
avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
if (!descriptors || num_descriptors < 1) {
return {};
}
for (size_t i = 0; i < num_descriptors; i++) {
AvbDescriptor desc;
if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) {
LERROR << "Descriptor[" << i << "] is invalid in vbmeta: " << vbmeta.partition();
*fatal_error = true;
return {};
}
if (desc.tag == AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) {
AvbChainPartitionDescriptor chain_desc;
if (!avb_chain_partition_descriptor_validate_and_byteswap(
(AvbChainPartitionDescriptor*)descriptors[i], &chain_desc)) {
LERROR << "Chain descriptor[" << i
<< "] is invalid in vbmeta: " << vbmeta.partition();
*fatal_error = true;
return {};
}
const char* chain_partition_name =
((const char*)descriptors[i]) + sizeof(AvbChainPartitionDescriptor);
const char* chain_public_key_blob =
chain_partition_name + chain_desc.partition_name_len;
chain_partitions.emplace_back(
std::string(chain_partition_name, chain_desc.partition_name_len),
std::string(chain_public_key_blob, chain_desc.public_key_len));
}
}
return chain_partitions;
}
// Loads the vbmeta from a given path.
std::unique_ptr<VBMetaData> LoadAndVerifyVbmetaByPath(
const std::string& image_path, const std::string& partition_name,
const std::string& expected_public_key_blob, bool allow_verification_error,
bool rollback_protection, bool is_chained_vbmeta, std::string* out_public_key_data,
bool* out_verification_disabled, VBMetaVerifyResult* out_verify_result) {
if (out_verify_result) {
*out_verify_result = VBMetaVerifyResult::kError;
}
// Ensures the device path (might be a symlink created by init) is ready to access.
if (!WaitForFile(image_path, 1s)) {
PERROR << "No such path: " << image_path;
return nullptr;
}
unique_fd fd(TEMP_FAILURE_RETRY(open(image_path.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd < 0) {
PERROR << "Failed to open: " << image_path;
return nullptr;
}
VBMetaVerifyResult verify_result;
std::unique_ptr<VBMetaData> vbmeta = VerifyVBMetaData(
fd, partition_name, expected_public_key_blob, out_public_key_data, &verify_result);
if (!vbmeta) {
LERROR << partition_name << ": Failed to load vbmeta, result: " << verify_result;
return nullptr;
}
vbmeta->set_vbmeta_path(image_path);
if (!allow_verification_error && verify_result == VBMetaVerifyResult::kErrorVerification) {
LERROR << partition_name << ": allow verification error is not allowed";
return nullptr;
}
std::unique_ptr<AvbVBMetaImageHeader> vbmeta_header =
vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */);
if (!vbmeta_header) {
LERROR << partition_name << ": Failed to get vbmeta header";
return nullptr;
}
if (rollback_protection && RollbackDetected(partition_name, vbmeta_header->rollback_index)) {
return nullptr;
}
// vbmeta flags can only be set by the top-level vbmeta image.
if (is_chained_vbmeta && vbmeta_header->flags != 0) {
LERROR << partition_name << ": chained vbmeta image has non-zero flags";
return nullptr;
}
// Checks if verification has been disabled by setting a bit in the image.
if (out_verification_disabled) {
if (vbmeta_header->flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED) {
LWARNING << "VERIFICATION_DISABLED bit is set for partition: " << partition_name;
*out_verification_disabled = true;
} else {
*out_verification_disabled = false;
}
}
if (out_verify_result) {
*out_verify_result = verify_result;
}
return vbmeta;
}
VBMetaVerifyResult LoadAndVerifyVbmetaByPartition(
const std::string& partition_name, const std::string& ab_suffix,
const std::string& ab_other_suffix, const std::string& expected_public_key_blob,
bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection,
std::function<std::string(const std::string&)> device_path_constructor, bool is_chained_vbmeta,
std::vector<VBMetaData>* out_vbmeta_images) {
auto image_path = device_path_constructor(
AvbPartitionToDevicePatition(partition_name, ab_suffix, ab_other_suffix));
bool verification_disabled = false;
VBMetaVerifyResult verify_result;
auto vbmeta = LoadAndVerifyVbmetaByPath(image_path, partition_name, expected_public_key_blob,
allow_verification_error, rollback_protection,
is_chained_vbmeta, nullptr /* out_public_key_data */,
&verification_disabled, &verify_result);
if (!vbmeta) {
return VBMetaVerifyResult::kError;
}
if (out_vbmeta_images) {
out_vbmeta_images->emplace_back(std::move(*vbmeta));
}
// Only loads chained vbmeta if AVB verification is NOT disabled.
if (!verification_disabled && load_chained_vbmeta) {
bool fatal_error = false;
auto chain_partitions = GetChainPartitionInfo(*out_vbmeta_images->rbegin(), &fatal_error);
if (fatal_error) {
return VBMetaVerifyResult::kError;
}
for (auto& chain : chain_partitions) {
auto sub_ret = LoadAndVerifyVbmetaByPartition(
chain.partition_name, ab_suffix, ab_other_suffix, chain.public_key_blob,
allow_verification_error, load_chained_vbmeta, rollback_protection,
device_path_constructor, true, /* is_chained_vbmeta */
out_vbmeta_images);
if (sub_ret != VBMetaVerifyResult::kSuccess) {
verify_result = sub_ret; // might be 'ERROR' or 'ERROR VERIFICATION'.
if (verify_result == VBMetaVerifyResult::kError) {
return verify_result; // stop here if we got an 'ERROR'.
}
}
}
}
return verify_result;
}
} // namespace fs_mgr
} // namespace android