| /* |
| * 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 { |
| |
| // 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 == "panicking") { |
| dm_verity_mode = "panic_on_corruption"; |
| } else 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(); |
| |
| if (hashtree_desc.flags & AVB_HASHTREE_DESCRIPTOR_FLAGS_CHECK_AT_MOST_ONCE) { |
| target.CheckAtMostOnce(); |
| } |
| |
| 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); |
| |
| std::chrono::milliseconds timeout = {}; |
| if (wait_for_verity_dev) timeout = 1s; |
| |
| std::string dev_path; |
| const std::string device_name(GetVerityDeviceName(*fstab_entry)); |
| android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance(); |
| if (!dm.CreateDevice(device_name, table, &dev_path, timeout)) { |
| LERROR << "Couldn't create verity device!"; |
| 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; |
| 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 |