1 /*
2  * Copyright (C) 2018 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 #include "art_api/dex_file_external.h"
18 
19 #include <inttypes.h>
20 #include <stdint.h>
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 
26 #include <cerrno>
27 #include <cstring>
28 #include <deque>
29 #include <map>
30 #include <memory>
31 #include <string>
32 #include <utility>
33 #include <vector>
34 
35 #include <android-base/logging.h>
36 #include <android-base/macros.h>
37 #include <android-base/mapped_file.h>
38 #include <android-base/stringprintf.h>
39 
40 #include <dex/class_accessor-inl.h>
41 #include <dex/code_item_accessors-inl.h>
42 #include <dex/dex_file-inl.h>
43 #include <dex/dex_file_loader.h>
44 
45 namespace art {
46 namespace {
47 
48 struct MethodCacheEntry {
49   int32_t offset;  // Offset relative to the start of the dex file header.
50   int32_t len;
51   int32_t index;  // Method index.
52 };
53 
54 class MappedFileContainer : public DexFileContainer {
55  public:
MappedFileContainer(std::unique_ptr<android::base::MappedFile> && map)56   explicit MappedFileContainer(std::unique_ptr<android::base::MappedFile>&& map)
57       : map_(std::move(map)) {}
~MappedFileContainer()58   ~MappedFileContainer() override {}
GetPermissions()59   int GetPermissions() override { return 0; }
IsReadOnly()60   bool IsReadOnly() override { return true; }
EnableWrite()61   bool EnableWrite() override { return false; }
DisableWrite()62   bool DisableWrite() override { return false; }
63 
64  private:
65   std::unique_ptr<android::base::MappedFile> map_;
66   DISALLOW_COPY_AND_ASSIGN(MappedFileContainer);
67 };
68 
69 }  // namespace
70 }  // namespace art
71 
72 extern "C" {
73 
74 struct ExtDexFileString {
75   const std::string str_;
76 };
77 
78 static const ExtDexFileString empty_string{""};
79 
ExtDexFileMakeString(const char * str,size_t size)80 const ExtDexFileString* ExtDexFileMakeString(const char* str, size_t size) {
81   if (size == 0) {
82     return &empty_string;
83   }
84   return new ExtDexFileString{std::string(str, size)};
85 }
86 
ExtDexFileGetString(const ExtDexFileString * ext_string,size_t * size)87 const char* ExtDexFileGetString(const ExtDexFileString* ext_string, /*out*/ size_t* size) {
88   DCHECK(ext_string != nullptr);
89   *size = ext_string->str_.size();
90   return ext_string->str_.data();
91 }
92 
ExtDexFileFreeString(const ExtDexFileString * ext_string)93 void ExtDexFileFreeString(const ExtDexFileString* ext_string) {
94   DCHECK(ext_string != nullptr);
95   if (ext_string != &empty_string) {
96     delete (ext_string);
97   }
98 }
99 
100 // Wraps DexFile to add the caching needed by the external interface. This is
101 // what gets passed over as ExtDexFile*.
102 struct ExtDexFile {
103  public:
104   std::unique_ptr<const art::DexFile> dex_file_;
ExtDexFileExtDexFile105   explicit ExtDexFile(std::unique_ptr<const art::DexFile>&& dex_file)
106       : dex_file_(std::move(dex_file)) {}
107 
GetMethodCacheEntryForOffsetExtDexFile108   art::MethodCacheEntry* GetMethodCacheEntryForOffset(int64_t dex_offset) {
109     // First look in the method cache.
110     auto it = method_cache_.upper_bound(dex_offset);
111     if (it != method_cache_.end() && dex_offset >= it->second.offset) {
112       return &it->second;
113     }
114 
115     uint32_t class_def_index;
116     if (GetClassDefIndex(dex_offset, &class_def_index)) {
117       art::ClassAccessor accessor(*dex_file_, class_def_index);
118 
119       for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
120         art::CodeItemInstructionAccessor code = method.GetInstructions();
121         if (!code.HasCodeItem()) {
122           continue;
123         }
124 
125         int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
126         int32_t len = code.InsnsSizeInBytes();
127         if (offset <= dex_offset && dex_offset < offset + len) {
128           int32_t index = method.GetIndex();
129           auto res = method_cache_.emplace(offset + len, art::MethodCacheEntry{offset, len, index});
130           return &res.first->second;
131         }
132       }
133     }
134 
135     return nullptr;
136   }
137 
138  private:
GetClassDefIndexExtDexFile139   bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) {
140     if (class_cache_.empty()) {
141       // Create binary search table with (end_dex_offset, class_def_index) entries.
142       // That is, we don't assume that dex code of given class is consecutive.
143       std::deque<std::pair<uint32_t, uint32_t>> cache;
144       for (art::ClassAccessor accessor : dex_file_->GetClasses()) {
145         for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
146           art::CodeItemInstructionAccessor code = method.GetInstructions();
147           if (code.HasCodeItem()) {
148             int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
149             DCHECK_NE(offset, 0);
150             cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex());
151           }
152         }
153       }
154       std::sort(cache.begin(), cache.end());
155 
156       // If two consecutive methods belong to same class, we can merge them.
157       // This tends to reduce the number of entries (used memory) by 10x.
158       size_t num_entries = cache.size();
159       if (cache.size() > 1) {
160         for (auto it = std::next(cache.begin()); it != cache.end(); it++) {
161           if (std::prev(it)->second == it->second) {
162             std::prev(it)->first = 0;  // Clear entry with lower end_dex_offset (mark to remove).
163             num_entries--;
164           }
165         }
166       }
167 
168       // The cache is immutable now. Store it as continuous vector to save space.
169       class_cache_.reserve(num_entries);
170       auto pred = [](auto it) { return it.first != 0; };  // Entries to copy (not cleared above).
171       std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred);
172     }
173 
174     // Binary search in the class cache. First element of the pair is the key.
175     auto comp = [](uint32_t value, const auto& it) { return value < it.first; };
176     auto it = std::upper_bound(class_cache_.begin(), class_cache_.end(), dex_offset, comp);
177     if (it != class_cache_.end()) {
178       *class_def_index = it->second;
179       return true;
180     }
181     return false;
182   }
183 
184   // Binary search table with (end_dex_offset, class_def_index) entries.
185   std::vector<std::pair<uint32_t, uint32_t>> class_cache_;
186   std::map<uint32_t, art::MethodCacheEntry> method_cache_;  // end_dex_offset -> method.
187 };
188 
ExtDexFileOpenFromMemory(const void * addr,size_t * size,const char * location,const ExtDexFileString ** ext_error_msg,ExtDexFile ** ext_dex_file)189 int ExtDexFileOpenFromMemory(const void* addr,
190                              /*inout*/ size_t* size,
191                              const char* location,
192                              /*out*/ const ExtDexFileString** ext_error_msg,
193                              /*out*/ ExtDexFile** ext_dex_file) {
194   if (*size < sizeof(art::DexFile::Header)) {
195     *size = sizeof(art::DexFile::Header);
196     *ext_error_msg = nullptr;
197     return false;
198   }
199 
200   const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(addr);
201   uint32_t file_size = header->file_size_;
202   if (art::CompactDexFile::IsMagicValid(header->magic_)) {
203     // Compact dex files store the data section separately so that it can be shared.
204     // Therefore we need to extend the read memory range to include it.
205     // TODO: This might be wasteful as we might read data in between as well.
206     //       In practice, this should be fine, as such sharing only happens on disk.
207     uint32_t computed_file_size;
208     if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) {
209       *ext_error_msg = new ExtDexFileString{
210           android::base::StringPrintf("Corrupt CompactDexFile header in '%s'", location)};
211       return false;
212     }
213     if (computed_file_size > file_size) {
214       file_size = computed_file_size;
215     }
216   } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) {
217     *ext_error_msg = new ExtDexFileString{
218         android::base::StringPrintf("Unrecognized dex file header in '%s'", location)};
219     return false;
220   }
221 
222   if (*size < file_size) {
223     *size = file_size;
224     *ext_error_msg = nullptr;
225     return false;
226   }
227 
228   std::string loc_str(location);
229   art::DexFileLoader loader;
230   std::string error_msg;
231   std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(addr),
232                                                              *size,
233                                                              loc_str,
234                                                              header->checksum_,
235                                                              /*oat_dex_file=*/nullptr,
236                                                              /*verify=*/false,
237                                                              /*verify_checksum=*/false,
238                                                              &error_msg);
239   if (dex_file == nullptr) {
240     *ext_error_msg = new ExtDexFileString{std::move(error_msg)};
241     return false;
242   }
243 
244   *ext_dex_file = new ExtDexFile(std::move(dex_file));
245   return true;
246 }
247 
ExtDexFileOpenFromFd(int fd,off_t offset,const char * location,const ExtDexFileString ** ext_error_msg,ExtDexFile ** ext_dex_file)248 int ExtDexFileOpenFromFd(int fd,
249                          off_t offset,
250                          const char* location,
251                          /*out*/ const ExtDexFileString** ext_error_msg,
252                          /*out*/ ExtDexFile** ext_dex_file) {
253   size_t length;
254   {
255     struct stat sbuf;
256     std::memset(&sbuf, 0, sizeof(sbuf));
257     if (fstat(fd, &sbuf) == -1) {
258       *ext_error_msg = new ExtDexFileString{
259           android::base::StringPrintf("fstat '%s' failed: %s", location, std::strerror(errno))};
260       return false;
261     }
262     if (S_ISDIR(sbuf.st_mode)) {
263       *ext_error_msg = new ExtDexFileString{
264           android::base::StringPrintf("Attempt to mmap directory '%s'", location)};
265       return false;
266     }
267     length = sbuf.st_size;
268   }
269 
270   if (length < offset + sizeof(art::DexFile::Header)) {
271     *ext_error_msg = new ExtDexFileString{android::base::StringPrintf(
272         "Offset %" PRId64 " too large for '%s' of size %zu",
273         int64_t{offset},
274         location,
275         length)};
276     return false;
277   }
278 
279   // Cannot use MemMap in libartbase here, because it pulls in dlopen which we
280   // can't have when being compiled statically.
281   std::unique_ptr<android::base::MappedFile> map =
282       android::base::MappedFile::FromFd(fd, offset, length, PROT_READ);
283   if (map == nullptr) {
284     *ext_error_msg = new ExtDexFileString{
285         android::base::StringPrintf("mmap '%s' failed: %s", location, std::strerror(errno))};
286     return false;
287   }
288 
289   const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(map->data());
290   uint32_t file_size;
291   if (__builtin_add_overflow(offset, header->file_size_, &file_size)) {
292     *ext_error_msg =
293         new ExtDexFileString{android::base::StringPrintf("Corrupt header in '%s'", location)};
294     return false;
295   }
296   if (length < file_size) {
297     *ext_error_msg = new ExtDexFileString{
298         android::base::StringPrintf("Dex file '%s' too short: expected %" PRIu32 ", got %" PRIu64,
299                                     location,
300                                     file_size,
301                                     uint64_t{length})};
302     return false;
303   }
304 
305   void* addr = map->data();
306   size_t size = map->size();
307   auto container = std::make_unique<art::MappedFileContainer>(std::move(map));
308 
309   std::string loc_str(location);
310   std::string error_msg;
311   art::DexFileLoader loader;
312   std::unique_ptr<const art::DexFile> dex_file = loader.Open(reinterpret_cast<const uint8_t*>(addr),
313                                                              size,
314                                                              loc_str,
315                                                              header->checksum_,
316                                                              /*oat_dex_file=*/nullptr,
317                                                              /*verify=*/false,
318                                                              /*verify_checksum=*/false,
319                                                              &error_msg,
320                                                              std::move(container));
321   if (dex_file == nullptr) {
322     *ext_error_msg = new ExtDexFileString{std::move(error_msg)};
323     return false;
324   }
325   *ext_dex_file = new ExtDexFile(std::move(dex_file));
326   return true;
327 }
328 
ExtDexFileGetMethodInfoForOffset(ExtDexFile * ext_dex_file,int64_t dex_offset,int with_signature,ExtDexFileMethodInfo * method_info)329 int ExtDexFileGetMethodInfoForOffset(ExtDexFile* ext_dex_file,
330                                      int64_t dex_offset,
331                                      int with_signature,
332                                      /*out*/ ExtDexFileMethodInfo* method_info) {
333   if (!ext_dex_file->dex_file_->IsInDataSection(ext_dex_file->dex_file_->Begin() + dex_offset)) {
334     return false;  // The DEX offset is not within the bytecode of this dex file.
335   }
336 
337   if (ext_dex_file->dex_file_->IsCompactDexFile()) {
338     // The data section of compact dex files might be shared.
339     // Check the subrange unique to this compact dex.
340     const art::CompactDexFile::Header& cdex_header =
341         ext_dex_file->dex_file_->AsCompactDexFile()->GetHeader();
342     uint32_t begin = cdex_header.data_off_ + cdex_header.OwnedDataBegin();
343     uint32_t end = cdex_header.data_off_ + cdex_header.OwnedDataEnd();
344     if (dex_offset < begin || dex_offset >= end) {
345       return false;  // The DEX offset is not within the bytecode of this dex file.
346     }
347   }
348 
349   art::MethodCacheEntry* entry = ext_dex_file->GetMethodCacheEntryForOffset(dex_offset);
350   if (entry != nullptr) {
351     method_info->offset = entry->offset;
352     method_info->len = entry->len;
353     method_info->name =
354         new ExtDexFileString{ext_dex_file->dex_file_->PrettyMethod(entry->index, with_signature)};
355     return true;
356   }
357 
358   return false;
359 }
360 
ExtDexFileGetAllMethodInfos(ExtDexFile * ext_dex_file,int with_signature,ExtDexFileMethodInfoCallback * method_info_cb,void * user_data)361 void ExtDexFileGetAllMethodInfos(ExtDexFile* ext_dex_file,
362                                  int with_signature,
363                                  ExtDexFileMethodInfoCallback* method_info_cb,
364                                  void* user_data) {
365   for (art::ClassAccessor accessor : ext_dex_file->dex_file_->GetClasses()) {
366     for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
367       art::CodeItemInstructionAccessor code = method.GetInstructions();
368       if (!code.HasCodeItem()) {
369         continue;
370       }
371 
372       ExtDexFileMethodInfo method_info;
373       method_info.offset = static_cast<int32_t>(reinterpret_cast<const uint8_t*>(code.Insns()) -
374                                                 ext_dex_file->dex_file_->Begin());
375       method_info.len = code.InsnsSizeInBytes();
376       method_info.name = new ExtDexFileString{
377           ext_dex_file->dex_file_->PrettyMethod(method.GetIndex(), with_signature)};
378       method_info_cb(&method_info, user_data);
379     }
380   }
381 }
382 
ExtDexFileFree(ExtDexFile * ext_dex_file)383 void ExtDexFileFree(ExtDexFile* ext_dex_file) { delete (ext_dex_file); }
384 
385 }  // extern "C"
386