diff --git a/Android.bp b/Android.bp index afa03371..10f6c797 100644 --- a/Android.bp +++ b/Android.bp @@ -175,6 +175,7 @@ cc_library_static { "fsck_unshare_blocks.cpp", "fuse_sdcard_provider.cpp", "install.cpp", + "package.cpp", "recovery.cpp", "roots.cpp", ], diff --git a/install.cpp b/install.cpp index a7868fa1..05f9af7a 100644 --- a/install.cpp +++ b/install.cpp @@ -51,6 +51,7 @@ #include "otautil/paths.h" #include "otautil/sysutil.h" #include "otautil/thermalutil.h" +#include "package.h" #include "private/install.h" #include "roots.h" #include "ui.h" @@ -585,34 +586,29 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo } } - MemMapping map; - if (!map.MapFile(path)) { - LOG(ERROR) << "failed to map file"; + auto package = Package::CreateMemoryPackage( + path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + if (!package) { log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure)); return INSTALL_CORRUPT; } // Verify package. - if (!verify_package(map.addr, map.length)) { + if (!verify_package(package.get())) { log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); return INSTALL_CORRUPT; } // Try to open the package. - ZipArchiveHandle zip; - int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip); - if (err != 0) { - LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err); + ZipArchiveHandle zip = package->GetZipArchiveHandle(); + if (!zip) { log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); - - CloseArchive(zip); return INSTALL_CORRUPT; } // Additionally verify the compatibility of the package. if (!verify_package_compatibility(zip)) { log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure)); - CloseArchive(zip); return INSTALL_CORRUPT; } @@ -626,7 +622,6 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo ui->SetEnableReboot(true); ui->Print("\n"); - CloseArchive(zip); return result; } @@ -705,7 +700,7 @@ int install_package(const std::string& path, bool* wipe_cache, bool needs_mount, return result; } -bool verify_package(const unsigned char* package_data, size_t package_size) { +bool verify_package(Package* package) { static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip"; std::vector loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE); if (loaded_keys.empty()) { @@ -717,8 +712,7 @@ bool verify_package(const unsigned char* package_data, size_t package_size) { // Verify package. ui->Print("Verifying update package...\n"); auto t0 = std::chrono::system_clock::now(); - int err = verify_file(package_data, package_size, loaded_keys, - std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + int err = verify_file(package, loaded_keys); std::chrono::duration duration = std::chrono::system_clock::now() - t0; ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); if (err != VERIFY_SUCCESS) { diff --git a/install.h b/install.h index 20a2b23e..7b6267bf 100644 --- a/install.h +++ b/install.h @@ -25,6 +25,8 @@ #include +#include "package.h" + enum InstallResult { INSTALL_SUCCESS, INSTALL_ERROR, @@ -46,9 +48,9 @@ enum class OtaType { int install_package(const std::string& package, bool* wipe_cache, bool needs_mount, int retry_count); -// Verify the package by ota keys. Return true if the package is verified successfully, -// otherwise return false. -bool verify_package(const unsigned char* package_data, size_t package_size); +// Verifies the package by ota keys. Returns true if the package is verified successfully, +// otherwise returns false. +bool verify_package(Package* package); // Reads meta data file of the package; parses each line in the format "key=value"; and writes the // result to |metadata|. Return true if succeed, otherwise return false. diff --git a/package.cpp b/package.cpp new file mode 100644 index 00000000..d4042785 --- /dev/null +++ b/package.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "package.h" + +#include + +#include +#include +#include + +#include "otautil/error_code.h" +#include "otautil/sysutil.h" + +// This class wraps the package in memory, i.e. a memory mapped package, or a package loaded +// to a string/vector. +class MemoryPackage : public Package { + public: + // Constructs the class from a file. We will memory maps the file later. + MemoryPackage(const std::string& path, std::unique_ptr map, + const std::function& set_progress); + + // Constructs the class from the package bytes in |content|. + MemoryPackage(std::vector content, const std::function& set_progress); + + ~MemoryPackage() override; + + // Memory maps the package file if necessary. Initializes the start address and size of the + // package. + uint64_t GetPackageSize() const override { + return package_size_; + } + + bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override; + + ZipArchiveHandle GetZipArchiveHandle() override; + + bool UpdateHashAtOffset(const std::vector& hashers, uint64_t start, + uint64_t length) override; + + private: + const uint8_t* addr_; // Start address of the package in memory. + uint64_t package_size_; // Package size in bytes. + + // The memory mapped package. + std::unique_ptr map_; + // A copy of the package content, valid only if we create the class with the exact bytes of + // the package. + std::vector package_content_; + // The physical path to the package, empty if we create the class with the package content. + std::string path_; + + // The ZipArchiveHandle of the package. + ZipArchiveHandle zip_handle_; +}; + +// TODO(xunchang) Implement the PackageFromFd. + +void Package::SetProgress(float progress) { + if (set_progress_) { + set_progress_(progress); + } +} + +std::unique_ptr Package::CreateMemoryPackage( + const std::string& path, const std::function& set_progress) { + std::unique_ptr mmap = std::make_unique(); + if (!mmap->MapFile(path)) { + LOG(ERROR) << "failed to map file"; + return nullptr; + } + + return std::make_unique(path, std::move(mmap), set_progress); +} + +std::unique_ptr Package::CreateMemoryPackage( + std::vector content, const std::function& set_progress) { + return std::make_unique(std::move(content), set_progress); +} + +MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr map, + const std::function& set_progress) + : map_(std::move(map)), path_(path), zip_handle_(nullptr) { + addr_ = map_->addr; + package_size_ = map_->length; + set_progress_ = set_progress; +} + +MemoryPackage::MemoryPackage(std::vector content, + const std::function& set_progress) + : package_content_(std::move(content)), zip_handle_(nullptr) { + CHECK(!package_content_.empty()); + addr_ = package_content_.data(); + package_size_ = package_content_.size(); + set_progress_ = set_progress; +} + +MemoryPackage::~MemoryPackage() { + if (zip_handle_) { + CloseArchive(zip_handle_); + } +} + +bool MemoryPackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) { + if (byte_count > package_size_ || offset > package_size_ - byte_count) { + LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count + << ", total package_size: " << package_size_; + return false; + } + memcpy(buffer, addr_ + offset, byte_count); + return true; +} + +bool MemoryPackage::UpdateHashAtOffset(const std::vector& hashers, + uint64_t start, uint64_t length) { + if (length > package_size_ || start > package_size_ - length) { + LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length + << ", total package_size: " << package_size_; + return false; + } + + for (const auto& hasher : hashers) { + hasher(addr_ + start, length); + } + return true; +} + +ZipArchiveHandle MemoryPackage::GetZipArchiveHandle() { + if (zip_handle_) { + return zip_handle_; + } + + if (auto err = OpenArchiveFromMemory(const_cast(addr_), package_size_, path_.c_str(), + &zip_handle_); + err != 0) { + LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err); + return nullptr; + } + + return zip_handle_; +} diff --git a/package.h b/package.h new file mode 100644 index 00000000..5eef9c96 --- /dev/null +++ b/package.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include + +#include "verifier.h" + +// This class serves as a wrapper for an OTA update package. It aims to provide the common +// interface for both packages loaded in memory and packages read from fd. +class Package : public VerifierInterface { + public: + virtual ~Package() = default; + + // Opens the package as a zip file and returns the ZipArchiveHandle. + virtual ZipArchiveHandle GetZipArchiveHandle() = 0; + + // Updates the progress in fraction during package verification. + void SetProgress(float progress) override; + + static std::unique_ptr CreateMemoryPackage( + const std::string& path, const std::function& set_progress); + + static std::unique_ptr CreateMemoryPackage( + std::vector content, const std::function& set_progress); + + protected: + // An optional function to update the progress. + std::function set_progress_; +}; diff --git a/recovery.cpp b/recovery.cpp index 90c84878..2c9f9de6 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -64,6 +64,7 @@ #include "otautil/error_code.h" #include "otautil/paths.h" #include "otautil/sysutil.h" +#include "package.h" #include "roots.h" #include "screen_ui.h" #include "ui.h" @@ -517,13 +518,15 @@ static std::string ReadWipePackage(size_t wipe_package_size) { // 1. verify the package. // 2. check metadata (ota-type, pre-device and serial number if having one). static bool CheckWipePackage(const std::string& wipe_package) { - if (!verify_package(reinterpret_cast(wipe_package.data()), - wipe_package.size())) { + auto package = Package::CreateMemoryPackage( + std::vector(wipe_package.begin(), wipe_package.end()), nullptr); + + if (!package || !verify_package(package.get())) { LOG(ERROR) << "Failed to verify package"; return false; } - // Extract metadata + // TODO(xunchang) get zip archive from package. ZipArchiveHandle zip; if (auto err = OpenArchiveFromMemory(const_cast(static_cast(&wipe_package[0])), diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index 9fcaa0b7..c26d76d7 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -36,6 +36,7 @@ #include "common/test_constants.h" #include "otautil/sysutil.h" +#include "package.h" #include "verifier.h" using namespace std::string_literals; @@ -47,15 +48,22 @@ static void LoadKeyFromFile(const std::string& file_name, Certificate* cert) { std::vector(testkey_string.begin(), testkey_string.end()), cert)); } +static void VerifyFile(const std::string& content, const std::vector& keys, + int expected) { + auto package = + Package::CreateMemoryPackage(std::vector(content.begin(), content.end()), nullptr); + ASSERT_NE(nullptr, package); + + ASSERT_EQ(expected, verify_file(package.get(), keys)); +} + static void VerifyPackageWithCertificates(const std::string& name, const std::vector& certs) { - std::string package = from_testdata_base(name); - MemMapping memmap; - if (!memmap.MapFile(package)) { - FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; - } + std::string path = from_testdata_base(name); + auto package = Package::CreateMemoryPackage(path, nullptr); + ASSERT_NE(nullptr, package); - ASSERT_EQ(VERIFY_SUCCESS, verify_file(memmap.addr, memmap.length, certs)); + ASSERT_EQ(VERIFY_SUCCESS, verify_file(package.get(), certs)); } static void VerifyPackageWithSingleCertificate(const std::string& name, Certificate&& cert) { @@ -231,20 +239,19 @@ class VerifierTest : public testing::TestWithParam> { protected: void SetUp() override { std::vector args = GetParam(); - std::string package = from_testdata_base(args[0]); - if (!memmap.MapFile(package)) { - FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; - } + std::string path = from_testdata_base(args[0]); + package_ = Package::CreateMemoryPackage(path, nullptr); + ASSERT_NE(nullptr, package_); for (auto it = ++args.cbegin(); it != args.cend(); ++it) { std::string public_key_file = from_testdata_base("testkey_" + *it + ".x509.pem"); - certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); - LoadKeyFromFile(public_key_file, &certs.back()); + certs_.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(public_key_file, &certs_.back()); } } - MemMapping memmap; - std::vector certs; + std::unique_ptr package_; + std::vector certs_; }; class VerifierSuccessTest : public VerifierTest { @@ -264,9 +271,7 @@ TEST(VerifierTest, BadPackage_AlteredFooter) { // Alter the footer. package[package.size() - 5] = '\x05'; - ASSERT_EQ(VERIFY_FAILURE, - verify_file(reinterpret_cast(package.data()), package.size(), - certs)); + VerifyFile(package, certs, VERIFY_FAILURE); } TEST(VerifierTest, BadPackage_AlteredContent) { @@ -281,15 +286,11 @@ TEST(VerifierTest, BadPackage_AlteredContent) { // Alter the content. std::string altered1(package); altered1[50] += 1; - ASSERT_EQ(VERIFY_FAILURE, - verify_file(reinterpret_cast(altered1.data()), altered1.size(), - certs)); + VerifyFile(altered1, certs, VERIFY_FAILURE); std::string altered2(package); altered2[10] += 1; - ASSERT_EQ(VERIFY_FAILURE, - verify_file(reinterpret_cast(altered2.data()), altered2.size(), - certs)); + VerifyFile(altered2, certs, VERIFY_FAILURE); } TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) { @@ -299,16 +300,15 @@ TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) { // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369). std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s; - ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast(package.data()), - package.size(), certs)); + VerifyFile(package, certs, VERIFY_FAILURE); } TEST_P(VerifierSuccessTest, VerifySucceed) { - ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS); + ASSERT_EQ(VERIFY_SUCCESS, verify_file(package_.get(), certs_)); } TEST_P(VerifierFailureTest, VerifyFailure) { - ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE); + ASSERT_EQ(VERIFY_FAILURE, verify_file(package_.get(), certs_)); } INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest, diff --git a/verifier.cpp b/verifier.cpp index 44bd4e18..b6c3895c 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -117,19 +117,9 @@ static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len, return true; } -/* - * Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. Verifies - * that it matches one of the given public keys. A callback function can be optionally provided for - * posting the progress. - * - * Returns VERIFY_SUCCESS or VERIFY_FAILURE (if any error is encountered or no key matches the - * signature). - */ -int verify_file(const unsigned char* addr, size_t length, const std::vector& keys, - const std::function& set_progress) { - if (set_progress) { - set_progress(0.0); - } +int verify_file(VerifierInterface* package, const std::vector& keys) { + CHECK(package); + package->SetProgress(0.0); // An archive with a whole-file signature will end in six bytes: // @@ -140,13 +130,18 @@ int verify_file(const unsigned char* addr, size_t length, const std::vectorGetPackageSize(); if (length < FOOTER_SIZE) { LOG(ERROR) << "not big enough to contain footer"; return VERIFY_FAILURE; } - const unsigned char* footer = addr + length - FOOTER_SIZE; + uint8_t footer[FOOTER_SIZE]; + if (!package->ReadFullyAtOffset(footer, FOOTER_SIZE, length - FOOTER_SIZE)) { + LOG(ERROR) << "Failed to read footer"; + return VERIFY_FAILURE; + } if (footer[2] != 0xff || footer[3] != 0xff) { LOG(ERROR) << "footer is wrong"; @@ -182,9 +177,13 @@ int verify_file(const unsigned char* addr, size_t length, const std::vectorReadFullyAtOffset(eocd, eocd_size, length - eocd_size)) { + LOG(ERROR) << "Failed to read EOCD of " << eocd_size << " bytes"; + return VERIFY_FAILURE; + } // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06. if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) { @@ -216,24 +215,29 @@ int verify_file(const unsigned char* addr, size_t length, const std::vector hashers; + if (need_sha1) { + hashers.emplace_back( + std::bind(&SHA1_Update, &sha1_ctx, std::placeholders::_1, std::placeholders::_2)); + } + if (need_sha256) { + hashers.emplace_back( + std::bind(&SHA256_Update, &sha256_ctx, std::placeholders::_1, std::placeholders::_2)); + } + double frac = -1.0; - size_t so_far = 0; + uint64_t so_far = 0; while (so_far < signed_len) { - // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a - // 1196MiB full OTA and 60% for an 89MiB incremental OTA. - // http://b/28135231. - size_t size = std::min(signed_len - so_far, 16 * MiB); + // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a 1196MiB full OTA and + // 60% for an 89MiB incremental OTA. http://b/28135231. + uint64_t read_size = std::min(signed_len - so_far, 16 * MiB); + package->UpdateHashAtOffset(hashers, so_far, read_size); + so_far += read_size; - if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); - if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); - so_far += size; - - if (set_progress) { - double f = so_far / (double)signed_len; - if (f > frac + 0.02 || size == so_far) { - set_progress(f); - frac = f; - } + double f = so_far / static_cast(signed_len); + if (f > frac + 0.02 || read_size == so_far) { + package->SetProgress(f); + frac = f; } } diff --git a/verifier.h b/verifier.h index df9a4b64..b80096d4 100644 --- a/verifier.h +++ b/verifier.h @@ -27,6 +27,8 @@ #include #include +using HasherUpdateCallback = std::function; + struct RSADeleter { void operator()(RSA* rsa) const { RSA_free(rsa); @@ -61,14 +63,28 @@ struct Certificate { std::unique_ptr ec; }; -/* - * 'addr' and 'length' define an update package file that has been loaded (or mmap'ed, or - * whatever) into memory. Verifies that the file is signed and the signature matches one of the - * given keys. It optionally accepts a callback function for posting the progress to. Returns one - * of the constants of VERIFY_SUCCESS and VERIFY_FAILURE. - */ -int verify_file(const unsigned char* addr, size_t length, const std::vector& keys, - const std::function& set_progress = nullptr); +class VerifierInterface { + public: + virtual ~VerifierInterface() = default; + + // Returns the package size in bytes. + virtual uint64_t GetPackageSize() const = 0; + + // Reads |byte_count| data starting from |offset|, and puts the result in |buffer|. + virtual bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) = 0; + + // Updates the hash contexts for |length| bytes data starting from |start|. + virtual bool UpdateHashAtOffset(const std::vector& hashers, uint64_t start, + uint64_t length) = 0; + + // Updates the progress in fraction during package verification. + virtual void SetProgress(float progress) = 0; +}; + +// Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. +// Verifies that it matches one of the given public keys. Returns VERIFY_SUCCESS or +// VERIFY_FAILURE (if any error is encountered or no key matches the signature). +int verify_file(VerifierInterface* package, const std::vector& keys); // Checks that the RSA key has a modulus of 2048 bits long, and public exponent is 3 or 65537. bool CheckRSAKey(const std::unique_ptr& rsa);