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