Merge "Add definition for zip64 struct"

This commit is contained in:
Tianjie Xu 2020-03-24 01:21:09 +00:00 committed by Gerrit Code Review
commit 18c00858d2
5 changed files with 205 additions and 61 deletions

View file

@ -145,7 +145,7 @@ struct ZipArchiveInfo {
/** The size in bytes of the archive itself. Used by zipinfo. */
off64_t archive_size;
/** The number of entries in the archive. */
size_t entry_count;
uint64_t entry_count;
};
/**

View file

@ -65,6 +65,10 @@ static const bool kCrcChecksEnabled = false;
// The maximum number of bytes to scan backwards for the EOCD start.
static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord);
// Set a reasonable cap (256 GiB) for the zip file size. So the data is always valid when
// we parse the fields in cd or local headers as 64 bits signed integers.
static constexpr uint64_t kMaxFileLength = 256 * static_cast<uint64_t>(1u << 30u);
/*
* A Read-only Zip archive.
*
@ -125,12 +129,27 @@ ZipArchive::~ZipArchive() {
}
}
static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* archive,
off64_t file_length, uint32_t read_amount,
uint8_t* scan_buffer) {
struct CentralDirectoryInfo {
uint64_t num_records;
// The size of the central directory (in bytes).
uint64_t cd_size;
// The offset of the start of the central directory, relative
// to the start of the file.
uint64_t cd_start_offset;
};
static ZipError FindCentralDirectoryInfoForZip64(CentralDirectoryInfo* /* cdInfo */) {
ALOGW("Zip: Parsing zip64 EOCD isn't supported yet.");
return kInvalidFile;
}
static ZipError FindCentralDirectoryInfo(const char* debug_file_name, ZipArchive* archive,
off64_t file_length, uint32_t read_amount,
CentralDirectoryInfo* cdInfo) {
std::vector<uint8_t> scan_buffer(read_amount);
const off64_t search_start = file_length - read_amount;
if (!archive->mapped_zip.ReadAtOffset(scan_buffer, read_amount, search_start)) {
if (!archive->mapped_zip.ReadAtOffset(scan_buffer.data(), read_amount, search_start)) {
ALOGE("Zip: read %" PRId64 " from offset %" PRId64 " failed", static_cast<int64_t>(read_amount),
static_cast<int64_t>(search_start));
return kIoError;
@ -159,7 +178,7 @@ static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* arc
}
const off64_t eocd_offset = search_start + i;
const EocdRecord* eocd = reinterpret_cast<const EocdRecord*>(scan_buffer + i);
auto eocd = reinterpret_cast<const EocdRecord*>(scan_buffer.data() + i);
/*
* Verify that there's no trailing space at the end of the central directory
* and its comment.
@ -171,6 +190,13 @@ static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* arc
return kInvalidFile;
}
// One of the field is 0xFFFFFFFF, look for the zip64 EOCD instead.
if (eocd->cd_size == UINT32_MAX || eocd->cd_start_offset == UINT32_MAX) {
ALOGV("Looking for the zip64 EOCD, cd_size: %" PRIu32 "cd_start_offset: %" PRId32,
eocd->cd_size, eocd->cd_start_offset);
return FindCentralDirectoryInfoForZip64(cdInfo);
}
/*
* Grab the CD offset and size, and the number of entries in the
* archive and verify that they look reasonable.
@ -180,47 +206,29 @@ static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* arc
eocd->cd_start_offset, eocd->cd_size, static_cast<int64_t>(eocd_offset));
return kInvalidOffset;
}
if (eocd->num_records == 0) {
#if defined(__ANDROID__)
ALOGW("Zip: empty archive?");
#endif
return kEmptyArchive;
}
ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32, eocd->num_records,
eocd->cd_size, eocd->cd_start_offset);
// It all looks good. Create a mapping for the CD, and set the fields
// in archive.
if (!archive->InitializeCentralDirectory(static_cast<off64_t>(eocd->cd_start_offset),
static_cast<size_t>(eocd->cd_size))) {
return kMmapFailed;
}
archive->num_entries = eocd->num_records;
archive->directory_offset = eocd->cd_start_offset;
return 0;
*cdInfo = {.num_records = eocd->num_records,
.cd_size = eocd->cd_size,
.cd_start_offset = eocd->cd_start_offset};
return kSuccess;
}
/*
* Find the zip Central Directory and memory-map it.
*
* On success, returns 0 after populating fields from the EOCD area:
* On success, returns kSuccess after populating fields from the EOCD area:
* directory_offset
* directory_ptr
* num_entries
*/
static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
// Test file length. We use lseek64 to make sure the file
// is small enough to be a zip file (Its size must be less than
// 0xffffffff bytes).
static ZipError MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
// Test file length. We use lseek64 to make sure the file is small enough to be a zip file.
off64_t file_length = archive->mapped_zip.GetFileLength();
if (file_length == -1) {
return kInvalidFile;
}
if (file_length > static_cast<off64_t>(0xffffffff)) {
if (file_length > kMaxFileLength) {
ALOGV("Zip: zip file too long %" PRId64, static_cast<int64_t>(file_length));
return kInvalidFile;
}
@ -247,10 +255,39 @@ static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* arch
read_amount = static_cast<uint32_t>(file_length);
}
std::vector<uint8_t> scan_buffer(read_amount);
int32_t result =
MapCentralDirectory0(debug_file_name, archive, file_length, read_amount, scan_buffer.data());
return result;
CentralDirectoryInfo cdInfo = {};
if (auto result =
FindCentralDirectoryInfo(debug_file_name, archive, file_length, read_amount, &cdInfo);
result != kSuccess) {
return result;
}
if (cdInfo.num_records == 0) {
#if defined(__ANDROID__)
ALOGW("Zip: empty archive?");
#endif
return kEmptyArchive;
}
if (cdInfo.cd_size >= SIZE_MAX) {
ALOGW("Zip: The size of central directory doesn't fit in range of size_t: %" PRIu64,
cdInfo.cd_size);
return kInvalidFile;
}
ALOGV("+++ num_entries=%" PRIu64 " dir_size=%" PRIu64 " dir_offset=%" PRIu64, cdInfo.num_records,
cdInfo.cd_size, cdInfo.cd_start_offset);
// It all looks good. Create a mapping for the CD, and set the fields in archive.
if (!archive->InitializeCentralDirectory(static_cast<off64_t>(cdInfo.cd_start_offset),
static_cast<size_t>(cdInfo.cd_size))) {
return kMmapFailed;
}
archive->num_entries = cdInfo.num_records;
archive->directory_offset = cdInfo.cd_start_offset;
return kSuccess;
}
/*
@ -262,13 +299,12 @@ static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* arch
static int32_t ParseZipArchive(ZipArchive* archive) {
const uint8_t* const cd_ptr = archive->central_directory.GetBasePtr();
const size_t cd_length = archive->central_directory.GetMapLength();
const uint16_t num_entries = archive->num_entries;
const uint64_t num_entries = archive->num_entries;
// TODO(xunchang) parse the zip64 Eocd
if (num_entries > UINT16_MAX) {
archive->cd_entry_map = CdEntryMapZip64::Create();
if (num_entries <= UINT16_MAX) {
archive->cd_entry_map = CdEntryMapZip32::Create(static_cast<uint16_t>(num_entries));
} else {
archive->cd_entry_map = CdEntryMapZip32::Create(num_entries);
archive->cd_entry_map = CdEntryMapZip64::Create();
}
if (archive->cd_entry_map == nullptr) {
return kAllocationFailed;
@ -280,9 +316,9 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
*/
const uint8_t* const cd_end = cd_ptr + cd_length;
const uint8_t* ptr = cd_ptr;
for (uint16_t i = 0; i < num_entries; i++) {
for (uint64_t i = 0; i < num_entries; i++) {
if (ptr > cd_end - sizeof(CentralDirectoryRecord)) {
ALOGW("Zip: ran off the end (item #%" PRIu16 ", %zu bytes of central directory)", i,
ALOGW("Zip: ran off the end (item #%" PRIu64 ", %zu bytes of central directory)", i,
cd_length);
#if defined(__ANDROID__)
android_errorWriteLog(0x534e4554, "36392138");
@ -292,14 +328,7 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
const CentralDirectoryRecord* cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
if (cdr->record_signature != CentralDirectoryRecord::kSignature) {
ALOGW("Zip: missed a central dir sig (at %" PRIu16 ")", i);
return kInvalidFile;
}
const off64_t local_header_offset = cdr->local_file_header_offset;
if (local_header_offset >= archive->directory_offset) {
ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu16,
static_cast<int64_t>(local_header_offset), i);
ALOGW("Zip: missed a central dir sig (at %" PRIu64 ")", i);
return kInvalidFile;
}
@ -308,15 +337,37 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
const uint16_t comment_length = cdr->comment_length;
const uint8_t* file_name = ptr + sizeof(CentralDirectoryRecord);
if (file_name + file_name_length > cd_end) {
ALOGW("Zip: file name for entry %" PRIu16
if (file_name_length >= cd_length || file_name > cd_end - file_name_length) {
ALOGW("Zip: file name for entry %" PRIu64
" exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
i, file_name_length, cd_length);
return kInvalidEntryName;
}
const uint8_t* extra_field = file_name + file_name_length;
if (extra_length >= cd_length || extra_field > cd_end - extra_length) {
ALOGW("Zip: extra field for entry %" PRIu64
" exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
i, extra_length, cd_length);
return kInvalidFile;
}
off64_t local_header_offset = cdr->local_file_header_offset;
if (local_header_offset == UINT32_MAX) {
// TODO(xunchang) parse the zip64 eocd
ALOGW("Zip: Parsing zip64 cd entry isn't supported yet");
return kInvalidFile;
}
if (local_header_offset >= archive->directory_offset) {
ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu64,
static_cast<int64_t>(local_header_offset), i);
return kInvalidFile;
}
// Check that file name is valid UTF-8 and doesn't contain NUL (U+0000) characters.
if (!IsValidEntryName(file_name, file_name_length)) {
ALOGW("Zip: invalid file name at entry %" PRIu16, i);
ALOGW("Zip: invalid file name at entry %" PRIu64, i);
return kInvalidEntryName;
}
@ -331,7 +382,7 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
ptr += sizeof(CentralDirectoryRecord) + file_name_length + extra_length + comment_length;
if ((ptr - cd_ptr) > static_cast<int64_t>(cd_length)) {
ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu16, ptr - cd_ptr, cd_length, i);
ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu64, ptr - cd_ptr, cd_length, i);
return kInvalidFile;
}
}
@ -351,14 +402,14 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
return kInvalidFile;
}
ALOGV("+++ zip good scan %" PRIu16 " entries", num_entries);
ALOGV("+++ zip good scan %" PRIu64 " entries", num_entries);
return 0;
}
static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) {
int32_t result = MapCentralDirectory(debug_file_name, archive);
return result != 0 ? result : ParseZipArchive(archive);
return result != kSuccess ? result : ParseZipArchive(archive);
}
int32_t OpenArchiveFd(int fd, const char* debug_file_name, ZipArchiveHandle* handle,
@ -489,7 +540,15 @@ static int32_t FindEntry(const ZipArchive* archive, std::string_view entryName,
// Figure out the local header offset from the central directory. The
// actual file data will begin after the local header and the name /
// extra comments.
const off64_t local_header_offset = cdr->local_file_header_offset;
off64_t local_header_offset = cdr->local_file_header_offset;
// One of the info field is UINT32_MAX, try to parse the real value in the zip64 extended info in
// the extra field.
if (cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX ||
cdr->local_file_header_offset == UINT32_MAX) {
ALOGW("Zip: Parsing zip64 local file header isn't supported yet");
return kInvalidFile;
}
if (local_header_offset + static_cast<off64_t>(sizeof(LocalFileHeader)) >= cd_offset) {
ALOGW("Zip: bad local hdr offset in zip");
return kInvalidOffset;

View file

@ -21,6 +21,8 @@
#include <inttypes.h>
#include <optional>
// The "end of central directory" (EOCD) record. Each archive
// contains exactly once such record which appears at the end of
// the archive. It contains archive wide information like the
@ -173,6 +175,89 @@ struct DataDescriptor {
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
} __attribute__((packed));
// The zip64 end of central directory locator helps to find the zip64 EOCD.
struct Zip64EocdLocator {
static constexpr uint32_t kSignature = 0x07064b50;
// The signature of zip64 eocd locator, must be |kSignature|
uint32_t locator_signature;
// The start disk of the zip64 eocd. This implementation assumes that each
// archive spans a single disk only.
uint32_t eocd_start_disk;
// The offset offset of the zip64 end of central directory record.
uint64_t zip64_eocd_offset;
// The total number of disks. This implementation assumes that each archive
// spans a single disk only.
uint32_t num_of_disks;
private:
Zip64EocdLocator() = default;
DISALLOW_COPY_AND_ASSIGN(Zip64EocdLocator);
} __attribute__((packed));
// The optional zip64 EOCD. If one of the fields in the end of central directory
// record is too small to hold required data, the field SHOULD be set to -1
// (0xFFFF or 0xFFFFFFFF) and the ZIP64 format record SHOULD be created.
struct Zip64EocdRecord {
static constexpr uint32_t kSignature = 0x06064b50;
// The signature of zip64 eocd record, must be |kSignature|
uint32_t record_signature;
// Size of zip64 end of central directory record. It SHOULD be the size of the
// remaining record and SHOULD NOT include the leading 12 bytes.
uint64_t record_size;
// The version of the tool that make this archive.
uint16_t version_made_by;
// Tool version needed to extract this archive.
uint16_t version_needed;
// Number of this disk.
uint32_t disk_num;
// Number of the disk with the start of the central directory.
uint32_t cd_start_disk;
// Total number of entries in the central directory on this disk.
// This implementation assumes that each archive spans a single
// disk only. i.e, that num_records_on_disk == num_records.
uint64_t num_records_on_disk;
// The total number of central directory records.
uint64_t num_records;
// The size of the central directory in bytes.
uint64_t cd_size;
// The offset of the start of the central directory, relative to the start of
// the file.
uint64_t cd_start_offset;
private:
Zip64EocdRecord() = default;
DISALLOW_COPY_AND_ASSIGN(Zip64EocdRecord);
} __attribute__((packed));
// The possible contents of the Zip64 Extended Information Extra Field. It may appear in
// the 'extra' field of a central directory record or local file header. The order of
// the fields in the zip64 extended information record is fixed, but the fields MUST
// only appear if the corresponding local or central directory record field is set to
// 0xFFFF or 0xFFFFFFFF. And this entry in the Local header MUST include BOTH original
// and compressed file size fields.
struct Zip64ExtendedInfo {
static constexpr uint16_t kHeaderId = 0x0001;
// The header tag for this 'extra' block, should be |kHeaderId|.
uint16_t header_id;
// The size in bytes of the remaining data (excluding the top 4 bytes).
uint16_t data_size;
// Size in bytes of the uncompressed file.
std::optional<uint64_t> uncompressed_file_size;
// Size in bytes of the compressed file.
std::optional<uint64_t> compressed_file_size;
// Local file header offset relative to the start of the zip file.
std::optional<uint64_t> local_header_offset;
// This implementation assumes that each archive spans a single disk only. So
// the disk_number is not used.
// uint32_t disk_num;
private:
Zip64ExtendedInfo() = default;
DISALLOW_COPY_AND_ASSIGN(Zip64ExtendedInfo);
};
// mask value that signifies that the entry has a DD
static const uint32_t kGPBDDFlagMask = 0x0008;

View file

@ -95,7 +95,7 @@ struct ZipArchive {
std::unique_ptr<android::base::MappedFile> directory_map;
// number of entries in the Zip archive
uint16_t num_entries;
uint64_t num_entries;
std::unique_ptr<CdEntryMapInterface> cd_entry_map;
ZipArchive(MappedZipFile&& map, bool assume_ownership);

View file

@ -133,8 +133,8 @@ static void MaybeShowHeader(ZipArchiveHandle zah) {
if (!flag_1 && includes.empty() && excludes.empty()) {
ZipArchiveInfo info{GetArchiveInfo(zah)};
printf("Archive: %s\n", archive_name);
printf("Zip file size: %" PRId64 " bytes, number of entries: %zu\n", info.archive_size,
info.entry_count);
printf("Zip file size: %" PRId64 " bytes, number of entries: %" PRIu64 "\n",
info.archive_size, info.entry_count);
}
}
}