From e71fe2441ad62793b15812a34f7b863dccefabdc Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 22 Feb 2021 15:00:15 -0800 Subject: [PATCH] fastboot driver: repack vendor boot ramdisk When a user issues `fastboot flash vendor_boot:foo ramdisk.img`, the fastboot driver fetches the vendor_boot image from the device, determines if `foo` is a valid vendor ramdisk fragment, repacks a new vendor boot image, then flash the vendor boot image back. This requires vendor boot header V4. As a convinent alias, `fastboot flash vendor_boot:default ramdisk.img` flashes the whole vendor ramdisk image. This works on vendor boot header V3 & 4. Fixes: 173654501 Test: pass Change-Id: I42b2483a736ea8aa9fd9372b960502a642934cdc --- fastboot/Android.bp | 34 ++ fastboot/fastboot.cpp | 38 ++- fastboot/testdata/Android.bp | 136 ++++++++ fastboot/testdata/fastboot_gen_rand.py | 32 ++ fastboot/vendor_boot_img_utils.cpp | 422 +++++++++++++++++++++++ fastboot/vendor_boot_img_utils.h | 34 ++ fastboot/vendor_boot_img_utils_test.cpp | 429 ++++++++++++++++++++++++ 7 files changed, 1124 insertions(+), 1 deletion(-) create mode 100644 fastboot/testdata/Android.bp create mode 100644 fastboot/testdata/fastboot_gen_rand.py create mode 100644 fastboot/vendor_boot_img_utils.cpp create mode 100644 fastboot/vendor_boot_img_utils.h create mode 100644 fastboot/vendor_boot_img_utils_test.cpp diff --git a/fastboot/Android.bp b/fastboot/Android.bp index 720810d05..3c4269b7c 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -55,6 +55,7 @@ cc_library_host_static { "tcp.cpp", "udp.cpp", "util.cpp", + "vendor_boot_img_utils.cpp", "fastboot_driver.cpp", ], @@ -75,6 +76,7 @@ cc_library_host_static { ], header_libs: [ + "avb_headers", "bootimg_headers", "libstorage_literals_headers", ], @@ -270,6 +272,7 @@ cc_library_host_static { "tcp.cpp", "udp.cpp", "util.cpp", + "vendor_boot_img_utils.cpp", "fastboot_driver.cpp", ], @@ -277,6 +280,7 @@ cc_library_host_static { use_version_lib: false, static_libs: ["libbuildversion"], header_libs: [ + "avb_headers", "libstorage_literals_headers", ], @@ -370,3 +374,33 @@ cc_test_host { }, }, } + +cc_test_host { + name: "fastboot_vendor_boot_img_utils_test", + srcs: ["vendor_boot_img_utils_test.cpp"], + static_libs: [ + "libbase", + "libc++fs", + "libfastboot", + "libgmock", + "liblog", + ], + header_libs: [ + "avb_headers", + "bootimg_headers", + ], + cflags: [ + "-Wall", + "-Werror", + ], + data: [ + ":fastboot_test_dtb", + ":fastboot_test_bootconfig", + ":fastboot_test_vendor_ramdisk_none", + ":fastboot_test_vendor_ramdisk_platform", + ":fastboot_test_vendor_ramdisk_replace", + ":fastboot_test_vendor_boot_v3", + ":fastboot_test_vendor_boot_v4_without_frag", + ":fastboot_test_vendor_boot_v4_with_frag" + ], +} diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index dacac97ed..94efeea36 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -76,6 +76,7 @@ #include "udp.h" #include "usb.h" #include "util.h" +#include "vendor_boot_img_utils.h" using android::base::borrowed_fd; using android::base::ReadFully; @@ -1292,6 +1293,40 @@ static void do_fetch(const std::string& partition, const std::string& slot_overr do_for_partitions(partition, slot_override, fetch, false /* force slot */); } +// Return immediately if not flashing a vendor boot image. If flashing a vendor boot image, +// repack vendor_boot image with an updated ramdisk. After execution, buf is set +// to the new image to flash, and return value is the real partition name to flash. +static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) { + std::string_view pname_sv{pname}; + + if (!android::base::StartsWith(pname_sv, "vendor_boot:") && + !android::base::StartsWith(pname_sv, "vendor_boot_a:") && + !android::base::StartsWith(pname_sv, "vendor_boot_b:")) { + return std::string(pname_sv); + } + if (buf->type != FB_BUFFER_FD) { + die("Flashing sparse vendor ramdisk image is not supported."); + } + if (buf->sz <= 0) { + die("repack_ramdisk() sees negative size: %" PRId64, buf->sz); + } + std::string partition(pname_sv.substr(0, pname_sv.find(':'))); + std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1)); + + unique_fd vendor_boot(make_temporary_fd("vendor boot repack")); + uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot); + auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd, + static_cast(buf->sz)); + if (!repack_res.ok()) { + die("%s", repack_res.error().message().c_str()); + } + + buf->fd = std::move(vendor_boot); + buf->sz = vendor_boot_size; + buf->image_size = vendor_boot_size; + return partition; +} + static void do_flash(const char* pname, const char* fname) { verbose("Do flash %s %s", pname, fname); struct fastboot_buffer buf; @@ -1302,7 +1337,8 @@ static void do_flash(const char* pname, const char* fname) { if (is_logical(pname)) { fb->ResizePartition(pname, std::to_string(buf.image_size)); } - flash_buf(pname, &buf); + std::string flash_pname = repack_ramdisk(pname, &buf); + flash_buf(flash_pname, &buf); } // Sets slot_override as the active slot. If slot_override is blank, diff --git a/fastboot/testdata/Android.bp b/fastboot/testdata/Android.bp new file mode 100644 index 000000000..5debf5e44 --- /dev/null +++ b/fastboot/testdata/Android.bp @@ -0,0 +1,136 @@ +// Copyright (C) 2021 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. + +python_binary_host { + name: "fastboot_gen_rand", + visibility: [":__subpackages__"], + srcs: ["fastboot_gen_rand.py"], +} + +genrule_defaults { + name: "fastboot_test_data_gen_defaults", + visibility: ["//system/core/fastboot"], + tools: [ + "fastboot_gen_rand", + ], +} + +// Genrules for components of test vendor boot image. + +// Fake dtb image. +genrule { + name: "fastboot_test_dtb", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_dtb.img"], + cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)", +} + +// Fake bootconfig image. +genrule { + name: "fastboot_test_bootconfig", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_bootconfig.img"], + cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)", +} + +// Fake vendor ramdisk with type "none". +genrule { + name: "fastboot_test_vendor_ramdisk_none", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_vendor_ramdisk_none.img"], + cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)", +} + +// Fake vendor ramdisk with type "platform". +genrule { + name: "fastboot_test_vendor_ramdisk_platform", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_vendor_ramdisk_platform.img"], + cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)", +} + +// Fake replacement ramdisk. +genrule { + name: "fastboot_test_vendor_ramdisk_replace", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_vendor_ramdisk_replace.img"], + cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)", +} + +// Genrules for test vendor boot images. + +fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " + + "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))" + +genrule_defaults { + name: "fastboot_test_vendor_boot_gen_defaults", + defaults: ["fastboot_test_data_gen_defaults"], + tools: [ + "avbtool", + "mkbootimg", + ], +} + +genrule { + name: "fastboot_test_vendor_boot_v3", + defaults: ["fastboot_test_vendor_boot_gen_defaults"], + out: ["vendor_boot_v3.img"], + srcs: [ + ":fastboot_test_dtb", + ":fastboot_test_vendor_ramdisk_none", + ], + cmd: "$(location mkbootimg) --header_version 3 " + + "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " + + "--dtb $(location :fastboot_test_dtb) " + + "--vendor_boot $(out) && " + + fastboot_sign_test_image, +} + +genrule { + name: "fastboot_test_vendor_boot_v4_without_frag", + defaults: ["fastboot_test_vendor_boot_gen_defaults"], + out: ["vendor_boot_v4_without_frag.img"], + srcs: [ + ":fastboot_test_dtb", + ":fastboot_test_vendor_ramdisk_none", + ":fastboot_test_bootconfig", + ], + cmd: "$(location mkbootimg) --header_version 4 " + + "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " + + "--dtb $(location :fastboot_test_dtb) " + + "--vendor_bootconfig $(location :fastboot_test_bootconfig) " + + "--vendor_boot $(out) && " + + fastboot_sign_test_image, +} + +genrule { + name: "fastboot_test_vendor_boot_v4_with_frag", + defaults: ["fastboot_test_vendor_boot_gen_defaults"], + out: ["vendor_boot_v4_with_frag.img"], + srcs: [ + ":fastboot_test_dtb", + ":fastboot_test_vendor_ramdisk_none", + ":fastboot_test_vendor_ramdisk_platform", + ":fastboot_test_bootconfig", + ], + cmd: "$(location mkbootimg) --header_version 4 " + + "--dtb $(location :fastboot_test_dtb) " + + "--vendor_bootconfig $(location :fastboot_test_bootconfig) " + + "--ramdisk_type none --ramdisk_name none_ramdisk " + + "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " + + "--ramdisk_type platform --ramdisk_name platform_ramdisk " + + "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " + + "--vendor_boot $(out) && " + + fastboot_sign_test_image, +} diff --git a/fastboot/testdata/fastboot_gen_rand.py b/fastboot/testdata/fastboot_gen_rand.py new file mode 100644 index 000000000..a87467b88 --- /dev/null +++ b/fastboot/testdata/fastboot_gen_rand.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 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. + +""" +Write given number of random bytes, generated with optional seed. +""" + +import random, argparse + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--seed', help='Seed to random generator') + parser.add_argument('--length', type=int, required=True, help='Length of output') + args = parser.parse_args() + + if args.seed: + random.seed(args.seed) + + print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length))) diff --git a/fastboot/vendor_boot_img_utils.cpp b/fastboot/vendor_boot_img_utils.cpp new file mode 100644 index 000000000..2db20cdcf --- /dev/null +++ b/fastboot/vendor_boot_img_utils.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2021 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 "vendor_boot_img_utils.h" + +#include + +#include +#include +#include +#include + +namespace { + +using android::base::Result; + +// Updates a given buffer by creating a new one. +class DataUpdater { + public: + DataUpdater(const std::string& old_data) : old_data_(&old_data) { + old_data_ptr_ = old_data_->data(); + new_data_.resize(old_data_->size(), '\0'); + new_data_ptr_ = new_data_.data(); + } + // Copy |num_bytes| from src to dst. + [[nodiscard]] Result Copy(uint32_t num_bytes) { + if (num_bytes == 0) return {}; + if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok()) + return res; + if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok()) + return res; + memcpy(new_data_ptr_, old_data_ptr_, num_bytes); + old_data_ptr_ += num_bytes; + new_data_ptr_ += num_bytes; + return {}; + } + // Replace |old_num_bytes| from src with new data. + [[nodiscard]] Result Replace(uint32_t old_num_bytes, const std::string& new_data) { + return Replace(old_num_bytes, new_data.data(), new_data.size()); + } + [[nodiscard]] Result Replace(uint32_t old_num_bytes, const void* new_data, + uint32_t new_data_size) { + if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__); + !res.ok()) + return res; + old_data_ptr_ += old_num_bytes; + + if (new_data_size == 0) return {}; + if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__); + !res.ok()) + return res; + memcpy(new_data_ptr_, new_data, new_data_size); + new_data_ptr_ += new_data_size; + return {}; + } + // Skip |old_skip| from src and |new_skip| from dst, respectively. + [[nodiscard]] Result Skip(uint32_t old_skip, uint32_t new_skip) { + if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok()) + return res; + old_data_ptr_ += old_skip; + if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok()) + return res; + new_data_ptr_ += new_skip; + return {}; + } + + [[nodiscard]] Result Seek(uint32_t offset) { + if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size()); + old_data_ptr_ = old_begin() + offset; + new_data_ptr_ = new_begin() + offset; + return {}; + } + + std::string Finish() { + new_data_ptr_ = nullptr; + return std::move(new_data_); + } + + [[nodiscard]] Result CheckOffset(uint32_t old_offset, uint32_t new_offset) { + if (old_begin() + old_offset != old_cur()) + return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset, + old_cur() - old_begin()); + if (new_begin() + new_offset != new_cur()) + return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset, + new_cur() - new_begin()); + return {}; + } + + uint64_t size() const { return old_data_->size(); } + const char* old_begin() const { return old_data_->data(); } + const char* old_cur() { return old_data_ptr_; } + const char* old_end() const { return old_data_->data() + old_data_->size(); } + char* new_begin() { return new_data_.data(); } + char* new_cur() { return new_data_ptr_; } + char* new_end() { return new_data_.data() + new_data_.size(); } + + private: + // Check if it is okay to advance |num_bytes| from |current|. + [[nodiscard]] Result CheckAdvance(const char* current, const char* end, + uint32_t num_bytes, const char* op) { + auto new_end = current + num_bytes; + if (new_end < current /* add overflow */) + return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current), + num_bytes, fmt::ptr(current)); + if (new_end > end) + return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current), + num_bytes, fmt::ptr(end)); + return {}; + } + const std::string* old_data_; + std::string new_data_; + const char* old_data_ptr_; + char* new_data_ptr_; +}; + +// Get the size of vendor boot header. +[[nodiscard]] Result get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) { + if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3); + if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4); + return Errorf("Unrecognized vendor boot header version {}", hdr->header_version); +} + +// Check that content contains a valid vendor boot image header with a version at least |version|. +[[nodiscard]] Result check_vendor_boot_hdr(const std::string& content, uint32_t version) { + // get_vendor_boot_header_size reads header_version, so make sure reading it does not + // go out of bounds by ensuring that the content has at least the size of V3 header. + if (content.size() < sizeof(vendor_boot_img_hdr_v3)) { + return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}", + content.size(), sizeof(vendor_boot_img_hdr_v3)); + } + // Now read hdr->header_version and assert the size. + auto hdr = reinterpret_cast(content.data()); + auto expect_header_size = get_vendor_boot_header_size(hdr); + if (!expect_header_size.ok()) return expect_header_size.error(); + if (content.size() < *expect_header_size) { + return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}", + content.size(), version, *expect_header_size); + } + if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) { + return Errorf("Vendor boot image magic mismatch"); + } + if (hdr->header_version < version) { + return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version); + } + return {}; +} + +// Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string. +[[nodiscard]] Result load_file(android::base::borrowed_fd fd, uint64_t expected_size, + const char* what) { + if (lseek(fd.get(), 0, SEEK_SET) != 0) { + return ErrnoErrorf("Can't seek to the beginning of {} image", what); + } + std::string content; + if (!android::base::ReadFdToString(fd, &content)) { + return ErrnoErrorf("Cannot read {} to string", what); + } + if (content.size() != expected_size) { + return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what, + expected_size, content.size()); + } + return content; +} + +// Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string. +[[nodiscard]] Result store_file(android::base::borrowed_fd fd, const std::string& data, + const char* what) { + if (lseek(fd.get(), 0, SEEK_SET) != 0) { + return ErrnoErrorf("Cannot seek to beginning of {} before writing", what); + } + if (!android::base::WriteStringToFd(data, fd)) { + return ErrnoErrorf("Cannot write new content to {}", what); + } + if (TEMP_FAILURE_RETRY(ftruncate64(fd.get(), data.size())) == -1) { + return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size()); + } + return {}; +} + +// Copy AVB footer if it exists in the old buffer. +[[nodiscard]] Result copy_avb_footer(DataUpdater* updater) { + if (updater->size() < AVB_FOOTER_SIZE) return {}; + if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res; + if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {}; + return updater->Copy(AVB_FOOTER_SIZE); +} + +// round |value| up to a multiple of |page_size|. +inline uint32_t round_up(uint32_t value, uint32_t page_size) { + return (value + page_size - 1) / page_size * page_size; +} + +// Replace the vendor ramdisk as a whole. +[[nodiscard]] Result replace_default_vendor_ramdisk(const std::string& vendor_boot, + const std::string& new_ramdisk) { + if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error(); + auto hdr = reinterpret_cast(vendor_boot.data()); + auto hdr_size = get_vendor_boot_header_size(hdr); + if (!hdr_size.ok()) return hdr_size.error(); + // Refer to bootimg.h for details. Numbers are in bytes. + const uint32_t o = round_up(*hdr_size, hdr->page_size); + const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + const uint32_t q = round_up(hdr->dtb_size, hdr->page_size); + + DataUpdater updater(vendor_boot); + + // Copy header (O bytes), then update fields in header. + if (auto res = updater.Copy(o); !res.ok()) return res.error(); + auto new_hdr = reinterpret_cast(updater.new_begin()); + new_hdr->vendor_ramdisk_size = new_ramdisk.size(); + // Because it is unknown how the new ramdisk is fragmented, the whole table is replaced + // with a single entry representing the full ramdisk. + if (new_hdr->header_version >= 4) { + auto new_hdr_v4 = static_cast(new_hdr); + new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4); + new_hdr_v4->vendor_ramdisk_table_entry_num = 1; + new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num * + new_hdr_v4->vendor_ramdisk_table_entry_size; + } + + // Copy the new ramdisk. + if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok()) + return res.error(); + const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size); + if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size); + !res.ok()) + return res.error(); + if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error(); + + // Copy DTB (Q bytes). + if (auto res = updater.Copy(q); !res.ok()) return res.error(); + + if (new_hdr->header_version >= 4) { + auto hdr_v4 = static_cast(hdr); + const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size); + const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size); + + auto new_entry = reinterpret_cast(updater.new_cur()); + auto new_hdr_v4 = static_cast(new_hdr); + auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size); + if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error(); + if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + new_r); !res.ok()) + return res.error(); + + // Replace table with single entry representing the full ramdisk. + new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size; + new_entry->ramdisk_offset = 0; + new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE; + memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE); + memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE); + + // Copy bootconfig (S bytes). + if (auto res = updater.Copy(s); !res.ok()) return res.error(); + } + + if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error(); + return updater.Finish(); +} + +// Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found. +[[nodiscard]] Result find_unique_ramdisk( + const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table, + uint32_t size) { + const vendor_ramdisk_table_entry_v4* ret = nullptr; + uint32_t idx = 0; + const vendor_ramdisk_table_entry_v4* entry = table; + for (; idx < size; idx++, entry++) { + auto entry_name_c_str = reinterpret_cast(entry->ramdisk_name); + auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE); + std::string_view entry_name(entry_name_c_str, entry_name_len); + if (entry_name == ramdisk_name) { + if (ret != nullptr) { + return Errorf("Multiple vendor ramdisk '{}' found, name should be unique", + ramdisk_name.c_str()); + } + ret = entry; + } + } + if (ret == nullptr) { + return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str()); + } + return ret; +} + +// Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and +// replace it with the content of |new_ramdisk|. +[[nodiscard]] Result replace_vendor_ramdisk_fragment(const std::string& ramdisk_name, + const std::string& vendor_boot, + const std::string& new_ramdisk) { + if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error(); + auto hdr = reinterpret_cast(vendor_boot.data()); + auto hdr_size = get_vendor_boot_header_size(hdr); + if (!hdr_size.ok()) return hdr_size.error(); + // Refer to bootimg.h for details. Numbers are in bytes. + const uint32_t o = round_up(*hdr_size, hdr->page_size); + const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + const uint32_t q = round_up(hdr->dtb_size, hdr->page_size); + const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size); + const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size); + + if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits::max()) { + return Errorf("Too many vendor ramdisk entries in table, overflow"); + } + + // Find entry with name |ramdisk_name|. + auto old_table_start = + reinterpret_cast(vendor_boot.data() + o + p + q); + auto find_res = + find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num); + if (!find_res.ok()) return find_res.error(); + const vendor_ramdisk_table_entry_v4* replace_entry = *find_res; + uint32_t replace_idx = replace_entry - old_table_start; + + // Now reconstruct. + DataUpdater updater(vendor_boot); + + // Copy header (O bytes), then update fields in header. + if (auto res = updater.Copy(o); !res.ok()) return res.error(); + auto new_hdr = reinterpret_cast(updater.new_begin()); + + // Copy ramdisk fragments, replace for the matching index. + { + auto old_ramdisk_entry = reinterpret_cast( + vendor_boot.data() + o + p + q); + uint32_t new_total_ramdisk_size = 0; + for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num; + new_ramdisk_idx++, old_ramdisk_entry++) { + if (new_ramdisk_idx == replace_idx) { + if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok()) + return res.error(); + new_total_ramdisk_size += new_ramdisk.size(); + } else { + if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok()) + return res.error(); + new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size; + } + } + new_hdr->vendor_ramdisk_size = new_total_ramdisk_size; + } + + // Pad ramdisk to page boundary. + const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size); + if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size); + !res.ok()) + return res.error(); + if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error(); + + // Copy DTB (Q bytes). + if (auto res = updater.Copy(q); !res.ok()) return res.error(); + + // Copy table, but with corresponding entries modified, including: + // - ramdisk_size of the entry replaced + // - ramdisk_offset of subsequent entries. + for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0; + new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) { + auto new_entry = reinterpret_cast(updater.new_cur()); + if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok()) + return res.error(); + new_entry->ramdisk_offset = new_total_ramdisk_size; + + if (new_entry_idx == replace_idx) { + new_entry->ramdisk_size = new_ramdisk.size(); + } + new_total_ramdisk_size += new_entry->ramdisk_size; + } + + // Copy padding of R pages; this is okay because table size is not changed. + if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num * + hdr->vendor_ramdisk_table_entry_size); + !res.ok()) + return res.error(); + if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + r); !res.ok()) + return res.error(); + + // Copy bootconfig (S bytes). + if (auto res = updater.Copy(s); !res.ok()) return res.error(); + + if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error(); + return updater.Finish(); +} + +} // namespace + +[[nodiscard]] Result replace_vendor_ramdisk(android::base::borrowed_fd vendor_boot_fd, + uint64_t vendor_boot_size, + const std::string& ramdisk_name, + android::base::borrowed_fd new_ramdisk_fd, + uint64_t new_ramdisk_size) { + if (new_ramdisk_size > std::numeric_limits::max()) { + return Errorf("New vendor ramdisk is too big"); + } + + auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot"); + if (!vendor_boot.ok()) return vendor_boot.error(); + auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk"); + if (!new_ramdisk.ok()) return new_ramdisk.error(); + + Result new_vendor_boot; + if (ramdisk_name == "default") { + new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk); + } else { + new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk); + } + if (!new_vendor_boot.ok()) return new_vendor_boot.error(); + if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok()) + return res.error(); + + return {}; +} diff --git a/fastboot/vendor_boot_img_utils.h b/fastboot/vendor_boot_img_utils.h new file mode 100644 index 000000000..0b702bc4d --- /dev/null +++ b/fastboot/vendor_boot_img_utils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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. + */ + +#pragma once + +#include + +#include + +#include +#include + +// Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image, +// specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks +// that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively. +// If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace +// a vendor ramdisk fragment with the given unique name. +[[nodiscard]] android::base::Result replace_vendor_ramdisk( + android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size, + const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd, + uint64_t new_ramdisk_size); diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp new file mode 100644 index 000000000..1563b89c7 --- /dev/null +++ b/fastboot/vendor_boot_img_utils_test.cpp @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2021 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 +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vendor_boot_img_utils.h" + +using android::base::borrowed_fd; +using android::base::ErrnoError; +using android::base::GetExecutableDirectory; +using android::base::ReadFdToString; +using android::base::Result; +using testing::AllOf; +using testing::Each; +using testing::Eq; +using testing::HasSubstr; +using testing::Not; +using testing::Property; +using std::string_literals::operator""s; + +// Expect that the Result returned by |expr| is successful, and value matches |result_matcher|. +#define EXPECT_RESULT(expr, result_matcher) \ + EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \ + Property(&decltype(expr)::value, result_matcher))) + +// Expect that the Result returned by |expr| fails, and error message matches |error_matcher|. +#define EXPECT_ERROR(expr, error_matcher) \ + do { \ + EXPECT_THAT( \ + expr, \ + AllOf(Property(&decltype(expr)::ok, Eq(false)), \ + Property(&decltype(expr)::error, \ + Property(&decltype(expr)::error_type::message, error_matcher)))); \ + } while (0) + +namespace { + +// Wrapper of fstat. +Result FileSize(borrowed_fd fd, std::filesystem::path path) { + struct stat sb; + if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")"; + return sb.st_size; +} + +// Seek to beginning then read the whole file. +Result ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) { + if (lseek64(fd.get(), 0, SEEK_SET) != 0) + return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)"; + std::string content; + if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")"; + return content; +} + +// Round |value| up to page boundary. +inline uint32_t round_up(uint32_t value, uint32_t page_size) { + return (value + page_size - 1) / page_size * page_size; +} + +// Match is successful if |arg| is a zero-padded version of |expected|. +MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) { + if (arg.size() < expected.size()) return false; + if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false; + auto remainder = std::string_view(arg).substr(expected.size()); + for (char e : remainder) + if (e != '\0') return false; + return true; +} + +// Same as Eq, but don't print the content to avoid spam. +MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) { + if (arg.size() != expected.size()) return false; + return 0 == memcmp(arg.data(), expected.data(), expected.size()); +} + +// Expect that |arg| and |expected| has the same AVB footer. +MATCHER_P(HasSameAvbFooter, expected, + (negation ? "has" : "does not have") + "expected AVB footer"s) { + if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false; + return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) == + std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE); +} + +// A lazy handle of a file. +struct TestFileHandle { + virtual ~TestFileHandle() = default; + // Lazily call OpenImpl(), cache result in open_result_. + android::base::Result Open() { + if (!open_result_.has_value()) open_result_ = OpenImpl(); + return open_result_.value(); + } + // The original size at the time when the file is opened. If the file has been modified, + // this field is NOT updated. + uint64_t size() { + CHECK(open_result_.has_value()); + return size_; + } + // The current size of the file. If the file has been modified since opened, + // this is updated. + Result fsize() { + CHECK(open_result_.has_value()); + return FileSize(fd_, abs_path_); + } + borrowed_fd fd() { + CHECK(open_result_.has_value()); + return fd_; + } + Result Read() { + CHECK(open_result_.has_value()); + return ReadStartOfFdToString(fd_, abs_path_); + } + + private: + std::filesystem::path abs_path_; + uint64_t size_; + std::optional> open_result_; + borrowed_fd fd_{-1}; + // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to + // |borrowed_fd_|. + android::base::Result OpenImpl() { + android::base::unique_fd read_fd(TEMP_FAILURE_RETRY( + open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY))); + if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")"; + auto size = FileSize(read_fd, abs_path_); + if (!size.ok()) return size.error(); + size_ = *size; + + auto borrowed_fd = Transform(abs_path_, std::move(read_fd)); + if (!borrowed_fd.ok()) return borrowed_fd.error(); + fd_ = borrowed_fd.value(); + + return {}; + } + + protected: + // |rel_path| is the relative path under test data directory. + TestFileHandle(const std::filesystem::path& rel_path) + : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {} + // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client + // to use. Implementation is responsible for managing the lifetime of the returned fd. + virtual android::base::Result Transform(const std::filesystem::path& abs_path, + android::base::unique_fd read_fd) = 0; +}; + +// A TestFileHandle where the file is readonly. +struct ReadOnlyTestFileHandle : TestFileHandle { + ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {} + + private: + android::base::unique_fd owned_fd_; + android::base::Result Transform(const std::filesystem::path&, + android::base::unique_fd read_fd) override { + owned_fd_ = std::move(read_fd); + return owned_fd_; + } +}; + +// A TestFileHandle where the test file is copies, hence read-writable. +struct ReadWriteTestFileHandle : TestFileHandle { + ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {} + + private: + std::unique_ptr temp_file_; + + android::base::Result Transform(const std::filesystem::path& abs_path, + android::base::unique_fd read_fd) override { + // Make a copy to avoid writing to test data. Test files are small, so it is okay + // to read the whole file. + auto content = ReadStartOfFdToString(read_fd, abs_path); + if (!content.ok()) return content.error(); + temp_file_ = std::make_unique(); + if (temp_file_->fd == -1) + return ErrnoError() << "copy " << abs_path << ": open temp file failed"; + if (!android::base::WriteStringToFd(*content, temp_file_->fd)) + return ErrnoError() << "copy " << abs_path << ": write temp file failed"; + + return temp_file_->fd; + } +}; + +class RepackVendorBootImgTestEnv : public ::testing::Environment { + public: + virtual void SetUp() { + OpenTestFile("test_dtb.img", &dtb, &dtb_content); + OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content); + OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content); + OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content); + OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content); + } + + std::unique_ptr dtb; + std::string dtb_content; + std::unique_ptr bootconfig; + std::string bootconfig_content; + std::unique_ptr none; + std::string none_content; + std::unique_ptr platform; + std::string platform_content; + std::unique_ptr replace; + std::string replace_content; + + private: + void OpenTestFile(const char* rel_path, std::unique_ptr* handle, + std::string* content) { + *handle = std::make_unique(rel_path); + ASSERT_RESULT_OK((*handle)->Open()); + auto content_res = (*handle)->Read(); + ASSERT_RESULT_OK(content_res); + *content = *content_res; + } +}; +RepackVendorBootImgTestEnv* env = nullptr; + +struct RepackVendorBootImgTestParam { + std::string vendor_boot_file_name; + uint32_t expected_header_version; + friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) { + return os << param.vendor_boot_file_name; + } +}; + +class RepackVendorBootImgTest : public ::testing::TestWithParam { + public: + virtual void SetUp() { + vboot = std::make_unique(GetParam().vendor_boot_file_name); + ASSERT_RESULT_OK(vboot->Open()); + } + std::unique_ptr vboot; +}; + +TEST_P(RepackVendorBootImgTest, InvalidSize) { + EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default", + env->replace->fd(), env->replace->size()), + HasSubstr("Size of vendor boot does not match")); + EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(), + env->replace->size() + 1), + HasSubstr("Size of new vendor ramdisk does not match")); +} + +TEST_P(RepackVendorBootImgTest, ReplaceUnknown) { + auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(), + env->replace->size()); + if (GetParam().expected_header_version == 3) { + EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3")); + } else if (GetParam().expected_header_version == 4) { + EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found")); + } +} + +TEST_P(RepackVendorBootImgTest, ReplaceDefault) { + auto old_content = vboot->Read(); + ASSERT_RESULT_OK(old_content); + + ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", + env->replace->fd(), env->replace->size())); + EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack"; + + auto new_content_res = vboot->Read(); + ASSERT_RESULT_OK(new_content_res); + std::string_view new_content(*new_content_res); + + auto hdr = reinterpret_cast(new_content.data()); + ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE)); + ASSERT_EQ(GetParam().expected_header_version, hdr->header_version); + EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size()); + EXPECT_EQ(hdr->dtb_size, env->dtb->size()); + + auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size); + auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + auto q = round_up(hdr->dtb_size, hdr->page_size); + + EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content)); + EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content)); + + if (hdr->header_version < 4) return; + + auto hdr_v4 = static_cast(hdr); + EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1); + EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size); + EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4)); + auto entry = reinterpret_cast(&new_content[o + p + q]); + EXPECT_EQ(entry->ramdisk_offset, 0); + EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size); + EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE); + + EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size()); + auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size); + auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size); + EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content)); + + EXPECT_THAT(new_content, HasSameAvbFooter(*old_content)); +} + +INSTANTIATE_TEST_SUITE_P( + RepackVendorBootImgTest, RepackVendorBootImgTest, + ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3}, + RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4}, + RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}), + [](const auto& info) { + return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false); + }); + +std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) { + auto ramdisk_name = reinterpret_cast(entry->ramdisk_name); + return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE)); +} + +class RepackVendorBootImgTestV4 : public ::testing::TestWithParam { + public: + virtual void SetUp() { + vboot = std::make_unique("vendor_boot_v4_with_frag.img"); + ASSERT_RESULT_OK(vboot->Open()); + } + std::unique_ptr vboot; +}; + +TEST_P(RepackVendorBootImgTestV4, Replace) { + uint32_t replace_ramdisk_type = GetParam(); + std::string replace_ramdisk_name; + std::string expect_new_ramdisk_content; + uint32_t expect_none_size = env->none->size(); + uint32_t expect_platform_size = env->platform->size(); + switch (replace_ramdisk_type) { + case VENDOR_RAMDISK_TYPE_NONE: + replace_ramdisk_name = "none_ramdisk"; + expect_new_ramdisk_content = env->replace_content + env->platform_content; + expect_none_size = env->replace->size(); + break; + case VENDOR_RAMDISK_TYPE_PLATFORM: + replace_ramdisk_name = "platform_ramdisk"; + expect_new_ramdisk_content = env->none_content + env->replace_content; + expect_platform_size = env->replace->size(); + break; + default: + LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type + << " is not supported by this test."; + } + + auto old_content = vboot->Read(); + ASSERT_RESULT_OK(old_content); + + ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name, + env->replace->fd(), env->replace->size())); + EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack"; + + auto new_content_res = vboot->Read(); + ASSERT_RESULT_OK(new_content_res); + std::string_view new_content(*new_content_res); + + auto hdr = reinterpret_cast(new_content.data()); + ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE)); + ASSERT_EQ(4, hdr->header_version); + EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size); + EXPECT_EQ(hdr->dtb_size, env->dtb->size()); + EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size()); + + auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size); + auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + auto q = round_up(hdr->dtb_size, hdr->page_size); + auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size); + auto s = round_up(hdr->bootconfig_size, hdr->page_size); + + EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content)); + EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content)); + + // Check changes in table. + EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2); + EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size); + EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4)); + auto entry_none = + reinterpret_cast(&new_content[o + p + q]); + EXPECT_EQ(entry_none->ramdisk_offset, 0); + EXPECT_EQ(entry_none->ramdisk_size, expect_none_size); + EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE); + EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk"); + + auto entry_platform = reinterpret_cast( + &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]); + EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size); + EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size); + EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM); + EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk"); + + EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content)); + + EXPECT_THAT(new_content, HasSameAvbFooter(*old_content)); +} +INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4, + ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM), + [](const auto& info) { + return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform"; + }); + +} // namespace + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + env = static_cast( + testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv)); + return RUN_ALL_TESTS(); +}