• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define TRACE_TAG ADB
18 
19 #include "apk_archive.h"
20 
21 #include <inttypes.h>
22 
23 #include "adb_trace.h"
24 #include "sysdeps.h"
25 
26 #include <android-base/endian.h>
27 #include <android-base/mapped_file.h>
28 
29 #include <openssl/md5.h>
30 
31 constexpr uint16_t kCompressStored = 0;
32 
33 // mask value that signifies that the entry has a DD
34 static const uint32_t kGPBDDFlagMask = 0x0008;
35 
36 namespace {
37 struct FileRegion {
FileRegion__anonccaeb35d0111::FileRegion38     FileRegion(borrowed_fd fd, off64_t offset, size_t length)
39         : mapped_(android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), offset, length,
40                                                           PROT_READ)) {
41         if (mapped_ != nullptr) {
42             return;
43         }
44 
45         // Mapped file failed, falling back to pread.
46         buffer_.resize(length);
47         if (auto err = adb_pread(fd.get(), buffer_.data(), length, offset); size_t(err) != length) {
48             fprintf(stderr, "Unable to read %lld bytes at offset %" PRId64 " \n",
49                     static_cast<long long>(length), offset);
50             buffer_.clear();
51             return;
52         }
53     }
54 
data__anonccaeb35d0111::FileRegion55     const char* data() const { return mapped_ ? mapped_->data() : buffer_.data(); }
size__anonccaeb35d0111::FileRegion56     size_t size() const { return mapped_ ? mapped_->size() : buffer_.size(); }
57 
58   private:
59     FileRegion() = default;
60     DISALLOW_COPY_AND_ASSIGN(FileRegion);
61 
62     std::unique_ptr<android::base::MappedFile> mapped_;
63     std::string buffer_;
64 };
65 }  // namespace
66 
67 using com::android::fastdeploy::APKDump;
68 
ApkArchive(const std::string & path)69 ApkArchive::ApkArchive(const std::string& path) : path_(path), size_(0) {
70     fd_.reset(adb_open(path_.c_str(), O_RDONLY));
71     if (fd_ == -1) {
72         fprintf(stderr, "Unable to open file '%s'\n", path_.c_str());
73         return;
74     }
75 
76     struct stat st;
77     if (stat(path_.c_str(), &st) == -1) {
78         fprintf(stderr, "Unable to stat file '%s'\n", path_.c_str());
79         return;
80     }
81     size_ = st.st_size;
82 }
83 
~ApkArchive()84 ApkArchive::~ApkArchive() {}
85 
ExtractMetadata()86 APKDump ApkArchive::ExtractMetadata() {
87     D("ExtractMetadata");
88     if (!ready()) {
89         return {};
90     }
91 
92     Location cdLoc = GetCDLocation();
93     if (!cdLoc.valid) {
94         return {};
95     }
96 
97     APKDump dump;
98     dump.set_absolute_path(path_);
99     dump.set_cd(ReadMetadata(cdLoc));
100 
101     Location sigLoc = GetSignatureLocation(cdLoc.offset);
102     if (sigLoc.valid) {
103         dump.set_signature(ReadMetadata(sigLoc));
104     }
105     return dump;
106 }
107 
FindEndOfCDRecord() const108 off_t ApkArchive::FindEndOfCDRecord() const {
109     constexpr int endOfCDSignature = 0x06054b50;
110     constexpr off_t endOfCDMinSize = 22;
111     constexpr off_t endOfCDMaxSize = 65535 + endOfCDMinSize;
112 
113     auto sizeToRead = std::min(size_, endOfCDMaxSize);
114     auto readOffset = size_ - sizeToRead;
115     FileRegion mapped(fd_, readOffset, sizeToRead);
116 
117     // Start scanning from the end
118     auto* start = mapped.data();
119     auto* cursor = start + mapped.size() - sizeof(endOfCDSignature);
120 
121     // Search for End of Central Directory record signature.
122     while (cursor >= start) {
123         if (*(int32_t*)cursor == endOfCDSignature) {
124             return readOffset + (cursor - start);
125         }
126         cursor--;
127     }
128     return -1;
129 }
130 
FindCDRecord(const char * cursor)131 ApkArchive::Location ApkArchive::FindCDRecord(const char* cursor) {
132     struct ecdr_t {
133         int32_t signature;
134         uint16_t diskNumber;
135         uint16_t numDisk;
136         uint16_t diskEntries;
137         uint16_t numEntries;
138         uint32_t crSize;
139         uint32_t offsetToCdHeader;
140         uint16_t commentSize;
141         uint8_t comment[0];
142     } __attribute__((packed));
143     ecdr_t* header = (ecdr_t*)cursor;
144 
145     Location location;
146     location.offset = header->offsetToCdHeader;
147     location.size = header->crSize;
148     location.valid = true;
149     return location;
150 }
151 
GetCDLocation()152 ApkArchive::Location ApkArchive::GetCDLocation() {
153     constexpr off_t cdEntryHeaderSizeBytes = 22;
154     Location location;
155 
156     // Find End of Central Directory Record
157     off_t eocdRecord = FindEndOfCDRecord();
158     if (eocdRecord < 0) {
159         fprintf(stderr, "Unable to find End of Central Directory record in file '%s'\n",
160                 path_.c_str());
161         return location;
162     }
163 
164     // Find Central Directory Record
165     FileRegion mapped(fd_, eocdRecord, cdEntryHeaderSizeBytes);
166     location = FindCDRecord(mapped.data());
167     if (!location.valid) {
168         fprintf(stderr, "Unable to find Central Directory File Header in file '%s'\n",
169                 path_.c_str());
170         return location;
171     }
172 
173     return location;
174 }
175 
GetSignatureLocation(off_t cdRecordOffset)176 ApkArchive::Location ApkArchive::GetSignatureLocation(off_t cdRecordOffset) {
177     Location location;
178 
179     // Signature constants.
180     constexpr off_t endOfSignatureSize = 24;
181     off_t signatureOffset = cdRecordOffset - endOfSignatureSize;
182     if (signatureOffset < 0) {
183         fprintf(stderr, "Unable to find signature in file '%s'\n", path_.c_str());
184         return location;
185     }
186 
187     FileRegion mapped(fd_, signatureOffset, endOfSignatureSize);
188 
189     uint64_t signatureSize = *(uint64_t*)mapped.data();
190     auto* signature = mapped.data() + sizeof(signatureSize);
191     // Check if there is a v2/v3 Signature block here.
192     if (memcmp(signature, "APK Sig Block 42", 16)) {
193         return location;
194     }
195 
196     // This is likely a signature block.
197     location.size = signatureSize;
198     location.offset = cdRecordOffset - location.size - 8;
199     location.valid = true;
200 
201     return location;
202 }
203 
ReadMetadata(Location loc) const204 std::string ApkArchive::ReadMetadata(Location loc) const {
205     FileRegion mapped(fd_, loc.offset, loc.size);
206     return {mapped.data(), mapped.size()};
207 }
208 
ParseCentralDirectoryRecord(const char * input,size_t size,std::string * md5Hash,int64_t * localFileHeaderOffset,int64_t * dataSize)209 size_t ApkArchive::ParseCentralDirectoryRecord(const char* input, size_t size, std::string* md5Hash,
210                                                int64_t* localFileHeaderOffset, int64_t* dataSize) {
211     // A structure representing the fixed length fields for a single
212     // record in the central directory of the archive. In addition to
213     // the fixed length fields listed here, each central directory
214     // record contains a variable length "file_name" and "extra_field"
215     // whose lengths are given by |file_name_length| and |extra_field_length|
216     // respectively.
217     static constexpr int kCDFileHeaderMagic = 0x02014b50;
218     struct CentralDirectoryRecord {
219         // The start of record signature. Must be |kSignature|.
220         uint32_t record_signature;
221         // Source tool version. Top byte gives source OS.
222         uint16_t version_made_by;
223         // Tool version. Ignored by this implementation.
224         uint16_t version_needed;
225         // The "general purpose bit flags" for this entry. The only
226         // flag value that we currently check for is the "data descriptor"
227         // flag.
228         uint16_t gpb_flags;
229         // The compression method for this entry, one of |kCompressStored|
230         // and |kCompressDeflated|.
231         uint16_t compression_method;
232         // The file modification time and date for this entry.
233         uint16_t last_mod_time;
234         uint16_t last_mod_date;
235         // The CRC-32 checksum for this entry.
236         uint32_t crc32;
237         // The compressed size (in bytes) of this entry.
238         uint32_t compressed_size;
239         // The uncompressed size (in bytes) of this entry.
240         uint32_t uncompressed_size;
241         // The length of the entry file name in bytes. The file name
242         // will appear immediately after this record.
243         uint16_t file_name_length;
244         // The length of the extra field info (in bytes). This data
245         // will appear immediately after the entry file name.
246         uint16_t extra_field_length;
247         // The length of the entry comment (in bytes). This data will
248         // appear immediately after the extra field.
249         uint16_t comment_length;
250         // The start disk for this entry. Ignored by this implementation).
251         uint16_t file_start_disk;
252         // File attributes. Ignored by this implementation.
253         uint16_t internal_file_attributes;
254         // File attributes. For archives created on Unix, the top bits are the
255         // mode.
256         uint32_t external_file_attributes;
257         // The offset to the local file header for this entry, from the
258         // beginning of this archive.
259         uint32_t local_file_header_offset;
260 
261       private:
262         CentralDirectoryRecord() = default;
263         DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
264     } __attribute__((packed));
265 
266     const CentralDirectoryRecord* cdr;
267     if (size < sizeof(*cdr)) {
268         return 0;
269     }
270 
271     auto begin = input;
272     cdr = reinterpret_cast<const CentralDirectoryRecord*>(begin);
273     if (cdr->record_signature != kCDFileHeaderMagic) {
274         fprintf(stderr, "Invalid Central Directory Record signature\n");
275         return 0;
276     }
277     auto end = begin + sizeof(*cdr) + cdr->file_name_length + cdr->extra_field_length +
278                cdr->comment_length;
279 
280     uint8_t md5Digest[MD5_DIGEST_LENGTH];
281     MD5((const unsigned char*)begin, end - begin, md5Digest);
282     md5Hash->assign((const char*)md5Digest, sizeof(md5Digest));
283 
284     *localFileHeaderOffset = cdr->local_file_header_offset;
285     *dataSize = (cdr->compression_method == kCompressStored) ? cdr->uncompressed_size
286                                                              : cdr->compressed_size;
287 
288     return end - begin;
289 }
290 
CalculateLocalFileEntrySize(int64_t localFileHeaderOffset,int64_t dataSize) const291 size_t ApkArchive::CalculateLocalFileEntrySize(int64_t localFileHeaderOffset,
292                                                int64_t dataSize) const {
293     // The local file header for a given entry. This duplicates information
294     // present in the central directory of the archive. It is an error for
295     // the information here to be different from the central directory
296     // information for a given entry.
297     static constexpr int kLocalFileHeaderMagic = 0x04034b50;
298     struct LocalFileHeader {
299         // The local file header signature, must be |kSignature|.
300         uint32_t lfh_signature;
301         // Tool version. Ignored by this implementation.
302         uint16_t version_needed;
303         // The "general purpose bit flags" for this entry. The only
304         // flag value that we currently check for is the "data descriptor"
305         // flag.
306         uint16_t gpb_flags;
307         // The compression method for this entry, one of |kCompressStored|
308         // and |kCompressDeflated|.
309         uint16_t compression_method;
310         // The file modification time and date for this entry.
311         uint16_t last_mod_time;
312         uint16_t last_mod_date;
313         // The CRC-32 checksum for this entry.
314         uint32_t crc32;
315         // The compressed size (in bytes) of this entry.
316         uint32_t compressed_size;
317         // The uncompressed size (in bytes) of this entry.
318         uint32_t uncompressed_size;
319         // The length of the entry file name in bytes. The file name
320         // will appear immediately after this record.
321         uint16_t file_name_length;
322         // The length of the extra field info (in bytes). This data
323         // will appear immediately after the entry file name.
324         uint16_t extra_field_length;
325 
326       private:
327         LocalFileHeader() = default;
328         DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
329     } __attribute__((packed));
330     static constexpr int kLocalFileHeaderSize = sizeof(LocalFileHeader);
331     CHECK(ready()) << path_;
332 
333     const LocalFileHeader* lfh;
334     if (localFileHeaderOffset + kLocalFileHeaderSize > size_) {
335         fprintf(stderr,
336                 "Invalid Local File Header offset in file '%s' at offset %lld, file size %lld\n",
337                 path_.c_str(), static_cast<long long>(localFileHeaderOffset),
338                 static_cast<long long>(size_));
339         return 0;
340     }
341 
342     FileRegion lfhMapped(fd_, localFileHeaderOffset, sizeof(LocalFileHeader));
343     lfh = reinterpret_cast<const LocalFileHeader*>(lfhMapped.data());
344     if (lfh->lfh_signature != kLocalFileHeaderMagic) {
345         fprintf(stderr, "Invalid Local File Header signature in file '%s' at offset %lld\n",
346                 path_.c_str(), static_cast<long long>(localFileHeaderOffset));
347         return 0;
348     }
349 
350     // The *optional* data descriptor start signature.
351     static constexpr int kOptionalDataDescriptorMagic = 0x08074b50;
352     struct DataDescriptor {
353         // CRC-32 checksum of the entry.
354         uint32_t crc32;
355         // Compressed size of the entry.
356         uint32_t compressed_size;
357         // Uncompressed size of the entry.
358         uint32_t uncompressed_size;
359 
360       private:
361         DataDescriptor() = default;
362         DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
363     } __attribute__((packed));
364     static constexpr int kDataDescriptorSize = sizeof(DataDescriptor);
365 
366     off_t ddOffset = localFileHeaderOffset + kLocalFileHeaderSize + lfh->file_name_length +
367                      lfh->extra_field_length + dataSize;
368     int64_t ddSize = 0;
369 
370     int64_t localDataSize;
371     if (lfh->gpb_flags & kGPBDDFlagMask) {
372         // There is trailing data descriptor.
373         const DataDescriptor* dd;
374 
375         if (ddOffset + int(sizeof(uint32_t)) > size_) {
376             fprintf(stderr,
377                     "Error reading trailing data descriptor signature in file '%s' at offset %lld, "
378                     "file size %lld\n",
379                     path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_));
380             return 0;
381         }
382 
383         FileRegion ddMapped(fd_, ddOffset, sizeof(uint32_t) + sizeof(DataDescriptor));
384 
385         off_t localDDOffset = 0;
386         if (kOptionalDataDescriptorMagic == *(uint32_t*)ddMapped.data()) {
387             ddOffset += sizeof(uint32_t);
388             localDDOffset += sizeof(uint32_t);
389             ddSize += sizeof(uint32_t);
390         }
391         if (ddOffset + kDataDescriptorSize > size_) {
392             fprintf(stderr,
393                     "Error reading trailing data descriptor in file '%s' at offset %lld, file size "
394                     "%lld\n",
395                     path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_));
396             return 0;
397         }
398 
399         dd = reinterpret_cast<const DataDescriptor*>(ddMapped.data() + localDDOffset);
400         localDataSize = (lfh->compression_method == kCompressStored) ? dd->uncompressed_size
401                                                                      : dd->compressed_size;
402         ddSize += sizeof(*dd);
403     } else {
404         localDataSize = (lfh->compression_method == kCompressStored) ? lfh->uncompressed_size
405                                                                      : lfh->compressed_size;
406     }
407     if (localDataSize != dataSize) {
408         fprintf(stderr,
409                 "Data sizes mismatch in file '%s' at offset %lld, CDr: %lld vs LHR/DD: %lld\n",
410                 path_.c_str(), static_cast<long long>(localFileHeaderOffset),
411                 static_cast<long long>(dataSize), static_cast<long long>(localDataSize));
412         return 0;
413     }
414 
415     return kLocalFileHeaderSize + lfh->file_name_length + lfh->extra_field_length + dataSize +
416            ddSize;
417 }
418