diff options
Diffstat (limited to 'libfec/fec_open.cpp')
-rw-r--r-- | libfec/fec_open.cpp | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp index 18731f21..8f3fa22d 100644 --- a/libfec/fec_open.cpp +++ b/libfec/fec_open.cpp @@ -18,7 +18,9 @@ #include <sys/ioctl.h> #include <sys/stat.h> +#include <android-base/strings.h> #include <ext4_utils/ext4_sb.h> +#include <libavb/libavb.h> #include <squashfs_utils.h> #if defined(__linux__) @@ -493,6 +495,145 @@ int fec_get_status(struct fec_handle *f, struct fec_status *s) return 0; } +static int parse_vbmeta_from_footer(fec_handle *f, + std::vector<uint8_t> *vbmeta) { + if (f->size <= AVB_FOOTER_SIZE) { + debug("file size not large enough to be avb images:" PRIu64, f->size); + return -1; + } + + AvbFooter footer_read; + if (!raw_pread(f->fd, &footer_read, AVB_FOOTER_SIZE, + f->size - AVB_FOOTER_SIZE)) { + error("failed to read footer: %s", strerror(errno)); + return -1; + } + + AvbFooter footer; + if (!avb_footer_validate_and_byteswap(&footer_read, &footer)) { + debug("invalid avb footer"); + return -1; + } + uint64_t vbmeta_offset = footer.vbmeta_offset; + uint64_t vbmeta_size = footer.vbmeta_size; + check(vbmeta_offset <= f->size - sizeof(footer) - vbmeta_size); + + std::vector<uint8_t> vbmeta_data(vbmeta_size, 0); + // TODO(xunchang) handle the sparse image with libsparse. + if (!raw_pread(f->fd, vbmeta_data.data(), vbmeta_data.size(), + vbmeta_offset)) { + error("failed to read avb vbmeta: %s", strerror(errno)); + return -1; + } + + if (auto status = avb_vbmeta_image_verify( + vbmeta_data.data(), vbmeta_data.size(), nullptr, nullptr); + status != AVB_VBMETA_VERIFY_RESULT_OK && + status != AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED) { + error("failed to verify avb vbmeta, status: %d", status); + return -1; + } + *vbmeta = std::move(vbmeta_data); + return 0; +} + +static int parse_avb_image(fec_handle *f, const std::vector<uint8_t> &vbmeta) { + // TODO(xunchang) check if avb verification or hashtree is disabled. + + // Look for the hashtree descriptor, we expect exactly one descriptor in + // vbmeta. + // TODO(xunchang) handle the image with AvbHashDescriptor. + auto parse_descriptor = [](const AvbDescriptor *descriptor, + void *user_data) { + if (descriptor && + avb_be64toh(descriptor->tag) == AVB_DESCRIPTOR_TAG_HASHTREE) { + auto desp = static_cast<const AvbDescriptor **>(user_data); + *desp = descriptor; + return false; + } + return true; + }; + + const AvbHashtreeDescriptor *hashtree_descriptor_ptr = nullptr; + avb_descriptor_foreach(vbmeta.data(), vbmeta.size(), parse_descriptor, + &hashtree_descriptor_ptr); + + AvbHashtreeDescriptor hashtree_descriptor; + if (!avb_hashtree_descriptor_validate_and_byteswap(hashtree_descriptor_ptr, + &hashtree_descriptor)) { + error("failed to verify avb hashtree descriptor"); + return -1; + } + + // The partition name, salt, root append right after the hashtree + // descriptor. + auto read_ptr = reinterpret_cast<const uint8_t *>(hashtree_descriptor_ptr); + // Calculate the offset with respect to the vbmeta; and check both the + // salt & root are within the range. + uint32_t salt_offset = + sizeof(AvbHashtreeDescriptor) + hashtree_descriptor.partition_name_len; + uint32_t root_offset = salt_offset + hashtree_descriptor.salt_len; + check(hashtree_descriptor.salt_len < vbmeta.size()); + check(salt_offset < vbmeta.size() - hashtree_descriptor.salt_len); + check(hashtree_descriptor.root_digest_len < vbmeta.size()); + check(root_offset < vbmeta.size() - hashtree_descriptor.root_digest_len); + std::vector<uint8_t> salt( + read_ptr + salt_offset, + read_ptr + salt_offset + hashtree_descriptor.salt_len); + std::vector<uint8_t> root_hash( + read_ptr + root_offset, + read_ptr + root_offset + hashtree_descriptor.root_digest_len); + + // Expect the AVB image has the format: + // 1. hashtree + // 2. ecc data + // 3. vbmeta + // 4. avb footer + check(hashtree_descriptor.fec_offset == + hashtree_descriptor.tree_offset + hashtree_descriptor.tree_size); + check(hashtree_descriptor.fec_offset <= + f->size - hashtree_descriptor.fec_size); + + f->data_size = hashtree_descriptor.fec_offset; + + f->ecc.blocks = fec_div_round_up(f->data_size, FEC_BLOCKSIZE); + f->ecc.rounds = fec_div_round_up(f->ecc.blocks, f->ecc.rsn); + f->ecc.size = hashtree_descriptor.fec_size; + f->ecc.start = hashtree_descriptor.fec_offset; + // TODO(xunchang) verify the integrity of the ecc data. + f->ecc.valid = true; + + std::string hash_algorithm = + reinterpret_cast<char *>(hashtree_descriptor.hash_algorithm); + int nid = -1; + if (android::base::EqualsIgnoreCase(hash_algorithm, "sha1")) { + nid = NID_sha1; + } else if (android::base::EqualsIgnoreCase(hash_algorithm, "sha256")) { + nid = NID_sha256; + } else { + error("unsupported hash algorithm %s", hash_algorithm.c_str()); + } + + hashtree_info hashtree; + hashtree.initialize(hashtree_descriptor.tree_offset, + hashtree_descriptor.tree_offset / FEC_BLOCKSIZE, salt, + nid); + if (hashtree.verify_tree(f, root_hash.data()) != 0) { + error("failed to verify hashtree"); + return -1; + } + + // We have validate the hashtree, + f->data_size = hashtree.hash_start; + f->avb = { + .valid = true, + .vbmeta = vbmeta, + .hashtree = std::move(hashtree), + }; + + return 0; +} + /* opens `path' using given options and returns a fec_handle in `handle' if successful */ int fec_open(struct fec_handle **handle, const char *path, int mode, int flags, @@ -546,6 +687,21 @@ int fec_open(struct fec_handle **handle, const char *path, int mode, int flags, f->data_size = f->size; /* until ecc and/or verity are loaded */ + std::vector<uint8_t> vbmeta; + if (parse_vbmeta_from_footer(f.get(), &vbmeta) == 0) { + if (parse_avb_image(f.get(), vbmeta) != 0) { + error("failed to parse avb image."); + return -1; + } + + *handle = f.release(); + return 0; + } + // TODO(xunchang) For android, handle the case when vbmeta is in a separate + // image. We could use avb_slot_verify() && AvbOps from libavb_user. + + // Fall back to use verity format. + if (load_ecc(f.get()) == -1) { debug("error-correcting codes not found from '%s'", path); } |