| /* |
| * 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. |
| */ |
| |
| #define TRACE_TAG ADB |
| |
| #include "apk_archive.h" |
| |
| #include <inttypes.h> |
| |
| #include "adb_trace.h" |
| #include "sysdeps.h" |
| |
| #include <android-base/endian.h> |
| #include <android-base/mapped_file.h> |
| |
| #include <openssl/md5.h> |
| |
| constexpr uint16_t kCompressStored = 0; |
| |
| // mask value that signifies that the entry has a DD |
| static const uint32_t kGPBDDFlagMask = 0x0008; |
| |
| namespace { |
| struct FileRegion { |
| FileRegion(borrowed_fd fd, off64_t offset, size_t length) |
| : mapped_(android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), offset, length, |
| PROT_READ)) { |
| if (mapped_ != nullptr) { |
| return; |
| } |
| |
| // Mapped file failed, falling back to pread. |
| buffer_.resize(length); |
| if (auto err = adb_pread(fd.get(), buffer_.data(), length, offset); size_t(err) != length) { |
| fprintf(stderr, "Unable to read %lld bytes at offset %" PRId64 " \n", |
| static_cast<long long>(length), offset); |
| buffer_.clear(); |
| return; |
| } |
| } |
| |
| const char* data() const { return mapped_ ? mapped_->data() : buffer_.data(); } |
| size_t size() const { return mapped_ ? mapped_->size() : buffer_.size(); } |
| |
| private: |
| FileRegion() = default; |
| DISALLOW_COPY_AND_ASSIGN(FileRegion); |
| |
| std::unique_ptr<android::base::MappedFile> mapped_; |
| std::string buffer_; |
| }; |
| } // namespace |
| |
| using com::android::fastdeploy::APKDump; |
| |
| ApkArchive::ApkArchive(const std::string& path) : path_(path), size_(0) { |
| fd_.reset(adb_open(path_.c_str(), O_RDONLY)); |
| if (fd_ == -1) { |
| fprintf(stderr, "Unable to open file '%s'\n", path_.c_str()); |
| return; |
| } |
| |
| struct stat st; |
| if (stat(path_.c_str(), &st) == -1) { |
| fprintf(stderr, "Unable to stat file '%s'\n", path_.c_str()); |
| return; |
| } |
| size_ = st.st_size; |
| } |
| |
| ApkArchive::~ApkArchive() {} |
| |
| APKDump ApkArchive::ExtractMetadata() { |
| D("ExtractMetadata"); |
| if (!ready()) { |
| return {}; |
| } |
| |
| Location cdLoc = GetCDLocation(); |
| if (!cdLoc.valid) { |
| return {}; |
| } |
| |
| APKDump dump; |
| dump.set_absolute_path(path_); |
| dump.set_cd(ReadMetadata(cdLoc)); |
| |
| Location sigLoc = GetSignatureLocation(cdLoc.offset); |
| if (sigLoc.valid) { |
| dump.set_signature(ReadMetadata(sigLoc)); |
| } |
| return dump; |
| } |
| |
| off_t ApkArchive::FindEndOfCDRecord() const { |
| constexpr int endOfCDSignature = 0x06054b50; |
| constexpr off_t endOfCDMinSize = 22; |
| constexpr off_t endOfCDMaxSize = 65535 + endOfCDMinSize; |
| |
| auto sizeToRead = std::min(size_, endOfCDMaxSize); |
| auto readOffset = size_ - sizeToRead; |
| FileRegion mapped(fd_, readOffset, sizeToRead); |
| |
| // Start scanning from the end |
| auto* start = mapped.data(); |
| auto* cursor = start + mapped.size() - sizeof(endOfCDSignature); |
| |
| // Search for End of Central Directory record signature. |
| while (cursor >= start) { |
| if (*(int32_t*)cursor == endOfCDSignature) { |
| return readOffset + (cursor - start); |
| } |
| cursor--; |
| } |
| return -1; |
| } |
| |
| ApkArchive::Location ApkArchive::FindCDRecord(const char* cursor) { |
| struct ecdr_t { |
| int32_t signature; |
| uint16_t diskNumber; |
| uint16_t numDisk; |
| uint16_t diskEntries; |
| uint16_t numEntries; |
| uint32_t crSize; |
| uint32_t offsetToCdHeader; |
| uint16_t commentSize; |
| uint8_t comment[0]; |
| } __attribute__((packed)); |
| ecdr_t* header = (ecdr_t*)cursor; |
| |
| Location location; |
| location.offset = header->offsetToCdHeader; |
| location.size = header->crSize; |
| location.valid = true; |
| return location; |
| } |
| |
| ApkArchive::Location ApkArchive::GetCDLocation() { |
| constexpr off_t cdEntryHeaderSizeBytes = 22; |
| Location location; |
| |
| // Find End of Central Directory Record |
| off_t eocdRecord = FindEndOfCDRecord(); |
| if (eocdRecord < 0) { |
| fprintf(stderr, "Unable to find End of Central Directory record in file '%s'\n", |
| path_.c_str()); |
| return location; |
| } |
| |
| // Find Central Directory Record |
| FileRegion mapped(fd_, eocdRecord, cdEntryHeaderSizeBytes); |
| location = FindCDRecord(mapped.data()); |
| if (!location.valid) { |
| fprintf(stderr, "Unable to find Central Directory File Header in file '%s'\n", |
| path_.c_str()); |
| return location; |
| } |
| |
| return location; |
| } |
| |
| ApkArchive::Location ApkArchive::GetSignatureLocation(off_t cdRecordOffset) { |
| Location location; |
| |
| // Signature constants. |
| constexpr off_t endOfSignatureSize = 24; |
| off_t signatureOffset = cdRecordOffset - endOfSignatureSize; |
| if (signatureOffset < 0) { |
| fprintf(stderr, "Unable to find signature in file '%s'\n", path_.c_str()); |
| return location; |
| } |
| |
| FileRegion mapped(fd_, signatureOffset, endOfSignatureSize); |
| |
| uint64_t signatureSize = *(uint64_t*)mapped.data(); |
| auto* signature = mapped.data() + sizeof(signatureSize); |
| // Check if there is a v2/v3 Signature block here. |
| if (memcmp(signature, "APK Sig Block 42", 16)) { |
| return location; |
| } |
| |
| // This is likely a signature block. |
| location.size = signatureSize; |
| location.offset = cdRecordOffset - location.size - 8; |
| location.valid = true; |
| |
| return location; |
| } |
| |
| std::string ApkArchive::ReadMetadata(Location loc) const { |
| FileRegion mapped(fd_, loc.offset, loc.size); |
| return {mapped.data(), mapped.size()}; |
| } |
| |
| size_t ApkArchive::ParseCentralDirectoryRecord(const char* input, size_t size, std::string* md5Hash, |
| int64_t* localFileHeaderOffset, int64_t* dataSize) { |
| // A structure representing the fixed length fields for a single |
| // record in the central directory of the archive. In addition to |
| // the fixed length fields listed here, each central directory |
| // record contains a variable length "file_name" and "extra_field" |
| // whose lengths are given by |file_name_length| and |extra_field_length| |
| // respectively. |
| static constexpr int kCDFileHeaderMagic = 0x02014b50; |
| struct CentralDirectoryRecord { |
| // The start of record signature. Must be |kSignature|. |
| uint32_t record_signature; |
| // Source tool version. Top byte gives source OS. |
| uint16_t version_made_by; |
| // Tool version. Ignored by this implementation. |
| uint16_t version_needed; |
| // The "general purpose bit flags" for this entry. The only |
| // flag value that we currently check for is the "data descriptor" |
| // flag. |
| uint16_t gpb_flags; |
| // The compression method for this entry, one of |kCompressStored| |
| // and |kCompressDeflated|. |
| uint16_t compression_method; |
| // The file modification time and date for this entry. |
| uint16_t last_mod_time; |
| uint16_t last_mod_date; |
| // The CRC-32 checksum for this entry. |
| uint32_t crc32; |
| // The compressed size (in bytes) of this entry. |
| uint32_t compressed_size; |
| // The uncompressed size (in bytes) of this entry. |
| uint32_t uncompressed_size; |
| // The length of the entry file name in bytes. The file name |
| // will appear immediately after this record. |
| uint16_t file_name_length; |
| // The length of the extra field info (in bytes). This data |
| // will appear immediately after the entry file name. |
| uint16_t extra_field_length; |
| // The length of the entry comment (in bytes). This data will |
| // appear immediately after the extra field. |
| uint16_t comment_length; |
| // The start disk for this entry. Ignored by this implementation). |
| uint16_t file_start_disk; |
| // File attributes. Ignored by this implementation. |
| uint16_t internal_file_attributes; |
| // File attributes. For archives created on Unix, the top bits are the |
| // mode. |
| uint32_t external_file_attributes; |
| // The offset to the local file header for this entry, from the |
| // beginning of this archive. |
| uint32_t local_file_header_offset; |
| |
| private: |
| CentralDirectoryRecord() = default; |
| DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord); |
| } __attribute__((packed)); |
| |
| const CentralDirectoryRecord* cdr; |
| if (size < sizeof(*cdr)) { |
| return 0; |
| } |
| |
| auto begin = input; |
| cdr = reinterpret_cast<const CentralDirectoryRecord*>(begin); |
| if (cdr->record_signature != kCDFileHeaderMagic) { |
| fprintf(stderr, "Invalid Central Directory Record signature\n"); |
| return 0; |
| } |
| auto end = begin + sizeof(*cdr) + cdr->file_name_length + cdr->extra_field_length + |
| cdr->comment_length; |
| |
| uint8_t md5Digest[MD5_DIGEST_LENGTH]; |
| MD5((const unsigned char*)begin, end - begin, md5Digest); |
| md5Hash->assign((const char*)md5Digest, sizeof(md5Digest)); |
| |
| *localFileHeaderOffset = cdr->local_file_header_offset; |
| *dataSize = (cdr->compression_method == kCompressStored) ? cdr->uncompressed_size |
| : cdr->compressed_size; |
| |
| return end - begin; |
| } |
| |
| size_t ApkArchive::CalculateLocalFileEntrySize(int64_t localFileHeaderOffset, |
| int64_t dataSize) const { |
| // The local file header for a given entry. This duplicates information |
| // present in the central directory of the archive. It is an error for |
| // the information here to be different from the central directory |
| // information for a given entry. |
| static constexpr int kLocalFileHeaderMagic = 0x04034b50; |
| struct LocalFileHeader { |
| // The local file header signature, must be |kSignature|. |
| uint32_t lfh_signature; |
| // Tool version. Ignored by this implementation. |
| uint16_t version_needed; |
| // The "general purpose bit flags" for this entry. The only |
| // flag value that we currently check for is the "data descriptor" |
| // flag. |
| uint16_t gpb_flags; |
| // The compression method for this entry, one of |kCompressStored| |
| // and |kCompressDeflated|. |
| uint16_t compression_method; |
| // The file modification time and date for this entry. |
| uint16_t last_mod_time; |
| uint16_t last_mod_date; |
| // The CRC-32 checksum for this entry. |
| uint32_t crc32; |
| // The compressed size (in bytes) of this entry. |
| uint32_t compressed_size; |
| // The uncompressed size (in bytes) of this entry. |
| uint32_t uncompressed_size; |
| // The length of the entry file name in bytes. The file name |
| // will appear immediately after this record. |
| uint16_t file_name_length; |
| // The length of the extra field info (in bytes). This data |
| // will appear immediately after the entry file name. |
| uint16_t extra_field_length; |
| |
| private: |
| LocalFileHeader() = default; |
| DISALLOW_COPY_AND_ASSIGN(LocalFileHeader); |
| } __attribute__((packed)); |
| static constexpr int kLocalFileHeaderSize = sizeof(LocalFileHeader); |
| CHECK(ready()) << path_; |
| |
| const LocalFileHeader* lfh; |
| if (localFileHeaderOffset + kLocalFileHeaderSize > size_) { |
| fprintf(stderr, |
| "Invalid Local File Header offset in file '%s' at offset %lld, file size %lld\n", |
| path_.c_str(), static_cast<long long>(localFileHeaderOffset), |
| static_cast<long long>(size_)); |
| return 0; |
| } |
| |
| FileRegion lfhMapped(fd_, localFileHeaderOffset, sizeof(LocalFileHeader)); |
| lfh = reinterpret_cast<const LocalFileHeader*>(lfhMapped.data()); |
| if (lfh->lfh_signature != kLocalFileHeaderMagic) { |
| fprintf(stderr, "Invalid Local File Header signature in file '%s' at offset %lld\n", |
| path_.c_str(), static_cast<long long>(localFileHeaderOffset)); |
| return 0; |
| } |
| |
| // The *optional* data descriptor start signature. |
| static constexpr int kOptionalDataDescriptorMagic = 0x08074b50; |
| struct DataDescriptor { |
| // CRC-32 checksum of the entry. |
| uint32_t crc32; |
| // Compressed size of the entry. |
| uint32_t compressed_size; |
| // Uncompressed size of the entry. |
| uint32_t uncompressed_size; |
| |
| private: |
| DataDescriptor() = default; |
| DISALLOW_COPY_AND_ASSIGN(DataDescriptor); |
| } __attribute__((packed)); |
| static constexpr int kDataDescriptorSize = sizeof(DataDescriptor); |
| |
| off_t ddOffset = localFileHeaderOffset + kLocalFileHeaderSize + lfh->file_name_length + |
| lfh->extra_field_length + dataSize; |
| int64_t ddSize = 0; |
| |
| int64_t localDataSize; |
| if (lfh->gpb_flags & kGPBDDFlagMask) { |
| // There is trailing data descriptor. |
| const DataDescriptor* dd; |
| |
| if (ddOffset + int(sizeof(uint32_t)) > size_) { |
| fprintf(stderr, |
| "Error reading trailing data descriptor signature in file '%s' at offset %lld, " |
| "file size %lld\n", |
| path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_)); |
| return 0; |
| } |
| |
| FileRegion ddMapped(fd_, ddOffset, sizeof(uint32_t) + sizeof(DataDescriptor)); |
| |
| off_t localDDOffset = 0; |
| if (kOptionalDataDescriptorMagic == *(uint32_t*)ddMapped.data()) { |
| ddOffset += sizeof(uint32_t); |
| localDDOffset += sizeof(uint32_t); |
| ddSize += sizeof(uint32_t); |
| } |
| if (ddOffset + kDataDescriptorSize > size_) { |
| fprintf(stderr, |
| "Error reading trailing data descriptor in file '%s' at offset %lld, file size " |
| "%lld\n", |
| path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_)); |
| return 0; |
| } |
| |
| dd = reinterpret_cast<const DataDescriptor*>(ddMapped.data() + localDDOffset); |
| localDataSize = (lfh->compression_method == kCompressStored) ? dd->uncompressed_size |
| : dd->compressed_size; |
| ddSize += sizeof(*dd); |
| } else { |
| localDataSize = (lfh->compression_method == kCompressStored) ? lfh->uncompressed_size |
| : lfh->compressed_size; |
| } |
| if (localDataSize != dataSize) { |
| fprintf(stderr, |
| "Data sizes mismatch in file '%s' at offset %lld, CDr: %lld vs LHR/DD: %lld\n", |
| path_.c_str(), static_cast<long long>(localFileHeaderOffset), |
| static_cast<long long>(dataSize), static_cast<long long>(localDataSize)); |
| return 0; |
| } |
| |
| return kLocalFileHeaderSize + lfh->file_name_length + lfh->extra_field_length + dataSize + |
| ddSize; |
| } |