xref: /aosp_15_r20/external/cronet/base/android/linker/linker_jni.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2015 The Chromium Authors
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 "base/android/linker/linker_jni.h"
6 
7 #include <android/dlext.h>
8 #include <dlfcn.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <inttypes.h>
12 #include <jni.h>
13 #include <link.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/mman.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19 
20 #include <memory>
21 
22 namespace chromium_android_linker {
23 
24 namespace {
25 
26 // Variable containing LibInfo for the loaded library.
27 LibInfo_class s_lib_info_fields;
28 
29 // Guarded by |mLock| in Linker.java.
30 RelroSharingStatus s_relro_sharing_status = RelroSharingStatus::NOT_ATTEMPTED;
31 
32 // Saved JavaVM passed to JNI_OnLoad().
33 JavaVM* s_java_vm = nullptr;
34 
GetPageSize()35 size_t GetPageSize() {
36   return sysconf(_SC_PAGESIZE);
37 }
38 
39 // With mmap(2) reserves a range of virtual addresses.
40 //
41 // The range must start with |hint| and be of size |size|. The |hint==0|
42 // indicates that the address of the mapping should be chosen at random,
43 // utilizing ASLR built into mmap(2).
44 //
45 // The start of the resulting region is returned in |address|.
46 //
47 // The value 0 returned iff the attempt failed (a part of the address range is
48 // already reserved by some other subsystem).
ReserveAddressWithHint(uintptr_t hint,uintptr_t * address,size_t * size)49 void ReserveAddressWithHint(uintptr_t hint, uintptr_t* address, size_t* size) {
50   void* ptr = reinterpret_cast<void*>(hint);
51   void* new_ptr = mmap(ptr, kAddressSpaceReservationSize, PROT_NONE,
52                        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
53   if (new_ptr == MAP_FAILED) {
54     PLOG_ERROR("mmap");
55     *address = 0;
56   } else if ((hint != 0) && (new_ptr != ptr)) {
57     // Something grabbed the address range before the early phase of the
58     // linker had a chance, this should be uncommon.
59     LOG_ERROR("Address range starting at 0x%" PRIxPTR " was not free to use",
60               hint);
61     munmap(new_ptr, kAddressSpaceReservationSize);
62     *address = 0;
63   } else {
64     *address = reinterpret_cast<uintptr_t>(new_ptr);
65     *size = kAddressSpaceReservationSize;
66     LOG_INFO("Reserved region at address: 0x%" PRIxPTR ", size: 0x%zu",
67              *address, *size);
68   }
69 }
70 
ScanRegionInBuffer(const char * buf,size_t length,uintptr_t * out_address,size_t * out_size)71 bool ScanRegionInBuffer(const char* buf,
72                         size_t length,
73                         uintptr_t* out_address,
74                         size_t* out_size) {
75   const char* position = strstr(buf, "[anon:libwebview reservation]");
76   if (!position)
77     return false;
78 
79   const char* line_start = position;
80   while (line_start > buf) {
81     line_start--;
82     if (*line_start == '\n') {
83       line_start++;
84       break;
85     }
86   }
87 
88   // Extract the region start and end. The failures below should not happen as
89   // long as the reservation is made the same way in
90   // frameworks/base/native/webview/loader/loader.cpp.
91   uintptr_t vma_start, vma_end;
92   char permissions[5] = {'\0'};  // Ensure a null-terminated string.
93   // Example line from proc(5):
94   // address           perms offset  dev   inode   pathname
95   // 00400000-00452000 r-xp 00000000 08:02 173521  /usr/bin/dbus-daemon
96   if (sscanf(line_start, "%" SCNxPTR "-%" SCNxPTR " %4c", &vma_start, &vma_end,
97              permissions) < 3) {
98     return false;
99   }
100 
101   if (strcmp(permissions, "---p"))
102     return false;
103 
104   const size_t kPageSize = GetPageSize();
105   if (vma_start % kPageSize || vma_end % kPageSize) {
106     return false;
107   }
108 
109   *out_address = static_cast<uintptr_t>(vma_start);
110   *out_size = vma_end - vma_start;
111 
112   return true;
113 }
114 
FindRegionInOpenFile(int fd,uintptr_t * out_address,size_t * out_size)115 bool FindRegionInOpenFile(int fd, uintptr_t* out_address, size_t* out_size) {
116   constexpr size_t kMaxLineLength = 256;
117   const size_t kPageSize = GetPageSize();
118   const size_t kReadSize = kPageSize;
119 
120   // Loop until no bytes left to scan. On every iteration except the last, fill
121   // the buffer till the end. On every iteration except the first, the buffer
122   // begins with kMaxLineLength bytes from the end of the previous fill.
123   char buf[kReadSize + kMaxLineLength + 1];
124   buf[kReadSize + kMaxLineLength] = '\0';  // Stop strstr().
125   size_t pos = 0;
126   size_t bytes_requested = kReadSize + kMaxLineLength;
127   bool reached_end = false;
128   while (true) {
129     // Fill the |buf| to the maximum and determine whether reading reached the
130     // end.
131     size_t bytes_read = 0;
132     do {
133       ssize_t rv = HANDLE_EINTR(
134           read(fd, buf + pos + bytes_read, bytes_requested - bytes_read));
135       if (rv == 0) {
136         reached_end = true;
137       } else if (rv < 0) {
138         PLOG_ERROR("read to find webview reservation");
139         return false;
140       }
141       bytes_read += rv;
142     } while (!reached_end && (bytes_read < bytes_requested));
143 
144     // Return results if the buffer contains the pattern.
145     if (ScanRegionInBuffer(buf, pos + bytes_read, out_address, out_size))
146       return true;
147 
148     // Did not find the pattern.
149     if (reached_end)
150       return false;
151 
152     // The buffer is filled to the end. Copy the end bytes to the beginning,
153     // allowing to scan these bytes on the next iteration.
154     memcpy(buf, buf + kReadSize, kMaxLineLength);
155     pos = kMaxLineLength;
156     bytes_requested = kReadSize;
157   }
158 }
159 
160 // Invokes android_dlopen_ext() to load the library into a given address range.
161 // Assumes that the address range is already reserved with mmap(2). On success,
162 // the |handle| of the loaded library is returned.
163 //
164 // Returns true iff this operation succeeds.
AndroidDlopenExt(void * mapping_start,size_t mapping_size,const char * filename,void ** handle)165 bool AndroidDlopenExt(void* mapping_start,
166                       size_t mapping_size,
167                       const char* filename,
168                       void** handle) {
169   android_dlextinfo dlextinfo = {};
170   dlextinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS;
171   dlextinfo.reserved_addr = mapping_start;
172   dlextinfo.reserved_size = mapping_size;
173 
174   LOG_INFO(
175       "android_dlopen_ext:"
176       " flags=0x%" PRIx64 ", reserved_addr=%p, reserved_size=%zu",
177       dlextinfo.flags, dlextinfo.reserved_addr, dlextinfo.reserved_size);
178 
179   void* rv = android_dlopen_ext(filename, RTLD_NOW, &dlextinfo);
180   if (rv == nullptr) {
181     LOG_ERROR("android_dlopen_ext: %s", dlerror());
182     return false;
183   }
184 
185   *handle = rv;
186   return true;
187 }
188 
189 // With munmap(2) unmaps the tail of the given contiguous range of virtual
190 // memory. Ignores errors.
TrimMapping(uintptr_t address,size_t old_size,size_t new_size)191 void TrimMapping(uintptr_t address, size_t old_size, size_t new_size) {
192   if (old_size <= new_size) {
193     LOG_ERROR("WARNING: library reservation was too small");
194   } else {
195     // Unmap the part of the reserved address space that is beyond the end of
196     // the loaded library data.
197     const uintptr_t unmap = address + new_size;
198     const size_t length = old_size - new_size;
199     munmap(reinterpret_cast<void*>(unmap), length);
200   }
201 }
202 
203 // Calls JNI_OnLoad() in the library referenced by |handle|.
204 // Returns true for success.
CallJniOnLoad(void * handle)205 bool CallJniOnLoad(void* handle) {
206   LOG_INFO("Entering");
207   // Locate and if found then call the loaded library's JNI_OnLoad() function.
208   using JNI_OnLoadFunctionPtr = int (*)(void* vm, void* reserved);
209   auto jni_onload =
210       reinterpret_cast<JNI_OnLoadFunctionPtr>(dlsym(handle, "JNI_OnLoad"));
211   if (jni_onload != nullptr) {
212     // Check that JNI_OnLoad returns a usable JNI version.
213     int jni_version = (*jni_onload)(s_java_vm, nullptr);
214     if (jni_version < JNI_VERSION_1_4) {
215       LOG_ERROR("JNI version is invalid: %d", jni_version);
216       return false;
217     }
218   }
219 
220   LOG_INFO("Done");
221   return true;
222 }
223 
224 }  // namespace
225 
String(JNIEnv * env,jstring str)226 String::String(JNIEnv* env, jstring str) {
227   size_ = env->GetStringUTFLength(str);
228   ptr_ = static_cast<char*>(::malloc(size_ + 1));
229 
230   // Note: This runs before browser native code is loaded, and so cannot
231   // rely on anything from base/. This means that we must use
232   // GetStringUTFChars() and not base::android::ConvertJavaStringToUTF8().
233   //
234   // GetStringUTFChars() suffices because the only strings used here are
235   // paths to APK files or names of shared libraries, all of which are
236   // plain ASCII, defined and hard-coded by the Chromium Android build.
237   //
238   // For more: see
239   //   https://crbug.com/508876
240   //
241   // Note: GetStringUTFChars() returns Java UTF-8 bytes. This is good
242   // enough for the linker though.
243   const char* bytes = env->GetStringUTFChars(str, nullptr);
244   ::memcpy(ptr_, bytes, size_);
245   ptr_[size_] = '\0';
246 
247   env->ReleaseStringUTFChars(str, bytes);
248 }
249 
IsValidAddress(jlong address)250 bool IsValidAddress(jlong address) {
251   bool result = static_cast<jlong>(static_cast<uintptr_t>(address)) == address;
252   if (!result) {
253     LOG_ERROR("Invalid address 0x%" PRIx64, static_cast<uint64_t>(address));
254   }
255   return result;
256 }
257 
258 // Finds the jclass JNI reference corresponding to a given |class_name|.
259 // |env| is the current JNI environment handle.
260 // On success, return true and set |*clazz|.
InitClassReference(JNIEnv * env,const char * class_name,jclass * clazz)261 bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) {
262   *clazz = env->FindClass(class_name);
263   if (!*clazz) {
264     LOG_ERROR("Could not find class for %s", class_name);
265     return false;
266   }
267   return true;
268 }
269 
270 // Initializes a jfieldID corresponding to the field of a given |clazz|,
271 // with name |field_name| and signature |field_sig|.
272 // |env| is the current JNI environment handle.
273 // On success, return true and set |*field_id|.
InitFieldId(JNIEnv * env,jclass clazz,const char * field_name,const char * field_sig,jfieldID * field_id)274 bool InitFieldId(JNIEnv* env,
275                  jclass clazz,
276                  const char* field_name,
277                  const char* field_sig,
278                  jfieldID* field_id) {
279   *field_id = env->GetFieldID(clazz, field_name, field_sig);
280   if (!*field_id) {
281     LOG_ERROR("Could not find ID for field '%s'", field_name);
282     return false;
283   }
284   LOG_INFO("Found ID %p for field '%s'", *field_id, field_name);
285   return true;
286 }
287 
FindWebViewReservation(uintptr_t * out_address,size_t * out_size)288 bool FindWebViewReservation(uintptr_t* out_address, size_t* out_size) {
289   // Note: reading /proc/PID/maps or /proc/PID/smaps is inherently racy. Among
290   // other things, the kernel provides these guarantees:
291   // * Each region record (line) is well formed
292   // * If there is something at a given vaddr during the entirety of the life of
293   //   the smaps/maps walk, there will be some output for it.
294   //
295   // In order for the address/size extraction to be safe, these precausions are
296   // made in base/android/linker:
297   // * Modification of the range is done only after this function exits
298   // * The use of the range is avoided if it is not sufficient in size, which
299   //   might happen if it gets split
300   const char kFileName[] = "/proc/self/maps";
301   int fd = HANDLE_EINTR(open(kFileName, O_RDONLY));
302   if (fd == -1) {
303     PLOG_ERROR("open %s", kFileName);
304     return false;
305   }
306 
307   bool result = FindRegionInOpenFile(fd, out_address, out_size);
308   close(fd);
309   return result;
310 }
311 
312 // Starting with API level 26 (Android O) the following functions from
313 // libandroid.so should be used to create shared memory regions to ensure
314 // compatibility with the future versions:
315 // * ASharedMemory_create()
316 // * ASharedMemory_setProt()
317 //
318 // This is inspired by //third_party/ashmem/ashmem-dev.c, which cannot be
319 // referenced from the linker library to avoid increasing binary size.
320 //
321 // *Not* threadsafe.
322 struct SharedMemoryFunctions {
SharedMemoryFunctionschromium_android_linker::SharedMemoryFunctions323   SharedMemoryFunctions() {
324     library_handle = dlopen("libandroid.so", RTLD_NOW);
325     create = reinterpret_cast<CreateFunction>(
326         dlsym(library_handle, "ASharedMemory_create"));
327     set_protection = reinterpret_cast<SetProtectionFunction>(
328         dlsym(library_handle, "ASharedMemory_setProt"));
329   }
330 
IsWorkingchromium_android_linker::SharedMemoryFunctions331   bool IsWorking() const {
332     if (!create || !set_protection) {
333       LOG_ERROR("Cannot get the shared memory functions from libandroid");
334       return false;
335     }
336     return true;
337   }
338 
~SharedMemoryFunctionschromium_android_linker::SharedMemoryFunctions339   ~SharedMemoryFunctions() {
340     if (library_handle)
341       dlclose(library_handle);
342   }
343 
344   typedef int (*CreateFunction)(const char*, size_t);
345   typedef int (*SetProtectionFunction)(int fd, int prot);
346 
347   CreateFunction create;
348   SetProtectionFunction set_protection;
349 
350   void* library_handle = nullptr;
351 };
352 
ExportLoadInfoToJava() const353 void NativeLibInfo::ExportLoadInfoToJava() const {
354   if (!env_)
355     return;
356   s_lib_info_fields.SetLoadInfo(env_, java_object_, load_address_, load_size_);
357 }
358 
ExportRelroInfoToJava() const359 void NativeLibInfo::ExportRelroInfoToJava() const {
360   if (!env_)
361     return;
362   s_lib_info_fields.SetRelroInfo(env_, java_object_, relro_start_, relro_size_,
363                                  relro_fd_);
364 }
365 
CloseRelroFd()366 void NativeLibInfo::CloseRelroFd() {
367   if (relro_fd_ == kInvalidFd)
368     return;
369   close(relro_fd_);
370   relro_fd_ = kInvalidFd;
371 }
372 
FindRelroAndLibraryRangesInElf()373 bool NativeLibInfo::FindRelroAndLibraryRangesInElf() {
374   LOG_INFO("Called for 0x%" PRIxPTR, load_address_);
375 
376   // Check that an ELF library starts at the |load_address_|.
377   if (memcmp(reinterpret_cast<void*>(load_address_), ELFMAG, SELFMAG) != 0) {
378     LOG_ERROR("Wrong magic number");
379     return false;
380   }
381   auto class_type = *reinterpret_cast<uint8_t*>(load_address_ + EI_CLASS);
382   if (class_type == ELFCLASS32) {
383     LOG_INFO("ELFCLASS32");
384   } else if (class_type == ELFCLASS64) {
385     LOG_INFO("ELFCLASS64");
386   } else {
387     LOG_ERROR("Could not determine ELF class");
388     return false;
389   }
390 
391   // Compute the ranges of PT_LOAD segments and the PT_GNU_RELRO. It is possible
392   // to reach for the same information by iterating over all loaded libraries
393   // and their program headers using dl_iterate_phdr(3). Instead here the
394   // iteration goes through the array |e_phoff[e_phnum]| to avoid acquisition of
395   // the global lock in Bionic (dlfcn.cpp).
396   //
397   // The code relies on (1) having RELRO in the PT_GNU_RELRO segment, and (2)
398   // the fact that the address *range* occupied by the library is the minimal
399   // address range containing all of the PT_LOAD and PT_GNU_RELRO segments.
400   // This is a contract between the static linker and the dynamic linker which
401   // seems unlikely to get broken. It might break though as a result of
402   // post-processing the DSO, which has historically happened for a few
403   // occasions (eliminating the unwind tables and splitting the library into
404   // DFMs).
405   auto min_vaddr = std::numeric_limits<ElfW(Addr)>::max();
406   auto min_relro_vaddr = min_vaddr;
407   ElfW(Addr) max_vaddr = 0;
408   ElfW(Addr) max_relro_vaddr = 0;
409   const auto* ehdr = reinterpret_cast<const ElfW(Ehdr)*>(load_address_);
410   const auto* phdrs =
411       reinterpret_cast<const ElfW(Phdr)*>(load_address_ + ehdr->e_phoff);
412   const size_t kPageSize = GetPageSize();
413   for (int i = 0; i < ehdr->e_phnum; i++) {
414     const ElfW(Phdr)* phdr = &phdrs[i];
415     switch (phdr->p_type) {
416       case PT_LOAD:
417         if (phdr->p_vaddr < min_vaddr)
418           min_vaddr = phdr->p_vaddr;
419         if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
420           max_vaddr = phdr->p_vaddr + phdr->p_memsz;
421         break;
422       case PT_GNU_RELRO:
423         min_relro_vaddr = PageStart(kPageSize, phdr->p_vaddr);
424         max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;
425 
426         // As of 2020-11 in libmonochrome.so RELRO is covered by a LOAD segment.
427         // It is not clear whether this property is going to be guaranteed in
428         // the future. Include the RELRO segment as part of the 'load size'.
429         // This way a potential future change in layout of LOAD segments would
430         // not open address space for racy mmap(MAP_FIXED).
431         if (min_relro_vaddr < min_vaddr)
432           min_vaddr = min_relro_vaddr;
433         if (max_vaddr < max_relro_vaddr)
434           max_vaddr = max_relro_vaddr;
435         break;
436       default:
437         break;
438     }
439   }
440 
441   // Fill out size and RELRO information.
442   load_size_ = PageEnd(kPageSize, max_vaddr) - PageStart(kPageSize, min_vaddr);
443   relro_size_ = PageEnd(kPageSize, max_relro_vaddr) -
444                 PageStart(kPageSize, min_relro_vaddr);
445   relro_start_ = load_address_ + PageStart(kPageSize, min_relro_vaddr);
446   return true;
447 }
448 
LoadWithDlopenExt(const String & path,void ** handle)449 bool NativeLibInfo::LoadWithDlopenExt(const String& path, void** handle) {
450   LOG_INFO("Entering");
451 
452   // The address range must be reserved during initialization in Linker.java.
453   if (!load_address_) {
454     // TODO(pasko): measure how often this happens.
455     return false;
456   }
457 
458   // Remember the memory reservation size. Starting from this point load_size_
459   // changes the meaning to reflect the size of the loaded library.
460   size_t reservation_size = load_size_;
461   auto* address = reinterpret_cast<void*>(load_address_);
462 
463   // Invoke android_dlopen_ext.
464   void* local_handle = nullptr;
465   if (!AndroidDlopenExt(address, reservation_size, path.c_str(),
466                         &local_handle)) {
467     LOG_ERROR("android_dlopen_ext() error");
468     munmap(address, load_size_);
469     return false;
470   }
471 
472   // Histogram ChromiumAndroidLinker.ModernLinkerDlopenExtTime that measured the
473   // amount of time the ModernLinker spends to run android_dlopen_ext() was
474   // removed in July 2023.
475 
476   // Determine the library address ranges and the RELRO region.
477   if (!FindRelroAndLibraryRangesInElf()) {
478     // Fail early if PT_GNU_RELRO is not found. It likely indicates a
479     // build misconfiguration.
480     LOG_ERROR("Could not find RELRO in the loaded library: %s", path.c_str());
481     abort();
482   }
483 
484   // Histogram ChromiumAndroidLinker.ModernLinkerIteratePhdrTime that measured
485   // the amount of time the ModernLinker spends to find the RELRO region using
486   // dl_iterate_phdr() was removed in July 2023.
487 
488   // Release the unused parts of the memory reservation.
489   TrimMapping(load_address_, reservation_size, load_size_);
490 
491   *handle = local_handle;
492   return true;
493 }
494 
CreateSharedRelroFd(const SharedMemoryFunctions & functions)495 bool NativeLibInfo::CreateSharedRelroFd(
496     const SharedMemoryFunctions& functions) {
497   LOG_INFO("Entering");
498   if (!relro_start_ || !relro_size_) {
499     LOG_ERROR("RELRO region is not populated");
500     return false;
501   }
502 
503   // Create a writable shared memory region.
504   int shared_mem_fd = functions.create("cr_relro", relro_size_);
505   if (shared_mem_fd == -1) {
506     LOG_ERROR("Cannot create the shared memory file");
507     return false;
508   }
509   int rw_flags = PROT_READ | PROT_WRITE;
510   functions.set_protection(shared_mem_fd, rw_flags);
511 
512   // Map the region as writable.
513   void* relro_copy_addr =
514       mmap(nullptr, relro_size_, rw_flags, MAP_SHARED, shared_mem_fd, 0);
515   if (relro_copy_addr == MAP_FAILED) {
516     PLOG_ERROR("failed to allocate space for copying RELRO");
517     close(shared_mem_fd);
518     return false;
519   }
520 
521   // Populate the shared memory region with the contents of RELRO.
522   void* relro_addr = reinterpret_cast<void*>(relro_start_);
523   memcpy(relro_copy_addr, relro_addr, relro_size_);
524 
525   // Protect the underlying physical pages from further modifications from all
526   // processes including the forked ones.
527   //
528   // Setting protection flags on the region to read-only guarantees that the
529   // memory can no longer get mapped as writable, for any FD pointing to the
530   // region, for any process. It is necessary to also munmap(2) the existing
531   // writable memory mappings, since they are not directly affected by the
532   // change of region's protection flags.
533   munmap(relro_copy_addr, relro_size_);
534   if (functions.set_protection(shared_mem_fd, PROT_READ) == -1) {
535     LOG_ERROR("Failed to set the RELRO FD as read-only.");
536     close(shared_mem_fd);
537     return false;
538   }
539 
540   relro_fd_ = shared_mem_fd;
541   return true;
542 }
543 
ReplaceRelroWithSharedOne(const SharedMemoryFunctions & functions) const544 bool NativeLibInfo::ReplaceRelroWithSharedOne(
545     const SharedMemoryFunctions& functions) const {
546   LOG_INFO("Entering");
547   if (relro_fd_ == -1 || !relro_start_ || !relro_size_) {
548     LOG_ERROR("Replacement RELRO not ready");
549     return false;
550   }
551 
552   // Map as read-only to *atomically* replace the RELRO region provided by the
553   // dynamic linker. To avoid memory corruption it is important that the
554   // contents of both memory regions is identical.
555   void* new_addr = mmap(reinterpret_cast<void*>(relro_start_), relro_size_,
556                         PROT_READ, MAP_FIXED | MAP_SHARED, relro_fd_, 0);
557   if (new_addr == MAP_FAILED) {
558     PLOG_ERROR("mmap: replace RELRO");
559     return false;
560   }
561 
562   LOG_INFO("Replaced RELRO at 0x%" PRIxPTR, relro_start_);
563   return true;
564 }
565 
NativeLibInfo(JNIEnv * env,jobject java_object)566 NativeLibInfo::NativeLibInfo(JNIEnv* env, jobject java_object)
567     : env_(env), java_object_(java_object) {}
568 
CopyFromJavaObject()569 bool NativeLibInfo::CopyFromJavaObject() {
570   if (!env_)
571     return false;
572 
573   if (!s_lib_info_fields.GetLoadInfo(env_, java_object_, &load_address_,
574                                      &load_size_)) {
575     return false;
576   }
577   s_lib_info_fields.GetRelroInfo(env_, java_object_, &relro_start_,
578                                  &relro_size_, &relro_fd_);
579   return true;
580 }
581 
LoadLibrary(const String & library_path,bool spawn_relro_region)582 bool NativeLibInfo::LoadLibrary(const String& library_path,
583                                 bool spawn_relro_region) {
584   // Load the library.
585   void* handle = nullptr;
586   if (!LoadWithDlopenExt(library_path, &handle)) {
587     LOG_ERROR("Failed to load native library: %s", library_path.c_str());
588     return false;
589   }
590   if (!CallJniOnLoad(handle))
591     return false;
592 
593   // Publish the library size and load address back to LibInfo in Java.
594   ExportLoadInfoToJava();
595 
596   if (!spawn_relro_region)
597     return true;
598 
599   // Spawn RELRO to a shared memory region by copying and remapping on top of
600   // itself.
601   SharedMemoryFunctions functions;
602   if (!functions.IsWorking())
603     return false;
604   if (!CreateSharedRelroFd(functions)) {
605     LOG_ERROR("Failed to create shared RELRO");
606     return false;
607   }
608   if (!ReplaceRelroWithSharedOne(functions)) {
609     LOG_ERROR("Failed to convert RELRO to shared memory");
610     CloseRelroFd();
611     return false;
612   }
613 
614   LOG_INFO(
615       "Created and converted RELRO to shared memory: relro_fd=%d, "
616       "relro_start=0x%" PRIxPTR,
617       relro_fd_, relro_start_);
618   ExportRelroInfoToJava();
619   return true;
620 }
621 
RelroIsIdentical(const NativeLibInfo & other_lib_info,const SharedMemoryFunctions & functions) const622 bool NativeLibInfo::RelroIsIdentical(
623     const NativeLibInfo& other_lib_info,
624     const SharedMemoryFunctions& functions) const {
625   // Abandon sharing if contents of the incoming RELRO region does not match the
626   // current one. This can be useful for debugging, but should never happen in
627   // the field.
628   if (other_lib_info.relro_start_ != relro_start_ ||
629       other_lib_info.relro_size_ != relro_size_ ||
630       other_lib_info.load_size_ != load_size_) {
631     LOG_ERROR("Incoming RELRO size does not match RELRO of the loaded library");
632     return false;
633   }
634   void* shared_relro_address =
635       mmap(nullptr, other_lib_info.relro_size_, PROT_READ, MAP_SHARED,
636            other_lib_info.relro_fd_, 0);
637   if (shared_relro_address == MAP_FAILED) {
638     PLOG_ERROR("mmap: check RELRO is identical");
639     return false;
640   }
641   void* current_relro_address = reinterpret_cast<void*>(relro_start_);
642   int not_equal =
643       memcmp(shared_relro_address, current_relro_address, relro_size_);
644   munmap(shared_relro_address, relro_size_);
645   if (not_equal) {
646     LOG_ERROR("Relocations are not identical, giving up.");
647     return false;
648   }
649   return true;
650 }
651 
CompareRelroAndReplaceItBy(const NativeLibInfo & other_lib_info)652 bool NativeLibInfo::CompareRelroAndReplaceItBy(
653     const NativeLibInfo& other_lib_info) {
654   if (other_lib_info.relro_fd_ == -1) {
655     LOG_ERROR("No shared region to use");
656     s_relro_sharing_status = RelroSharingStatus::EXTERNAL_RELRO_FD_NOT_PROVIDED;
657     return false;
658   }
659 
660   if (load_address_ == 0) {
661     LOG_ERROR("Load address reset. Second attempt to load the library?");
662     s_relro_sharing_status = RelroSharingStatus::EXTERNAL_LOAD_ADDRESS_RESET;
663     return false;
664   }
665 
666   if (!FindRelroAndLibraryRangesInElf()) {
667     LOG_ERROR("Could not find RELRO from externally provided address: 0x%p",
668               reinterpret_cast<void*>(other_lib_info.load_address_));
669     s_relro_sharing_status = RelroSharingStatus::EXTERNAL_RELRO_NOT_FOUND;
670     return false;
671   }
672 
673   SharedMemoryFunctions functions;
674   if (!functions.IsWorking()) {
675     s_relro_sharing_status = RelroSharingStatus::NO_SHMEM_FUNCTIONS;
676     return false;
677   }
678   if (!RelroIsIdentical(other_lib_info, functions)) {
679     LOG_ERROR("RELRO is not identical");
680     s_relro_sharing_status = RelroSharingStatus::NOT_IDENTICAL;
681     return false;
682   }
683 
684   // Make it shared.
685   //
686   // The alternative approach to invoke mprotect+mremap is probably faster than
687   // munmap+mmap here. The advantage of the latter is that it removes all
688   // formerly writable mappings, so:
689   //  * It does not rely on disallowing mprotect(PROT_WRITE)
690   //  * This way |ReplaceRelroWithSharedOne()| is reused across spawning RELRO
691   //    and receiving it
692   if (!other_lib_info.ReplaceRelroWithSharedOne(functions)) {
693     LOG_ERROR("Failed to use relro_fd");
694     s_relro_sharing_status = RelroSharingStatus::REMAP_FAILED;
695     return false;
696   }
697 
698   s_relro_sharing_status = RelroSharingStatus::SHARED;
699   return true;
700 }
701 
CreateSharedRelroFdForTesting()702 bool NativeLibInfo::CreateSharedRelroFdForTesting() {
703   // The library providing these functions will be dlclose()-ed after returning
704   // from this context. The extra overhead of dlopen() is OK for testing.
705   SharedMemoryFunctions functions;
706   if (!functions.IsWorking())
707     abort();
708   return CreateSharedRelroFd(functions);
709 }
710 
711 // static
SharedMemoryFunctionsSupportedForTesting()712 bool NativeLibInfo::SharedMemoryFunctionsSupportedForTesting() {
713   SharedMemoryFunctions functions;
714   return functions.IsWorking();
715 }
716 
717 JNI_BOUNDARY_EXPORT void
Java_org_chromium_base_library_1loader_LinkerJni_nativeFindMemoryRegionAtRandomAddress(JNIEnv * env,jclass clazz,jobject lib_info_obj)718 Java_org_chromium_base_library_1loader_LinkerJni_nativeFindMemoryRegionAtRandomAddress(
719     JNIEnv* env,
720     jclass clazz,
721     jobject lib_info_obj) {
722   LOG_INFO("Entering");
723   uintptr_t address;
724   size_t size;
725   ReserveAddressWithHint(0, &address, &size);
726   s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
727 }
728 
729 JNI_BOUNDARY_EXPORT void
Java_org_chromium_base_library_1loader_LinkerJni_nativeReserveMemoryForLibrary(JNIEnv * env,jclass clazz,jobject lib_info_obj)730 Java_org_chromium_base_library_1loader_LinkerJni_nativeReserveMemoryForLibrary(
731     JNIEnv* env,
732     jclass clazz,
733     jobject lib_info_obj) {
734   LOG_INFO("Entering");
735   uintptr_t address;
736   size_t size;
737   s_lib_info_fields.GetLoadInfo(env, lib_info_obj, &address, &size);
738   ReserveAddressWithHint(address, &address, &size);
739   s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
740 }
741 
742 JNI_BOUNDARY_EXPORT jboolean
Java_org_chromium_base_library_1loader_LinkerJni_nativeFindRegionReservedByWebViewZygote(JNIEnv * env,jclass clazz,jobject lib_info_obj)743 Java_org_chromium_base_library_1loader_LinkerJni_nativeFindRegionReservedByWebViewZygote(
744     JNIEnv* env,
745     jclass clazz,
746     jobject lib_info_obj) {
747   LOG_INFO("Entering");
748   uintptr_t address;
749   size_t size;
750   if (!FindWebViewReservation(&address, &size))
751     return false;
752   s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
753   return true;
754 }
755 
756 JNI_BOUNDARY_EXPORT jboolean
Java_org_chromium_base_library_1loader_LinkerJni_nativeLoadLibrary(JNIEnv * env,jclass clazz,jstring jdlopen_ext_path,jobject lib_info_obj,jboolean spawn_relro_region)757 Java_org_chromium_base_library_1loader_LinkerJni_nativeLoadLibrary(
758     JNIEnv* env,
759     jclass clazz,
760     jstring jdlopen_ext_path,
761     jobject lib_info_obj,
762     jboolean spawn_relro_region) {
763   LOG_INFO("Entering");
764 
765   // Copy the contents from the Java-side LibInfo object.
766   NativeLibInfo lib_info = {env, lib_info_obj};
767   if (!lib_info.CopyFromJavaObject())
768     return false;
769 
770   String library_path(env, jdlopen_ext_path);
771   if (!lib_info.LoadLibrary(library_path, spawn_relro_region)) {
772     return false;
773   }
774   return true;
775 }
776 
777 JNI_BOUNDARY_EXPORT jboolean
Java_org_chromium_base_library_1loader_LinkerJni_nativeUseRelros(JNIEnv * env,jclass clazz,jlong local_load_address,jobject remote_lib_info_obj)778 Java_org_chromium_base_library_1loader_LinkerJni_nativeUseRelros(
779     JNIEnv* env,
780     jclass clazz,
781     jlong local_load_address,
782     jobject remote_lib_info_obj) {
783   LOG_INFO("Entering");
784   // Copy the contents from the Java-side LibInfo object.
785   NativeLibInfo incoming_lib_info = {env, remote_lib_info_obj};
786   if (!incoming_lib_info.CopyFromJavaObject()) {
787     s_relro_sharing_status = RelroSharingStatus::CORRUPTED_IN_JAVA;
788     return false;
789   }
790 
791   // Create an empty NativeLibInfo to extract the current information about the
792   // loaded library and later compare with the contents of the
793   // |incoming_lib_info|.
794   NativeLibInfo lib_info = {nullptr, nullptr};
795   lib_info.set_load_address(static_cast<uintptr_t>(local_load_address));
796 
797   if (!lib_info.CompareRelroAndReplaceItBy(incoming_lib_info)) {
798     return false;
799   }
800   return true;
801 }
802 
803 JNI_BOUNDARY_EXPORT jint
Java_org_chromium_base_library_1loader_LinkerJni_nativeGetRelroSharingResult(JNIEnv * env,jclass clazz)804 Java_org_chromium_base_library_1loader_LinkerJni_nativeGetRelroSharingResult(
805     JNIEnv* env,
806     jclass clazz) {
807   return static_cast<jint>(s_relro_sharing_status);
808 }
809 
LinkerJNIInit(JavaVM * vm,JNIEnv * env)810 bool LinkerJNIInit(JavaVM* vm, JNIEnv* env) {
811   // Find LibInfo field ids.
812   if (!s_lib_info_fields.Init(env)) {
813     return false;
814   }
815 
816   s_java_vm = vm;
817   return true;
818 }
819 
820 }  // namespace chromium_android_linker
821