/* * Copyright (C) 2018 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 "apex_file.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apex_key.h" #include "apexd_utils.h" #include "string_log.h" using android::base::EndsWith; using android::base::ReadFullyAtOffset; using android::base::StartsWith; using android::base::unique_fd; using google::protobuf::util::MessageDifferencer; namespace android { namespace apex { namespace { constexpr const char* kImageFilename = "apex_payload.img"; constexpr const char* kManifestFilename = "apex_manifest.json"; constexpr const char* kBundledPublicKeyFilename = "apex_pubkey"; #ifdef DEBUG_ALLOW_BUNDLED_KEY constexpr const bool kDebugAllowBundledKey = true; #else constexpr const bool kDebugAllowBundledKey = false; #endif } // namespace // Tests if /manifest.json file exists. bool isFlattenedApex(const std::string& path) { struct stat buf; const std::string manifest = path + "/" + kManifestFilename; if (stat(manifest.c_str(), &buf) != 0) { if (errno == ENOENT) { return false; } // If the APEX is there but not a flatttened apex, the final component // of path will be a file, and stat will complain that it's not a directory. // We are OK with that to avoid two stat calls. if (errno != ENOTDIR) { PLOG(ERROR) << "Failed to stat " << path; } return false; } if (!S_ISREG(buf.st_mode)) { return false; } return true; } StatusOr ApexFile::Open(const std::string& path) { bool flattened; int32_t image_offset; size_t image_size; std::string manifest_content; std::string pubkey; if (isFlattenedApex(path)) { flattened = true; image_offset = 0; image_size = 0; const std::string manifest_path = path + "/" + kManifestFilename; if (!android::base::ReadFileToString(manifest_path, &manifest_content)) { std::string err = StringLog() << "Failed to read manifest file: " << manifest_path; return StatusOr::MakeError(err); } // TODO(b/124115379) don't read public key from flattened APEX when // we no longer have APEX tests on devices with flattened APEXes. const std::string pubkey_path = path + "/" + kBundledPublicKeyFilename; if (access(pubkey_path.c_str(), F_OK) == 0) { if (!android::base::ReadFileToString(pubkey_path, &pubkey)) { std::string err = StringLog() << "Failed to read pubkey file: " << pubkey_path; return StatusOr::MakeError(err); } } } else { flattened = false; ZipArchiveHandle handle; auto handle_guard = android::base::make_scope_guard([&handle] { CloseArchive(handle); }); int ret = OpenArchive(path.c_str(), &handle); if (ret < 0) { std::string err = StringLog() << "Failed to open package " << path << ": " << ErrorCodeString(ret); return StatusOr::MakeError(err); } // Locate the mountable image within the zipfile and store offset and size. ZipEntry entry; ret = FindEntry(handle, ZipString(kImageFilename), &entry); if (ret < 0) { std::string err = StringLog() << "Could not find entry \"" << kImageFilename << "\" in package " << path << ": " << ErrorCodeString(ret); return StatusOr::MakeError(err); } image_offset = entry.offset; image_size = entry.uncompressed_length; ret = FindEntry(handle, ZipString(kManifestFilename), &entry); if (ret < 0) { std::string err = StringLog() << "Could not find entry \"" << kManifestFilename << "\" in package " << path << ": " << ErrorCodeString(ret); return StatusOr::MakeError(err); } uint32_t length = entry.uncompressed_length; manifest_content.resize(length, '\0'); ret = ExtractToMemory(handle, &entry, reinterpret_cast(&(manifest_content)[0]), length); if (ret != 0) { std::string err = StringLog() << "Failed to extract manifest from package " << path << ": " << ErrorCodeString(ret); return StatusOr::MakeError(err); } ret = FindEntry(handle, ZipString(kBundledPublicKeyFilename), &entry); if (ret >= 0) { LOG(VERBOSE) << "Found bundled key in package " << path; length = entry.uncompressed_length; pubkey.resize(length, '\0'); ret = ExtractToMemory(handle, &entry, reinterpret_cast(&(pubkey)[0]), length); if (ret != 0) { std::string err = StringLog() << "Failed to extract public key from package " << path << ": " << ErrorCodeString(ret); return StatusOr::MakeError(err); } } } StatusOr manifest = ParseManifest(manifest_content); if (!manifest.Ok()) { return StatusOr::MakeError(manifest.ErrorMessage()); } ApexFile apexFile(path, flattened, image_offset, image_size, *manifest, pubkey); return StatusOr(std::move(apexFile)); } // AVB-related code. namespace { static constexpr const char* kApexKeyProp = "apex.key"; static constexpr int kVbMetaMaxSize = 64 * 1024; std::string bytes_to_hex(const uint8_t* bytes, size_t bytes_len) { std::ostringstream s; s << std::hex << std::setfill('0'); for (size_t i = 0; i < bytes_len; i++) { s << std::setw(2) << static_cast(bytes[i]); } return s.str(); } std::string getSalt(const AvbHashtreeDescriptor& desc, const uint8_t* trailingData) { const uint8_t* desc_salt = trailingData + desc.partition_name_len; return bytes_to_hex(desc_salt, desc.salt_len); } std::string getDigest(const AvbHashtreeDescriptor& desc, const uint8_t* trailingData) { const uint8_t* desc_digest = trailingData + desc.partition_name_len + desc.salt_len; return bytes_to_hex(desc_digest, desc.root_digest_len); } StatusOr> getAvbFooter(const ApexFile& apex, const unique_fd& fd) { std::array footer_data; auto footer = std::make_unique(); // The AVB footer is located in the last part of the image off_t offset = apex.GetImageSize() + apex.GetImageOffset() - AVB_FOOTER_SIZE; int ret = lseek(fd, offset, SEEK_SET); if (ret == -1) { return StatusOr>::MakeError( PStringLog() << "Couldn't seek to AVB footer"); } ret = read(fd, footer_data.data(), AVB_FOOTER_SIZE); if (ret != AVB_FOOTER_SIZE) { return StatusOr>::MakeError( PStringLog() << "Couldn't read AVB footer"); } if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_data.data(), footer.get())) { return StatusOr>::MakeError( StringLog() << "AVB footer verification failed."); } LOG(VERBOSE) << "AVB footer verification successful."; return StatusOr>(std::move(footer)); } Status verifyPublicKey(const uint8_t* key, size_t length, std::string public_key_content) { if (public_key_content.length() != length || memcmp(&public_key_content[0], key, length) != 0) { return Status::Fail("Failed to compare the bundled public key with key"); } return Status::Success(); } StatusOr getPublicKeyName(const ApexFile& apex, const uint8_t* data, size_t length) { size_t keyNameLen; const char* keyName = avb_property_lookup(data, length, kApexKeyProp, strlen(kApexKeyProp), &keyNameLen); if (keyName == nullptr || keyNameLen == 0) { return StatusOr::MakeError( StringLog() << "Cannot find prop '" << kApexKeyProp << "' from " << apex.GetPath()); } if (keyName != apex.GetManifest().name()) { return StatusOr::MakeError( StringLog() << "Key mismatch: apex name is '" << apex.GetManifest().name() << "'" << " but key name is '" << keyName << "'"); } return StatusOr(keyName); } Status verifyVbMetaSignature(const ApexFile& apex, const uint8_t* data, size_t length) { const uint8_t* pk; size_t pk_len; AvbVBMetaVerifyResult res; res = avb_vbmeta_image_verify(data, length, &pk, &pk_len); switch (res) { case AVB_VBMETA_VERIFY_RESULT_OK: break; case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED: case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH: case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH: return Status::Fail(StringLog() << "Error verifying " << apex.GetPath() << ": " << avb_vbmeta_verify_result_to_string(res)); case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER: return Status::Fail(StringLog() << "Error verifying " << apex.GetPath() << ": " << "invalid vbmeta header"); case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION: return Status::Fail(StringLog() << "Error verifying " << apex.GetPath() << ": " << "unsupported version"); default: return Status::Fail("Unknown vmbeta_image_verify return value"); } StatusOr key_name = getPublicKeyName(apex, data, length); if (!key_name.Ok()) { return key_name.ErrorStatus(); } StatusOr public_key = getApexKey(*key_name); Status st; if (public_key.Ok()) { // TODO(b/115718846) // We need to decide whether we need rollback protection, and whether // we can use the rollback protection provided by libavb. st = verifyPublicKey(pk, pk_len, *public_key); } else if (kDebugAllowBundledKey) { // Failing to find the matching public key in the built-in partitions // is a hard error for non-debuggable build. For debuggable builds, // the public key bundled in the APEX itself is used as a fallback. LOG(WARNING) << "Verifying " << apex.GetPath() << " with the bundled key"; st = verifyPublicKey(pk, pk_len, apex.GetBundledPublicKey()); } else { return public_key.ErrorStatus(); } if (st.Ok()) { LOG(VERBOSE) << apex.GetPath() << ": public key matches."; return st; } return Status::Fail(StringLog() << "Error verifying " << apex.GetPath() << ": " << "couldn't verify public key: " << st.ErrorMessage()); } StatusOr> verifyVbMeta(const ApexFile& apex, const unique_fd& fd, const AvbFooter& footer) { if (footer.vbmeta_size > kVbMetaMaxSize) { return StatusOr>::MakeError( "VbMeta size in footer exceeds kVbMetaMaxSize."); } off_t offset = apex.GetImageOffset() + footer.vbmeta_offset; std::unique_ptr vbmeta_buf(new uint8_t[footer.vbmeta_size]); if (!ReadFullyAtOffset(fd, vbmeta_buf.get(), footer.vbmeta_size, offset)) { return StatusOr>::MakeError( PStringLog() << "Couldn't read AVB meta-data"); } Status st = verifyVbMetaSignature(apex, vbmeta_buf.get(), footer.vbmeta_size); if (!st.Ok()) { return StatusOr>::MakeError(st.ErrorMessage()); } return StatusOr>(std::move(vbmeta_buf)); } StatusOr findDescriptor(uint8_t* vbmeta_data, size_t vbmeta_size) { const AvbDescriptor** descriptors; size_t num_descriptors; descriptors = avb_descriptor_get_all(vbmeta_data, vbmeta_size, &num_descriptors); // avb_descriptor_get_all() returns an internally allocated array // of pointers and it needs to be avb_free()ed after using it. auto guard = android::base::ScopeGuard(std::bind(avb_free, descriptors)); for (size_t i = 0; i < num_descriptors; i++) { AvbDescriptor desc; if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) { return StatusOr::MakeError( "Couldn't validate AvbDescriptor."); } if (desc.tag != AVB_DESCRIPTOR_TAG_HASHTREE) { // Ignore other descriptors continue; } return StatusOr( (const AvbHashtreeDescriptor*)descriptors[i]); } return StatusOr::MakeError( "Couldn't find any AVB hashtree descriptors."); } StatusOr> verifyDescriptor( const AvbHashtreeDescriptor* desc) { auto verifiedDesc = std::make_unique(); if (!avb_hashtree_descriptor_validate_and_byteswap(desc, verifiedDesc.get())) { return StatusOr>::MakeError( "Couldn't validate AvbDescriptor."); } return StatusOr>( std::move(verifiedDesc)); } } // namespace StatusOr ApexFile::VerifyApexVerity() const { ApexVerityData verityData; unique_fd fd(open(GetPath().c_str(), O_RDONLY | O_CLOEXEC)); if (fd.get() == -1) { return StatusOr::MakeError(PStringLog() << "Failed to open " << GetPath()); } StatusOr> footer = getAvbFooter(*this, fd); if (!footer.Ok()) { return StatusOr::MakeError(footer.ErrorMessage()); } StatusOr> vbmeta_data = verifyVbMeta(*this, fd, **footer); if (!vbmeta_data.Ok()) { return StatusOr::MakeError(vbmeta_data.ErrorMessage()); } StatusOr descriptor = findDescriptor(vbmeta_data->get(), (*footer)->vbmeta_size); if (!descriptor.Ok()) { return StatusOr::MakeError(descriptor.ErrorMessage()); } StatusOr> verifiedDescriptor = verifyDescriptor(*descriptor); if (!verifiedDescriptor.Ok()) { return StatusOr::MakeError( verifiedDescriptor.ErrorMessage()); } verityData.desc = std::move(*verifiedDescriptor); // This area is now safe to access, because we just verified it const uint8_t* trailingData = (const uint8_t*)*descriptor + sizeof(AvbHashtreeDescriptor); verityData.salt = getSalt(*verityData.desc, trailingData); verityData.root_digest = getDigest(*verityData.desc, trailingData); return StatusOr(std::move(verityData)); } Status ApexFile::VerifyManifestMatches(const std::string& mount_path) const { std::string manifest_content; const std::string manifest_path = mount_path + "/" + kManifestFilename; if (!android::base::ReadFileToString(manifest_path, &manifest_content)) { std::string err = StringLog() << "Failed to read manifest file: " << manifest_path; return Status::Fail(err); } StatusOr verifiedManifest = ParseManifest(manifest_content); if (!verifiedManifest.Ok()) { return Status::Fail(verifiedManifest.ErrorMessage()); } if (!MessageDifferencer::Equals(manifest_, *verifiedManifest)) { return Status::Fail( "Manifest inside filesystem does not match manifest outside it"); } return Status::Success(); } StatusOr> FindApexes( const std::vector& paths) { using StatusT = StatusOr>; std::vector result; for (const auto& path : paths) { auto exist = PathExists(path); if (!exist.Ok()) { return StatusT::MakeError(exist.ErrorStatus()); } if (!*exist) continue; const auto& apexes = FindApexFilesByName(path, isPathForBuiltinApexes(path)); if (!apexes.Ok()) { return apexes; } result.insert(result.end(), apexes->begin(), apexes->end()); } return StatusOr>(result); } StatusOr> FindApexFilesByName(const std::string& path, bool include_dirs) { auto filter_fn = [include_dirs](const std::filesystem::directory_entry& entry) { std::error_code ec; if (entry.is_regular_file(ec) && EndsWith(entry.path().filename().string(), kApexPackageSuffix)) { return true; // APEX file, take. } // Directory and asked to scan for flattened. return entry.is_directory(ec) && include_dirs; }; return ReadDir(path, filter_fn); } bool isPathForBuiltinApexes(const std::string& path) { for (const auto& dir : kApexPackageBuiltinDirs) { if (StartsWith(path, dir)) { return true; } } return false; } } // namespace apex } // namespace android