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