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