1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "crazy_linker_zip.h"
6
7#include <fcntl.h>
8#include <sys/mman.h>
9#include <sys/stat.h>
10#include <unistd.h>
11
12#include "crazy_linker_debug.h"
13#include "crazy_linker_system.h"
14
15namespace {
16
17// All offsets are given in bytes relative to the start of the header.
18// Arithmetic is used to indicate the size of small fields that are skipped.
19
20// http://www.pkware.com/documents/casestudies/APPNOTE.TXT Section 4.3.16
21// This marker appears at the start of the end of central directory record
22const uint32_t kEndOfCentralDirectoryMarker = 0x06054b50;
23
24// Offsets of fields in End of Central Directory.
25const int kOffsetNumOfEntriesInEndOfCentralDirectory = 4 + 2 + 2;
26const int kOffsetOfCentralDirLengthInEndOfCentralDirectory =
27    kOffsetNumOfEntriesInEndOfCentralDirectory + 2 + 2;
28const int kOffsetOfStartOfCentralDirInEndOfCentralDirectory =
29    kOffsetOfCentralDirLengthInEndOfCentralDirectory + 4;
30
31// http://www.pkware.com/documents/casestudies/APPNOTE.TXT Section 4.3.12
32// This marker appears at the start of the central directory
33const uint32_t kCentralDirHeaderMarker = 0x2014b50;
34
35// Offsets of fields in Central Directory Header.
36const int kOffsetFilenameLengthInCentralDirectory =
37    4 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4;
38const int kOffsetExtraFieldLengthInCentralDirectory =
39    kOffsetFilenameLengthInCentralDirectory + 2;
40const int kOffsetCommentLengthInCentralDirectory =
41    kOffsetExtraFieldLengthInCentralDirectory + 2;
42const int kOffsetLocalHeaderOffsetInCentralDirectory =
43    kOffsetCommentLengthInCentralDirectory + 2 + 2 + 2 + 4;
44const int kOffsetFilenameInCentralDirectory =
45    kOffsetLocalHeaderOffsetInCentralDirectory + 4;
46
47// http://www.pkware.com/documents/casestudies/APPNOTE.TXT Section 4.3.7
48// This marker appears at the start of local header
49const uint32_t kLocalHeaderMarker = 0x04034b50;
50
51// Offsets of fields in the Local Header.
52const int kOffsetFilenameLengthInLocalHeader =
53    4 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4;
54const int kOffsetExtraFieldLengthInLocalHeader =
55    kOffsetFilenameLengthInLocalHeader + 2;
56const int kOffsetFilenameInLocalHeader =
57    kOffsetExtraFieldLengthInLocalHeader + 2;
58
59// RAII pattern for unmapping and closing the mapped file.
60class ScopedMMap {
61 public:
62  ScopedMMap(void* mem, uint32_t len) : mem_(mem), len_(len) {}
63  ~ScopedMMap() {
64    if (munmap(mem_, len_) == -1) {
65      LOG_ERRNO("%s: munmap failed when trying to unmap zip file\n",
66                __FUNCTION__);
67    }
68  }
69 private:
70  void* mem_;
71  uint32_t len_;
72};
73
74inline uint32_t ReadUInt16(uint8_t* mem_bytes, int offset) {
75  return
76      static_cast<uint32_t>(mem_bytes[offset]) |
77      (static_cast<uint32_t>(mem_bytes[offset + 1]) << 8);
78}
79
80inline uint32_t ReadUInt32(uint8_t* mem_bytes, int offset) {
81  return
82      static_cast<uint32_t>(mem_bytes[offset]) |
83      (static_cast<uint32_t>(mem_bytes[offset + 1]) << 8) |
84      (static_cast<uint32_t>(mem_bytes[offset + 2]) << 16) |
85      (static_cast<uint32_t>(mem_bytes[offset + 3]) << 24);
86}
87
88}  // unnamed namespace
89
90namespace crazy {
91
92const uint32_t kMaxZipFileLength = 1U << 31;  // 2GB
93
94int FindStartOffsetOfFileInZipFile(const char* zip_file, const char* filename) {
95  // Open the file
96  FileDescriptor fd;
97  if (!fd.OpenReadOnly(zip_file)) {
98    LOG_ERRNO("%s: open failed trying to open zip file %s\n",
99              __FUNCTION__, zip_file);
100    return -1;
101  }
102
103  // Find the length of the file.
104  struct stat stat_buf;
105  if (stat(zip_file, &stat_buf) == -1) {
106    LOG_ERRNO("%s: stat failed trying to stat zip file %s\n",
107              __FUNCTION__, zip_file);
108    return -1;
109  }
110
111  if (stat_buf.st_size > kMaxZipFileLength) {
112    LOG("%s: The size %ld of %s is too large to map\n",
113        __FUNCTION__, stat_buf.st_size, zip_file);
114    return -1;
115  }
116
117  // Map the file into memory.
118  void* mem = fd.Map(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, 0);
119  if (mem == MAP_FAILED) {
120    LOG_ERRNO("%s: mmap failed trying to mmap zip file %s\n",
121              __FUNCTION__, zip_file);
122    return -1;
123  }
124  ScopedMMap scoped_mmap(mem, stat_buf.st_size);
125
126  // Scan backwards from the end of the file searching for the end of
127  // central directory marker.
128  uint8_t* mem_bytes = static_cast<uint8_t*>(mem);
129  int off;
130  for (off = stat_buf.st_size - sizeof(kEndOfCentralDirectoryMarker);
131       off >= 0; --off) {
132    if (ReadUInt32(mem_bytes, off) == kEndOfCentralDirectoryMarker) {
133      break;
134    }
135  }
136  if (off == -1) {
137    LOG("%s: Failed to find end of central directory in %s\n",
138        __FUNCTION__, zip_file);
139    return -1;
140  }
141
142  // We have located the end of central directory record, now locate
143  // the central directory by reading the end of central directory record.
144
145  uint32_t length_of_central_dir = ReadUInt32(
146      mem_bytes, off + kOffsetOfCentralDirLengthInEndOfCentralDirectory);
147  uint32_t start_of_central_dir = ReadUInt32(
148      mem_bytes, off + kOffsetOfStartOfCentralDirInEndOfCentralDirectory);
149
150  if (start_of_central_dir > off) {
151    LOG("%s: Found out of range offset %u for start of directory in %s\n",
152        __FUNCTION__, start_of_central_dir, zip_file);
153    return -1;
154  }
155
156  uint32_t end_of_central_dir = start_of_central_dir + length_of_central_dir;
157  if (end_of_central_dir > off) {
158    LOG("%s: Found out of range offset %u for end of directory in %s\n",
159        __FUNCTION__, end_of_central_dir, zip_file);
160    return -1;
161  }
162
163  uint32_t num_entries = ReadUInt16(
164      mem_bytes, off + kOffsetNumOfEntriesInEndOfCentralDirectory);
165
166  // Read the headers in the central directory and locate the file.
167  off = start_of_central_dir;
168  const int target_len = strlen(filename);
169  int n = 0;
170  for (; n < num_entries && off < end_of_central_dir; ++n) {
171    uint32_t marker = ReadUInt32(mem_bytes, off);
172    if (marker != kCentralDirHeaderMarker) {
173      LOG("%s: Failed to find central directory header marker in %s. "
174          "Found 0x%x but expected 0x%x\n", __FUNCTION__,
175          zip_file, marker, kCentralDirHeaderMarker);
176      return -1;
177    }
178    uint32_t file_name_length =
179        ReadUInt16(mem_bytes, off + kOffsetFilenameLengthInCentralDirectory);
180    uint32_t extra_field_length =
181        ReadUInt16(mem_bytes, off + kOffsetExtraFieldLengthInCentralDirectory);
182    uint32_t comment_field_length =
183        ReadUInt16(mem_bytes, off + kOffsetCommentLengthInCentralDirectory);
184    uint32_t header_length = kOffsetFilenameInCentralDirectory +
185        file_name_length + extra_field_length + comment_field_length;
186
187    uint32_t local_header_offset =
188        ReadUInt32(mem_bytes, off + kOffsetLocalHeaderOffsetInCentralDirectory);
189
190    uint8_t* filename_bytes =
191        mem_bytes + off + kOffsetFilenameInCentralDirectory;
192
193    if (file_name_length == target_len &&
194        memcmp(filename_bytes, filename, target_len) == 0) {
195      // Filename matches. Read the local header and compute the offset.
196      uint32_t marker = ReadUInt32(mem_bytes, local_header_offset);
197      if (marker != kLocalHeaderMarker) {
198        LOG("%s: Failed to find local file header marker in %s. "
199            "Found 0x%x but expected 0x%x\n", __FUNCTION__,
200            zip_file, marker, kLocalHeaderMarker);
201        return -1;
202      }
203
204      uint32_t file_name_length =
205          ReadUInt16(
206              mem_bytes,
207              local_header_offset + kOffsetFilenameLengthInLocalHeader);
208      uint32_t extra_field_length =
209          ReadUInt16(
210              mem_bytes,
211              local_header_offset + kOffsetExtraFieldLengthInLocalHeader);
212      uint32_t header_length =
213          kOffsetFilenameInLocalHeader + file_name_length + extra_field_length;
214
215      return local_header_offset + header_length;
216    }
217
218    off += header_length;
219  }
220
221  if (n < num_entries) {
222    LOG("%s: Did not find all the expected entries in the central directory. "
223        "Found %d but expected %d\n", __FUNCTION__, n, num_entries);
224  }
225
226  if (off < end_of_central_dir) {
227    LOG("%s: There are %d extra bytes at the end of the central directory.\n",
228        __FUNCTION__, end_of_central_dir - off);
229  }
230
231  LOG("%s: Did not find %s in %s\n", __FUNCTION__, filename, zip_file);
232  return -1;
233}
234
235}  // crazy namespace
236