From b299cb7217e7f63d9128a0e595bcc21ab645f41f Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Wed, 17 Feb 2021 13:44:49 -0800 Subject: [PATCH 01/13] fastbootd: Add getvar max-fetch-size. Test: run it Test: see follow up CL on fuzzy_fastboot Bug: 173654501 Change-Id: I5ed110c5569d83cbe791d04b4abea3a2af2765a9 --- fastboot/Android.bp | 6 ++++++ fastboot/constants.h | 1 + fastboot/device/commands.cpp | 4 +++- fastboot/device/commands.h | 1 + fastboot/device/variables.cpp | 16 ++++++++++++++++ fastboot/device/variables.h | 2 ++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/fastboot/Android.bp b/fastboot/Android.bp index a1f1c17cb..daff496d9 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -138,6 +138,12 @@ cc_binary { recovery: true, + product_variables: { + debuggable: { + cppflags: ["-DFB_ENABLE_FETCH"], + }, + }, + srcs: [ "device/commands.cpp", "device/fastboot_device.cpp", diff --git a/fastboot/constants.h b/fastboot/constants.h index ba43ca5bd..4cc154adb 100644 --- a/fastboot/constants.h +++ b/fastboot/constants.h @@ -77,3 +77,4 @@ #define FB_VAR_FIRST_API_LEVEL "first-api-level" #define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level" #define FB_VAR_TREBLE_ENABLED "treble-enabled" +#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size" diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp index b2b6a9e5c..3c56f5001 100644 --- a/fastboot/device/commands.cpp +++ b/fastboot/device/commands.cpp @@ -136,7 +136,9 @@ bool GetVarHandler(FastbootDevice* device, const std::vector& args) {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}}, {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}}, {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}}, - {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}}; + {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}, + {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}}, + }; if (args.size() < 2) { return device->WriteFail("Missing argument"); diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h index c1324bc40..108ad2f58 100644 --- a/fastboot/device/commands.h +++ b/fastboot/device/commands.h @@ -20,6 +20,7 @@ #include constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000; +constexpr unsigned int kMaxFetchSizeDefault = 0x10000000; class FastbootDevice; diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp index e7d8bc366..ee1eed876 100644 --- a/fastboot/device/variables.cpp +++ b/fastboot/device/variables.cpp @@ -33,6 +33,12 @@ #include "flashing.h" #include "utility.h" +#ifdef FB_ENABLE_FETCH +static constexpr bool kEnableFetch = true; +#else +static constexpr bool kEnableFetch = false; +#endif + using ::android::hardware::boot::V1_0::BoolResult; using ::android::hardware::boot::V1_0::Slot; using ::android::hardware::boot::V1_1::MergeStatus; @@ -509,3 +515,13 @@ bool GetTrebleEnabled(FastbootDevice* /* device */, const std::vector& /* args */, + std::string* message) { + if (!kEnableFetch) { + *message = "fetch not supported on user builds"; + return false; + } + *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault); + return true; +} diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h index c11e472b7..f40a0257e 100644 --- a/fastboot/device/variables.h +++ b/fastboot/device/variables.h @@ -80,6 +80,8 @@ bool GetSecurityPatchLevel(FastbootDevice* device, const std::vector& args, std::string* message); +bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector& /* args */, + std::string* message); // Helpers for getvar all. std::vector> GetAllPartitionArgsWithSlot(FastbootDevice* device); From a4eb4753acc9f5265a08300cb72bb829d1c96db4 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 16 Feb 2021 19:37:21 -0800 Subject: [PATCH 02/13] fastbootd: Add fetch command on device Test: manual Test: see follow up CL on fuzzy_fastboot Bug: 173654501 Change-Id: I912d60d6dca0082734f2b84adc6a647c881bb5a1 --- fastboot/Android.bp | 3 +- fastboot/constants.h | 1 + fastboot/device/commands.cpp | 169 ++++++++++++++++++++++++++++ fastboot/device/commands.h | 1 + fastboot/device/fastboot_device.cpp | 13 ++- fastboot/device/fastboot_device.h | 1 + 6 files changed, 183 insertions(+), 5 deletions(-) diff --git a/fastboot/Android.bp b/fastboot/Android.bp index daff496d9..8e98e9fd5 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -189,7 +189,8 @@ cc_binary { header_libs: [ "avb_headers", "libsnapshot_headers", - ] + "libstorage_literals_headers", + ], } cc_defaults { diff --git a/fastboot/constants.h b/fastboot/constants.h index 4cc154adb..4ea68daff 100644 --- a/fastboot/constants.h +++ b/fastboot/constants.h @@ -35,6 +35,7 @@ #define FB_CMD_OEM "oem" #define FB_CMD_GSI "gsi" #define FB_CMD_SNAPSHOT_UPDATE "snapshot-update" +#define FB_CMD_FETCH "fetch" #define RESPONSE_OKAY "OKAY" #define RESPONSE_FAIL "FAIL" diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp index 3c56f5001..cf706357c 100644 --- a/fastboot/device/commands.cpp +++ b/fastboot/device/commands.cpp @@ -16,6 +16,7 @@ #include "commands.h" +#include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include "constants.h" @@ -43,6 +45,12 @@ #include "flashing.h" #include "utility.h" +#ifdef FB_ENABLE_FETCH +static constexpr bool kEnableFetch = true; +#else +static constexpr bool kEnableFetch = false; +#endif + using android::fs_mgr::MetadataBuilder; using ::android::hardware::hidl_string; using ::android::hardware::boot::V1_0::BoolResult; @@ -54,6 +62,8 @@ using ::android::hardware::fastboot::V1_0::Status; using android::snapshot::SnapshotManager; using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl; +using namespace android::storage_literals; + struct VariableHandlers { // Callback to retrieve the value of a single variable. std::function&, std::string*)> get; @@ -673,3 +683,162 @@ bool SnapshotUpdateHandler(FastbootDevice* device, const std::vectorWriteStatus(FastbootResult::OKAY, "Success"); } + +namespace { +// Helper of FetchHandler. +class PartitionFetcher { + public: + static bool Fetch(FastbootDevice* device, const std::vector& args) { + if constexpr (!kEnableFetch) { + return device->WriteFail("Fetch is not allowed on user build"); + } + + if (GetDeviceLockStatus()) { + return device->WriteFail("Fetch is not allowed on locked devices"); + } + + PartitionFetcher fetcher(device, args); + if (fetcher.Open()) { + fetcher.Fetch(); + } + CHECK(fetcher.ret_.has_value()); + return *fetcher.ret_; + } + + private: + PartitionFetcher(FastbootDevice* device, const std::vector& args) + : device_(device), args_(&args) {} + // Return whether the partition is successfully opened. + // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value + // that FetchHandler should return. + bool Open() { + partition_name_ = args_->at(1); + if (partition_name_ != "vendor_boot") { + ret_ = device_->WriteFail("Fetch is only allowed on vendor_boot"); + return false; + } + + if (!OpenPartition(device_, partition_name_, &handle_)) { + ret_ = device_->WriteFail( + android::base::StringPrintf("Cannot open %s", partition_name_.c_str())); + return false; + } + + partition_size_ = get_block_device_size(handle_.fd()); + if (partition_size_ == 0) { + ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0", + partition_name_.c_str())); + return false; + } + + start_offset_ = 0; + if (args_->size() >= 3) { + if (!android::base::ParseUint(args_->at(2), &start_offset_)) { + ret_ = device_->WriteFail("Invalid offset, must be integer"); + return false; + } + if (start_offset_ > std::numeric_limits::max()) { + ret_ = device_->WriteFail( + android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_)); + return false; + } + } + if (start_offset_ > partition_size_) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_, + partition_name_.c_str(), partition_size_)); + return false; + } + uint64_t maximum_total_size_to_read = partition_size_ - start_offset_; + total_size_to_read_ = maximum_total_size_to_read; + if (args_->size() >= 4) { + if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) { + ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer"); + return false; + } + } + if (total_size_to_read_ == 0) { + ret_ = device_->WriteOkay("Read 0 bytes"); + return false; + } + if (total_size_to_read_ > maximum_total_size_to_read) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64 + " and fetching from offset 0x%" PRIx64, + total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_)); + return false; + } + + if (total_size_to_read_ > kMaxFetchSizeDefault) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "Cannot fetch 0x%" PRIx64 + " bytes because it exceeds maximum transport size 0x%x", + partition_size_, kMaxDownloadSizeDefault)); + return false; + } + + return true; + } + + // Assume Open() returns true. + // After execution, ret_ is set to the value that FetchHandler should return. + void Fetch() { + CHECK(start_offset_ <= std::numeric_limits::max()); + if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast(start_offset_)) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(), + start_offset_, strerror(errno))); + return; + } + + if (!device_->WriteStatus(FastbootResult::DATA, + android::base::StringPrintf( + "%08x", static_cast(total_size_to_read_)))) { + ret_ = false; + return; + } + uint64_t end_offset = start_offset_ + total_size_to_read_; + std::vector buf(1_MiB); + uint64_t current_offset = start_offset_; + while (current_offset < end_offset) { + // On any error, exit. We can't return a status message to the driver because + // we are in the middle of writing data, so just let the driver guess what's wrong + // by ending the data stream prematurely. + uint64_t remaining = end_offset - current_offset; + uint64_t chunk_size = std::min(buf.size(), remaining); + if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) { + PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from " + << partition_name_ << " @ offset 0x" << current_offset; + ret_ = false; + return; + } + if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) { + PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of " + << partition_name_ << " @ offset 0x" << current_offset; + ret_ = false; + return; + } + current_offset += chunk_size; + } + + ret_ = device_->WriteOkay(android::base::StringPrintf( + "Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(), + start_offset_, total_size_to_read_)); + } + + FastbootDevice* device_; + const std::vector* args_ = nullptr; + std::string partition_name_; + PartitionHandle handle_; + uint64_t partition_size_ = 0; + uint64_t start_offset_ = 0; + uint64_t total_size_to_read_ = 0; + + // What FetchHandler should return. + std::optional ret_ = std::nullopt; +}; +} // namespace + +bool FetchHandler(FastbootDevice* device, const std::vector& args) { + return PartitionFetcher::Fetch(device, args); +} diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h index 108ad2f58..345ae1afe 100644 --- a/fastboot/device/commands.h +++ b/fastboot/device/commands.h @@ -51,3 +51,4 @@ bool UpdateSuperHandler(FastbootDevice* device, const std::vector& bool OemCmdHandler(FastbootDevice* device, const std::vector& args); bool GsiHandler(FastbootDevice* device, const std::vector& args); bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector& args); +bool FetchHandler(FastbootDevice* device, const std::vector& args); diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp index 35f3de020..64a934ddb 100644 --- a/fastboot/device/fastboot_device.cpp +++ b/fastboot/device/fastboot_device.cpp @@ -61,6 +61,7 @@ FastbootDevice::FastbootDevice() {FB_CMD_OEM, OemCmdHandler}, {FB_CMD_GSI, GsiHandler}, {FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler}, + {FB_CMD_FETCH, FetchHandler}, }), boot_control_hal_(IBootControl::getService()), health_hal_(get_health_service()), @@ -137,14 +138,18 @@ bool FastbootDevice::WriteStatus(FastbootResult result, const std::string& messa } bool FastbootDevice::HandleData(bool read, std::vector* data) { - auto read_write_data_size = read ? this->get_transport()->Read(data->data(), data->size()) - : this->get_transport()->Write(data->data(), data->size()); + return HandleData(read, data->data(), data->size()); +} + +bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) { + auto read_write_data_size = read ? this->get_transport()->Read(data, size) + : this->get_transport()->Write(data, size); if (read_write_data_size == -1) { LOG(ERROR) << (read ? "read from" : "write to") << " transport failed"; return false; } - if (static_cast(read_write_data_size) != data->size()) { - LOG(ERROR) << (read ? "read" : "write") << " expected " << data->size() << " bytes, got " + if (static_cast(read_write_data_size) != size) { + LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got " << read_write_data_size; return false; } diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h index 23be72143..35361365c 100644 --- a/fastboot/device/fastboot_device.h +++ b/fastboot/device/fastboot_device.h @@ -40,6 +40,7 @@ class FastbootDevice { void ExecuteCommands(); bool WriteStatus(FastbootResult result, const std::string& message); bool HandleData(bool read, std::vector* data); + bool HandleData(bool read, char* data, uint64_t size); std::string GetCurrentSlot(); // Shortcuts for writing status results. From c4073d3e13ac7a769adbf95d5042407096e22f8a Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Thu, 18 Feb 2021 12:35:42 -0800 Subject: [PATCH 03/13] fastbootd: add read arg to OpenPartition Allow it to read partitions. Bug: 173654501 Test: pass Change-Id: I115e84734dd258243ca3f4f1b373b06adcaa4080 --- fastboot/device/utility.cpp | 6 ++++-- fastboot/device/utility.h | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp index 7c6ac8993..4c847987f 100644 --- a/fastboot/device/utility.cpp +++ b/fastboot/device/utility.cpp @@ -77,7 +77,8 @@ bool OpenLogicalPartition(FastbootDevice* device, const std::string& partition_n } // namespace -bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle) { +bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle, + bool read) { // We prioritize logical partitions over physical ones, and do this // consistently for other partition operations (like getvar:partition-size). if (LogicalPartitionExists(device, name)) { @@ -89,7 +90,8 @@ bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHan return false; } - unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), O_WRONLY | O_EXCL))); + int flags = O_EXCL | (read ? O_RDONLY : O_WRONLY); + unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags))); if (fd < 0) { PLOG(ERROR) << "Failed to open block device: " << handle->path(); return false; diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h index 3b71ef006..c2646d718 100644 --- a/fastboot/device/utility.h +++ b/fastboot/device/utility.h @@ -75,7 +75,11 @@ std::string GetSuperSlotSuffix(FastbootDevice* device, const std::string& partit std::optional FindPhysicalPartition(const std::string& name); bool LogicalPartitionExists(FastbootDevice* device, const std::string& name, bool* is_zero_length = nullptr); -bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle); + +// If read, partition is readonly. Else it is write only. +bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle, + bool read = false); + bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number); std::vector ListPartitions(FastbootDevice* device); bool GetDeviceLockStatus(); From a5cee93b5add53afd25624f0f445f98bc8048b19 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Thu, 18 Feb 2021 16:03:59 -0800 Subject: [PATCH 04/13] fastbootd: add O_CLOEXEC/O_BINARY for OpenPartition O_CLOEXEC ensures fastbootd does not leak fds to the parent process (recovery). O_BINARY has no effect in Linux, but it is explicit that the file (partition) to read is a binary file. Test: pass Bug: 173654501 Change-Id: Idba922965ce666af1b7ee460ec7449fabd511c35 --- fastboot/device/utility.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp index 4c847987f..f9267e0eb 100644 --- a/fastboot/device/utility.cpp +++ b/fastboot/device/utility.cpp @@ -90,7 +90,8 @@ bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHan return false; } - int flags = O_EXCL | (read ? O_RDONLY : O_WRONLY); + int flags = (read ? O_RDONLY : O_WRONLY); + flags |= (O_EXCL | O_CLOEXEC | O_BINARY); unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags))); if (fd < 0) { PLOG(ERROR) << "Failed to open block device: " << handle->path(); From 60de969eff92ab29dbb6a617c01942246bf41619 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Wed, 17 Feb 2021 13:51:54 -0800 Subject: [PATCH 05/13] fastboot driver: fix message Test: pass Bug: 173654501 Change-Id: I7d5e7ce817a5ec4e3aba6b44bab3149683a46fdd --- fastboot/fastboot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index 38be934b9..97d87f64a 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -1019,7 +1019,7 @@ static void copy_boot_avb_footer(const std::string& partition, struct fastboot_b std::string partition_size_str; if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) { - die("cannot get boot partition size"); + die("cannot get partition size for %s", partition.c_str()); } partition_size_str = fb_fix_numeric_var(partition_size_str); From b10d067e0cbeacc117dc404769d9019794672637 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 22 Feb 2021 15:06:27 -0800 Subject: [PATCH 06/13] fastboot driver: Fix ownership of fd in fastboot_buffer. fastboot_buffer owns the fd. Make ImageSource::Open() returns a unique_fd and pass ownership all the way down to the fastboot_buffer to avoid leaking fds. Test: none Change-Id: I9e7e176fc1da74c532a86d0fdba0113bdc81a166 --- fastboot/fastboot.cpp | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index 97d87f64a..a26986fe9 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -114,7 +114,7 @@ struct fastboot_buffer { enum fb_buffer_type type; void* data; int64_t sz; - int fd; + unique_fd fd; int64_t image_size; }; @@ -640,14 +640,14 @@ static void delete_fbemarker_tmpdir(const std::string& dir) { } } -static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) { +static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) { unique_fd fd(make_temporary_fd(entry_name)); ZipEntry64 zip_entry; if (FindEntry(zip, entry_name, &zip_entry) != 0) { fprintf(stderr, "archive does not contain '%s'\n", entry_name); errno = ENOENT; - return -1; + return unique_fd(); } fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name, @@ -664,7 +664,7 @@ static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) { fprintf(stderr, " took %.3fs\n", now() - start); - return fd.release(); + return fd; } static void CheckRequirement(const std::string& cur_product, const std::string& var, @@ -893,7 +893,7 @@ static int64_t get_sparse_limit(int64_t size) { return 0; } -static bool load_buf_fd(int fd, struct fastboot_buffer* buf) { +static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) { int64_t sz = get_file_size(fd); if (sz == -1) { return false; @@ -918,7 +918,7 @@ static bool load_buf_fd(int fd, struct fastboot_buffer* buf) { } else { buf->type = FB_BUFFER_FD; buf->data = nullptr; - buf->fd = fd; + buf->fd = std::move(fd); buf->sz = sz; } @@ -941,7 +941,7 @@ static bool load_buf(const char* fname, struct fastboot_buffer* buf) { return false; } - return load_buf_fd(fd.release(), buf); + return load_buf_fd(std::move(fd), buf); } static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) { @@ -987,12 +987,11 @@ static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_bo data[flags_offset] |= 0x02; } - int fd = make_temporary_fd("vbmeta rewriting"); + unique_fd fd(make_temporary_fd("vbmeta rewriting")); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified vbmeta"); } - close(buf->fd); - buf->fd = fd; + buf->fd = std::move(fd); lseek(fd, 0, SEEK_SET); } @@ -1044,7 +1043,7 @@ static void copy_boot_avb_footer(const std::string& partition, struct fastboot_b return; } - int fd = make_temporary_fd("boot rewriting"); + unique_fd fd(make_temporary_fd("boot rewriting")); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified boot"); } @@ -1052,8 +1051,7 @@ static void copy_boot_avb_footer(const std::string& partition, struct fastboot_b if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) { die("Failed copying AVB footer in boot"); } - close(buf->fd); - buf->fd = fd; + buf->fd = std::move(fd); buf->sz = partition_size; lseek(fd, 0, SEEK_SET); } @@ -1310,7 +1308,7 @@ static void CancelSnapshotIfNeeded() { class ImageSource { public: virtual bool ReadFile(const std::string& name, std::vector* out) const = 0; - virtual int OpenFile(const std::string& name) const = 0; + virtual unique_fd OpenFile(const std::string& name) const = 0; }; class FlashAllTool { @@ -1428,8 +1426,8 @@ void FlashAllTool::CollectImages() { void FlashAllTool::FlashImages(const std::vector>& images) { for (const auto& [image, slot] : images) { fastboot_buffer buf; - int fd = source_.OpenFile(image->img_name); - if (fd < 0 || !load_buf_fd(fd, &buf)) { + unique_fd fd = source_.OpenFile(image->img_name); + if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) { if (image->optional_if_no_image) { continue; } @@ -1494,7 +1492,7 @@ class ZipImageSource final : public ImageSource { public: explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {} bool ReadFile(const std::string& name, std::vector* out) const override; - int OpenFile(const std::string& name) const override; + unique_fd OpenFile(const std::string& name) const override; private: ZipArchiveHandle zip_; @@ -1504,7 +1502,7 @@ bool ZipImageSource::ReadFile(const std::string& name, std::vector* out) c return UnzipToMemory(zip_, name, out); } -int ZipImageSource::OpenFile(const std::string& name) const { +unique_fd ZipImageSource::OpenFile(const std::string& name) const { return unzip_to_file(zip_, name.c_str()); } @@ -1524,7 +1522,7 @@ static void do_update(const char* filename, const std::string& slot_override, bo class LocalImageSource final : public ImageSource { public: bool ReadFile(const std::string& name, std::vector* out) const override; - int OpenFile(const std::string& name) const override; + unique_fd OpenFile(const std::string& name) const override; }; bool LocalImageSource::ReadFile(const std::string& name, std::vector* out) const { @@ -1535,9 +1533,9 @@ bool LocalImageSource::ReadFile(const std::string& name, std::vector* out) return ReadFileToVector(path, out); } -int LocalImageSource::OpenFile(const std::string& name) const { +unique_fd LocalImageSource::OpenFile(const std::string& name) const { auto path = find_item_given_name(name); - return open(path.c_str(), O_RDONLY | O_BINARY); + return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY))); } static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) { @@ -1656,7 +1654,7 @@ static void fb_perform_format( if (fd == -1) { die("Cannot open generated image: %s", strerror(errno)); } - if (!load_buf_fd(fd.release(), &buf)) { + if (!load_buf_fd(std::move(fd), &buf)) { die("Cannot read image: %s", strerror(errno)); } flash_buf(partition, &buf); From bbf374dbd7a9d1e8090d8a9cabcdaf181dee615c Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Wed, 17 Feb 2021 11:16:39 -0800 Subject: [PATCH 07/13] fastboot driver: add RunAndReadBuffer helper Refactor UploadInner and add a new RunAndReadBuffer helper function that handles the generic procedure of: 1. Sending a command 2. Receiving a DATA response with N bytes 3. Receiving another response Test: pass Bug: 173654501 Change-Id: I568bea127315e42d8a111c23602fc582e7bc935b --- fastboot/fastboot_driver.cpp | 52 +++++++++++++++++++++--------------- fastboot/fastboot_driver.h | 4 ++- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp index 8d534ea32..79305c414 100644 --- a/fastboot/fastboot_driver.cpp +++ b/fastboot/fastboot_driver.cpp @@ -297,41 +297,54 @@ RetCode FastBootDriver::Upload(const std::string& outfile, std::string* response return result; } -RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response, - std::vector* info) { +// This function executes cmd, then expect a "DATA" response with a number N, followed +// by N bytes, and another response. +// This is the common way for the device to send data to the driver used by upload and fetch. +RetCode FastBootDriver::RunAndReadBuffer( + const std::string& cmd, std::string* response, std::vector* info, + const std::function& write_fn) { RetCode ret; int dsize = 0; - if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) { - error_ = "Upload request failed: " + error_; + if ((ret = RawCommand(cmd, response, info, &dsize))) { + error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str()); return ret; } - if (!dsize) { - error_ = "Upload request failed, device reports 0 bytes available"; + if (dsize <= 0) { + error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available", + cmd.c_str(), dsize); return BAD_DEV_RESP; } - std::vector data; - data.resize(dsize); - - if ((ret = ReadBuffer(data))) { + std::vector data(dsize); + if ((ret = ReadBuffer(data.data(), data.size())) != SUCCESS) { return ret; } + if ((ret = write_fn(data.data(), data.size())) != SUCCESS) { + return ret; + } + return HandleResponse(response, info); +} +RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response, + std::vector* info) { std::ofstream ofs; ofs.open(outfile, std::ofstream::out | std::ofstream::binary); if (ofs.fail()) { error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str()); return IO_ERROR; } - ofs.write(data.data(), data.size()); - if (ofs.fail() || ofs.bad()) { - error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str()); - return IO_ERROR; - } + auto write_fn = [&](const char* data, uint64_t size) { + ofs.write(data, size); + if (ofs.fail() || ofs.bad()) { + error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str()); + return IO_ERROR; + } + return SUCCESS; + }; + RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn); ofs.close(); - - return HandleResponse(response, info); + return ret; } // Helpers @@ -524,11 +537,6 @@ RetCode FastBootDriver::SendBuffer(const void* buf, size_t size) { return SUCCESS; } -RetCode FastBootDriver::ReadBuffer(std::vector& buf) { - // Read the buffer - return ReadBuffer(buf.data(), buf.size()); -} - RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) { // Read the buffer ssize_t tmp = transport_->Read(buf, size); diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h index 72656322e..4d7f3c723 100644 --- a/fastboot/fastboot_driver.h +++ b/fastboot/fastboot_driver.h @@ -149,11 +149,13 @@ class FastBootDriver { RetCode SendBuffer(const std::vector& buf); RetCode SendBuffer(const void* buf, size_t size); - RetCode ReadBuffer(std::vector& buf); RetCode ReadBuffer(void* buf, size_t size); RetCode UploadInner(const std::string& outfile, std::string* response = nullptr, std::vector* info = nullptr); + RetCode RunAndReadBuffer(const std::string& cmd, std::string* response, + std::vector* info, + const std::function& write_fn); int SparseWriteCallback(std::vector& tpbuf, const char* data, size_t len); From 17d469bd579a83f5cad372dd8b1db47d3e874830 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Thu, 18 Feb 2021 15:15:46 -0800 Subject: [PATCH 08/13] fastboot driver: RunAndReadBuffer don't allocate too much mem Instead of allocating the buffer for the whole upload (which can be arbitrary number of bytes as the device determines), read 1 MiB at a time. Test: pass Bug: 173654501 Change-Id: Ib601b0341b10b7dccbb429cd21aad86a2d3bfda8 --- fastboot/Android.bp | 4 ++++ fastboot/fastboot_driver.cpp | 22 ++++++++++++++++------ fs_mgr/libstorage_literals/Android.bp | 5 +++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/fastboot/Android.bp b/fastboot/Android.bp index 8e98e9fd5..720810d05 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -76,6 +76,7 @@ cc_library_host_static { header_libs: [ "bootimg_headers", + "libstorage_literals_headers", ], export_header_lib_headers: [ @@ -275,6 +276,9 @@ cc_library_host_static { // Only version the final binaries use_version_lib: false, static_libs: ["libbuildversion"], + header_libs: [ + "libstorage_literals_headers", + ], generated_headers: ["platform_tools_version"], diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp index 79305c414..a516a9130 100644 --- a/fastboot/fastboot_driver.cpp +++ b/fastboot/fastboot_driver.cpp @@ -45,11 +45,13 @@ #include #include #include +#include #include "constants.h" #include "transport.h" using android::base::StringPrintf; +using namespace android::storage_literals; namespace fastboot { @@ -316,12 +318,20 @@ RetCode FastBootDriver::RunAndReadBuffer( return BAD_DEV_RESP; } - std::vector data(dsize); - if ((ret = ReadBuffer(data.data(), data.size())) != SUCCESS) { - return ret; - } - if ((ret = write_fn(data.data(), data.size())) != SUCCESS) { - return ret; + const uint64_t total_size = dsize; + const uint64_t buf_size = std::min(total_size, 1_MiB); + std::vector data(buf_size); + uint64_t current_offset = 0; + while (current_offset < total_size) { + uint64_t remaining = total_size - current_offset; + uint64_t chunk_size = std::min(buf_size, remaining); + if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) { + return ret; + } + if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) { + return ret; + } + current_offset += chunk_size; } return HandleResponse(response, info); } diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp index 635ca498c..5b0716851 100644 --- a/fs_mgr/libstorage_literals/Android.bp +++ b/fs_mgr/libstorage_literals/Android.bp @@ -8,4 +8,9 @@ cc_library_headers { host_supported: true, recovery_available: true, export_include_dirs: ["."], + target: { + windows: { + enabled: true, + }, + }, } From bcd27702071cb12df042883c4b120e9b5ebe5ecb Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 16 Feb 2021 19:37:32 -0800 Subject: [PATCH 09/13] fastboot driver: add fetch command in driver The `fastboot fetch` command is a wrapper around the fetch protocol. It: - getvar max-fetch-size - get the size of the partition - read the data by chunks, chunk size = max-fetch-size. The name of the partition is passed directly to the device (with the usual has-slot magic for flashing etc.) If we support fetching partitions other than vendor_boot in the future, no change in the driver is needed. Bug: 173654501 Test: manual Change-Id: Ie576eea668234df236b096a372e65c5e91c1e48c --- fastboot/device/commands.cpp | 21 +++++++-- fastboot/fastboot.cpp | 85 +++++++++++++++++++++++++++--------- fastboot/fastboot_driver.cpp | 25 +++++++++++ fastboot/fastboot_driver.h | 4 ++ 4 files changed, 111 insertions(+), 24 deletions(-) diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp index cf706357c..b72f3fe08 100644 --- a/fastboot/device/commands.cpp +++ b/fastboot/device/commands.cpp @@ -712,13 +712,20 @@ class PartitionFetcher { // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value // that FetchHandler should return. bool Open() { - partition_name_ = args_->at(1); - if (partition_name_ != "vendor_boot") { - ret_ = device_->WriteFail("Fetch is only allowed on vendor_boot"); + if (args_->size() < 2) { + ret_ = device_->WriteFail("Missing partition arg"); return false; } - if (!OpenPartition(device_, partition_name_, &handle_)) { + partition_name_ = args_->at(1); + if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) == + kAllowedPartitions.end()) { + ret_ = device_->WriteFail("Fetch is only allowed on [" + + android::base::Join(kAllowedPartitions, ", ") + "]"); + return false; + } + + if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) { ret_ = device_->WriteFail( android::base::StringPrintf("Cannot open %s", partition_name_.c_str())); return false; @@ -826,6 +833,12 @@ class PartitionFetcher { start_offset_, total_size_to_read_)); } + static constexpr std::array kAllowedPartitions{ + "vendor_boot", + "vendor_boot_a", + "vendor_boot_b", + }; + FastbootDevice* device_; const std::vector* args_ = nullptr; std::string partition_name_; diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index a26986fe9..62297ce7e 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -77,11 +77,13 @@ #include "usb.h" #include "util.h" +using android::base::borrowed_fd; using android::base::ReadFully; using android::base::Split; using android::base::Trim; using android::base::unique_fd; using namespace std::string_literals; +using namespace std::placeholders; static const char* serial = nullptr; @@ -414,6 +416,7 @@ static int show_help() { " snapshot-update merge On devices that support snapshot-based updates, finish\n" " an in-progress update if it is in the \"merging\"\n" " phase.\n" + " fetch PARTITION Fetch a partition image from the device." "\n" "boot image:\n" " boot KERNEL [RAMDISK [SECOND]]\n" @@ -466,7 +469,7 @@ static int show_help() { " --version Display version.\n" " --help, -h Show this message.\n" ); - // clang-format off + // clang-format on return 0; } @@ -851,24 +854,23 @@ static struct sparse_file** load_sparse_files(int fd, int64_t max_size) { return out_s; } -static int64_t get_target_sparse_limit() { - std::string max_download_size; - if (fb->GetVar("max-download-size", &max_download_size) != fastboot::SUCCESS || - max_download_size.empty()) { - verbose("target didn't report max-download-size"); +static uint64_t get_uint_var(const char* var_name) { + std::string value_str; + if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) { + verbose("target didn't report %s", var_name); return 0; } // Some bootloaders (angler, for example) send spurious whitespace too. - max_download_size = android::base::Trim(max_download_size); + value_str = android::base::Trim(value_str); - uint64_t limit; - if (!android::base::ParseUint(max_download_size, &limit)) { - fprintf(stderr, "couldn't parse max-download-size '%s'\n", max_download_size.c_str()); + uint64_t value; + if (!android::base::ParseUint(value_str, &value)) { + fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str()); return 0; } - if (limit > 0) verbose("target reported max download size of %" PRId64 " bytes", limit); - return limit; + if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value); + return value; } static int64_t get_sparse_limit(int64_t size) { @@ -877,7 +879,7 @@ static int64_t get_sparse_limit(int64_t size) { // Unlimited, so see what the target device's limit is. // TODO: shouldn't we apply this limit even if you've used -S? if (target_sparse_limit == -1) { - target_sparse_limit = get_target_sparse_limit(); + target_sparse_limit = static_cast(get_uint_var("max-download-size")); } if (target_sparse_limit > 0) { limit = target_sparse_limit; @@ -1011,21 +1013,28 @@ static std::string fb_fix_numeric_var(std::string var) { return var; } -static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) { - if (buf->sz < AVB_FOOTER_SIZE) { - return; - } - +static uint64_t get_partition_size(const std::string& partition) { std::string partition_size_str; if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) { die("cannot get partition size for %s", partition.c_str()); } partition_size_str = fb_fix_numeric_var(partition_size_str); - int64_t partition_size; - if (!android::base::ParseInt(partition_size_str, &partition_size)) { + uint64_t partition_size; + if (!android::base::ParseUint(partition_size_str, &partition_size)) { die("Couldn't parse partition size '%s'.", partition_size_str.c_str()); } + return partition_size; +} + +static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) { + if (buf->sz < AVB_FOOTER_SIZE) { + return; + } + + // If overflows and negative, it should be < buf->sz. + int64_t partition_size = static_cast(get_partition_size(partition)); + if (partition_size == buf->sz) { return; } @@ -1245,6 +1254,38 @@ static bool is_retrofit_device() { return android::base::StartsWith(value, "system_"); } +// Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch +// the full image. +static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) { + uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE); + if (fetch_size == 0) { + die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE); + } + uint64_t partition_size = get_partition_size(partition); + if (partition_size <= 0) { + die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size); + } + + uint64_t offset = 0; + while (offset < partition_size) { + uint64_t chunk_size = std::min(fetch_size, partition_size - offset); + if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) { + die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(), + offset, chunk_size); + } + offset += chunk_size; + } + return partition_size; +} + +static void do_fetch(const std::string& partition, const std::string& slot_override, + const std::string& outfile) { + unique_fd fd(TEMP_FAILURE_RETRY( + open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644))); + auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd)); + do_for_partitions(partition, slot_override, fetch, false /* force slot */); +} + static void do_flash(const char* pname, const char* fname) { struct fastboot_buffer buf; @@ -2167,6 +2208,10 @@ int FastBootTool::Main(int argc, char* argv[]) { syntax_error("expected: snapshot-update [cancel|merge]"); } fb->SnapshotUpdateCommand(arg); + } else if (command == FB_CMD_FETCH) { + std::string partition = next_arg(&args); + std::string outfile = next_arg(&args); + do_fetch(partition, slot_override, outfile); } else { syntax_error("unknown command %s", command.c_str()); } diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp index a516a9130..14ee78555 100644 --- a/fastboot/fastboot_driver.cpp +++ b/fastboot/fastboot_driver.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -357,6 +359,29 @@ RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* res return ret; } +RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd, + int64_t offset, int64_t size, std::string* response, + std::vector* info) { + prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")", + partition.c_str(), offset, size)); + std::string cmd = FB_CMD_FETCH ":" + partition; + if (offset >= 0) { + cmd += android::base::StringPrintf(":0x%08" PRIx64, offset); + if (size >= 0) { + cmd += android::base::StringPrintf(":0x%08" PRIx64, size); + } + } + RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) { + if (!android::base::WriteFully(fd, data, size)) { + error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno)); + return IO_ERROR; + } + return SUCCESS; + }); + epilog_(ret); + return ret; +} + // Helpers void FastBootDriver::SetInfoCallback(std::function info) { info_ = info; diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h index 4d7f3c723..f1c094f28 100644 --- a/fastboot/fastboot_driver.h +++ b/fastboot/fastboot_driver.h @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -106,6 +107,9 @@ class FastBootDriver { std::vector* info = nullptr); RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr, std::vector* info = nullptr); + RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd, + int64_t offset = -1, int64_t size = -1, std::string* response = nullptr, + std::vector* info = nullptr); /* HIGHER LEVEL COMMANDS -- Composed of the commands above */ RetCode FlashPartition(const std::string& partition, const std::vector& data); From 6d7da63014a8d451b02246180712d61fb7508ff9 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 23 Feb 2021 12:15:51 -0800 Subject: [PATCH 10/13] fastboot driver: Allow colon in partition name when flashing If fastboot flash vendor_boot:default, only call has-slot on the first token, and append slot to the first token only. Test: fastboot flash vendor_boot:default Bug: 173654501 Change-Id: I8ff7b3a0bafb964792ab8a788d64d14bc47ae83d --- fastboot/fastboot.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index 62297ce7e..dacac97ed 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -1192,8 +1192,10 @@ static void do_for_partition(const std::string& part, const std::string& slot, const std::function& func, bool force_slot) { std::string has_slot; std::string current_slot; + // |part| can be vendor_boot:default. Append slot to the first token. + auto part_tokens = android::base::Split(part, ":"); - if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) { + if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) { /* If has-slot is not supported, the answer is no. */ has_slot = "no"; } @@ -1203,14 +1205,15 @@ static void do_for_partition(const std::string& part, const std::string& slot, if (current_slot == "") { die("Failed to identify current slot"); } - func(part + "_" + current_slot); + part_tokens[0] += "_" + current_slot; } else { - func(part + '_' + slot); + part_tokens[0] += "_" + slot; } + func(android::base::Join(part_tokens, ":")); } else { if (force_slot && slot != "") { - fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n", - part.c_str(), slot.c_str()); + fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n", + part_tokens[0].c_str(), slot.c_str()); } func(part); } @@ -1224,10 +1227,13 @@ static void do_for_partition(const std::string& part, const std::string& slot, static void do_for_partitions(const std::string& part, const std::string& slot, const std::function& func, bool force_slot) { std::string has_slot; + // |part| can be vendor_boot:default. Query has-slot on the first token only. + auto part_tokens = android::base::Split(part, ":"); if (slot == "all") { - if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) { - die("Could not check if partition %s has slot %s", part.c_str(), slot.c_str()); + if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) { + die("Could not check if partition %s has slot %s", part_tokens[0].c_str(), + slot.c_str()); } if (has_slot == "yes") { for (int i=0; i < get_slot_count(); i++) { @@ -1287,6 +1293,7 @@ static void do_fetch(const std::string& partition, const std::string& slot_overr } static void do_flash(const char* pname, const char* fname) { + verbose("Do flash %s %s", pname, fname); struct fastboot_buffer buf; if (!load_buf(fname, &buf)) { From e71fe2441ad62793b15812a34f7b863dccefabdc Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 22 Feb 2021 15:00:15 -0800 Subject: [PATCH 11/13] 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(); +} From 2a7a14bfc464e0302b391a2551d1b25c683b160e Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Fri, 5 Mar 2021 19:27:29 -0800 Subject: [PATCH 12/13] fuzzy_fastboot: Add conformance test for getvar:max-fetch-size. Test: run against bootloader Test: run against fastbootd Bug: 173654501 Change-Id: Ide38eee6b801110fef52f0f9213c038a72c775f2 --- fastboot/fuzzy_fastboot/main.cpp | 39 +++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp index 34ab32c12..6dbcf4a50 100644 --- a/fastboot/fuzzy_fastboot/main.cpp +++ b/fastboot/fuzzy_fastboot/main.cpp @@ -349,22 +349,35 @@ TEST_F(Conformance, GetVarBattVoltageOk) { EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'"; } +void AssertHexUint32(const std::string& name, const std::string& var) { + ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string"; + // This must start with 0x + ASSERT_FALSE(isspace(var.front())) + << "getvar:" << name << " responded with a string with leading whitespace"; + ASSERT_FALSE(var.compare(0, 2, "0x")) + << "getvar:" << name << " responded with a string that does not start with 0x..."; + int64_t size = strtoll(var.c_str(), nullptr, 16); + ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name; + // At most 32-bits + ASSERT_LE(size, std::numeric_limits::max()) + << "getvar:" << name << " must fit in a uint32_t"; + ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4) + << "getvar:" << name << " responded with too large of string: " + var; +} + TEST_F(Conformance, GetVarDownloadSize) { std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; - EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string"; - // This must start with 0x - EXPECT_FALSE(isspace(var.front())) - << "getvar:max-download-size responded with a string with leading whitespace"; - EXPECT_FALSE(var.compare(0, 2, "0x")) - << "getvar:max-download-size responded with a string that does not start with 0x..."; - int64_t size = strtoll(var.c_str(), nullptr, 16); - EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size"; - // At most 32-bits - EXPECT_LE(size, std::numeric_limits::max()) - << "getvar:max-download-size must fit in a uint32_t"; - EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) - << "getvar:max-download-size responded with too large of string: " + var; + AssertHexUint32("max-download-size", var); +} + +// If fetch is supported, getvar:max-fetch-size must return a hex string. +TEST_F(Conformance, GetVarFetchSize) { + std::string var; + if (SUCCESS != fb->GetVar("max-fetch-size", &var)) { + GTEST_SKIP() << "getvar:max-fetch-size failed"; + } + AssertHexUint32("max-fetch-size", var); } TEST_F(Conformance, GetVarAll) { From f09c1efd0af0fa35d751153f074a0e83b01e306b Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Fri, 5 Mar 2021 18:57:22 -0800 Subject: [PATCH 13/13] fuzzy_fastboot: Add tests for fetch:vendor_boot Test: run test against bootloader Test: run test against fastbootd Bug: 173654501 Change-Id: Ia3182b4f4390048139d2cafe9b1654b6fb92eb7b --- fastboot/fuzzy_fastboot/main.cpp | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp index 6dbcf4a50..b6beaf93a 100644 --- a/fastboot/fuzzy_fastboot/main.cpp +++ b/fastboot/fuzzy_fastboot/main.cpp @@ -43,8 +43,10 @@ #include #include +#include #include #include +#include #include #include @@ -669,6 +671,33 @@ TEST_F(UnlockPermissions, DownloadFlash) { EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode"; } +// If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*. +TEST_F(UnlockPermissions, FetchVendorBoot) { + std::string var; + uint64_t fetch_size; + if (fb->GetVar("max-fetch-size", &var) != SUCCESS) { + GTEST_SKIP() << "This test is skipped because fetch is not supported."; + } + ASSERT_FALSE(var.empty()); + ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer"; + std::vector> parts; + EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; + for (const auto& [partition, partition_size] : parts) { + if (!android::base::StartsWith(partition, "vendor_boot")) continue; + TemporaryFile fetched; + + uint64_t offset = 0; + while (offset < partition_size) { + uint64_t chunk_size = std::min(fetch_size, partition_size - offset); + auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size); + ASSERT_EQ(fastboot::RetCode::SUCCESS, ret) + << "Unable to fetch " << partition << " (offset=" << offset + << ", size=" << chunk_size << ")"; + offset += chunk_size; + } + } +} + TEST_F(LockPermissions, DownloadFlash) { std::vector buf{'a', 'o', 's', 'p'}; EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode"; @@ -730,6 +759,16 @@ TEST_F(LockPermissions, Boot) { EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL"; } +TEST_F(LockPermissions, FetchVendorBoot) { + std::vector> parts; + EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; + for (const auto& [partition, _] : parts) { + TemporaryFile fetched; + ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL) + << "fetch:" << partition << ":0:0 did not fail in locked mode"; + } +} + TEST_F(Fuzz, DownloadSize) { std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";