Home | History | Annotate | Download | only in linker
      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 // This is the Android-specific Chromium linker, a tiny shared library
      6 // implementing a custom dynamic linker that can be used to load the
      7 // real Chromium libraries (e.g. libcontentshell.so).
      8 
      9 // The main point of this linker is to be able to share the RELRO
     10 // section of libcontentshell.so (or equivalent) between the browser and
     11 // renderer process.
     12 
     13 // This source code *cannot* depend on anything from base/ or the C++
     14 // STL, to keep the final library small, and avoid ugly dependency issues.
     15 
     16 #include <android/log.h>
     17 #include <crazy_linker.h>
     18 #include <jni.h>
     19 #include <stdlib.h>
     20 #include <unistd.h>
     21 
     22 // Set this to 1 to enable debug traces to the Android log.
     23 // Note that LOG() from "base/logging.h" cannot be used, since it is
     24 // in base/ which hasn't been loaded yet.
     25 #define DEBUG 0
     26 
     27 #define TAG "chromium_android_linker"
     28 
     29 #if DEBUG
     30 #define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
     31 #else
     32 #define LOG_INFO(...) ((void)0)
     33 #endif
     34 #define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
     35 
     36 #define UNUSED __attribute__((unused))
     37 
     38 namespace {
     39 
     40 // A simply scoped UTF String class that can be initialized from
     41 // a Java jstring handle. Modeled like std::string, which cannot
     42 // be used here.
     43 class String {
     44  public:
     45   String(JNIEnv* env, jstring str);
     46 
     47   ~String() {
     48     if (ptr_)
     49       ::free(ptr_);
     50   }
     51 
     52   const char* c_str() const { return ptr_ ? ptr_ : ""; }
     53   size_t size() const { return size_; }
     54 
     55  private:
     56   char* ptr_;
     57   size_t size_;
     58 };
     59 
     60 String::String(JNIEnv* env, jstring str) {
     61   size_ = env->GetStringUTFLength(str);
     62   ptr_ = static_cast<char*>(::malloc(size_ + 1));
     63 
     64   // Note: the result contains Java "modified UTF-8" bytes.
     65   // Good enough for the linker though.
     66   const char* bytes = env->GetStringUTFChars(str, NULL);
     67   ::memcpy(ptr_, bytes, size_);
     68   ptr_[size_] = '\0';
     69 
     70   env->ReleaseStringUTFChars(str, bytes);
     71 }
     72 
     73 // Return a pointer to the base name from an input |path| string.
     74 const char* GetBaseNamePtr(const char* path) {
     75   const char* p = strrchr(path, '/');
     76   if (p)
     77     return p + 1;
     78   return path;
     79 }
     80 
     81 // Return true iff |address| is a valid address for the target CPU.
     82 bool IsValidAddress(jlong address) {
     83   return static_cast<jlong>(static_cast<size_t>(address)) == address;
     84 }
     85 
     86 // Find the jclass JNI reference corresponding to a given |class_name|.
     87 // |env| is the current JNI environment handle.
     88 // On success, return true and set |*clazz|.
     89 bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) {
     90   *clazz = env->FindClass(class_name);
     91   if (!*clazz) {
     92     LOG_ERROR("Could not find class for %s", class_name);
     93     return false;
     94   }
     95   return true;
     96 }
     97 
     98 // Initialize a jfieldID corresponding to the field of a given |clazz|,
     99 // with name |field_name| and signature |field_sig|.
    100 // |env| is the current JNI environment handle.
    101 // On success, return true and set |*field_id|.
    102 bool InitFieldId(JNIEnv* env,
    103                  jclass clazz,
    104                  const char* field_name,
    105                  const char* field_sig,
    106                  jfieldID* field_id) {
    107   *field_id = env->GetFieldID(clazz, field_name, field_sig);
    108   if (!*field_id) {
    109     LOG_ERROR("Could not find ID for field '%s'", field_name);
    110     return false;
    111   }
    112   LOG_INFO(
    113       "%s: Found ID %p for field '%s'", __FUNCTION__, *field_id, field_name);
    114   return true;
    115 }
    116 
    117 // Initialize a jmethodID corresponding to the static method of a given
    118 // |clazz|, with name |method_name| and signature |method_sig|.
    119 // |env| is the current JNI environment handle.
    120 // On success, return true and set |*method_id|.
    121 bool InitStaticMethodId(JNIEnv* env,
    122                         jclass clazz,
    123                         const char* method_name,
    124                         const char* method_sig,
    125                         jmethodID* method_id) {
    126   *method_id = env->GetStaticMethodID(clazz, method_name, method_sig);
    127   if (!*method_id) {
    128     LOG_ERROR("Could not find ID for static method '%s'", method_name);
    129     return false;
    130   }
    131   LOG_INFO("%s: Found ID %p for static method '%s'",
    132            __FUNCTION__, *method_id, method_name);
    133   return true;
    134 }
    135 
    136 // A class used to model the field IDs of the org.chromium.base.Linker
    137 // LibInfo inner class, used to communicate data with the Java side
    138 // of the linker.
    139 struct LibInfo_class {
    140   jfieldID load_address_id;
    141   jfieldID load_size_id;
    142   jfieldID relro_start_id;
    143   jfieldID relro_size_id;
    144   jfieldID relro_fd_id;
    145 
    146   // Initialize an instance.
    147   bool Init(JNIEnv* env) {
    148     jclass clazz;
    149     if (!InitClassReference(
    150              env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) {
    151       return false;
    152     }
    153 
    154     return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) &&
    155            InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) &&
    156            InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) &&
    157            InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) &&
    158            InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id);
    159   }
    160 
    161   void SetLoadInfo(JNIEnv* env,
    162                    jobject library_info_obj,
    163                    size_t load_address,
    164                    size_t load_size) {
    165     env->SetLongField(library_info_obj, load_address_id, load_address);
    166     env->SetLongField(library_info_obj, load_size_id, load_size);
    167   }
    168 
    169   // Use this instance to convert a RelroInfo reference into
    170   // a crazy_library_info_t.
    171   void GetRelroInfo(JNIEnv* env,
    172                     jobject library_info_obj,
    173                     size_t* relro_start,
    174                     size_t* relro_size,
    175                     int* relro_fd) {
    176     *relro_start = static_cast<size_t>(
    177         env->GetLongField(library_info_obj, relro_start_id));
    178 
    179     *relro_size =
    180         static_cast<size_t>(env->GetLongField(library_info_obj, relro_size_id));
    181 
    182     *relro_fd = env->GetIntField(library_info_obj, relro_fd_id);
    183   }
    184 
    185   void SetRelroInfo(JNIEnv* env,
    186                     jobject library_info_obj,
    187                     size_t relro_start,
    188                     size_t relro_size,
    189                     int relro_fd) {
    190     env->SetLongField(library_info_obj, relro_start_id, relro_start);
    191     env->SetLongField(library_info_obj, relro_size_id, relro_size);
    192     env->SetIntField(library_info_obj, relro_fd_id, relro_fd);
    193   }
    194 };
    195 
    196 static LibInfo_class s_lib_info_fields;
    197 
    198 // The linker uses a single crazy_context_t object created on demand.
    199 // There is no need to protect this against concurrent access, locking
    200 // is already handled on the Java side.
    201 static crazy_context_t* s_crazy_context;
    202 
    203 crazy_context_t* GetCrazyContext() {
    204   if (!s_crazy_context) {
    205     // Create new context.
    206     s_crazy_context = crazy_context_create();
    207 
    208     // Ensure libraries located in the same directory as the linker
    209     // can be loaded before system ones.
    210     crazy_context_add_search_path_for_address(
    211         s_crazy_context, reinterpret_cast<void*>(&s_crazy_context));
    212   }
    213 
    214   return s_crazy_context;
    215 }
    216 
    217 // A scoped crazy_library_t that automatically closes the handle
    218 // on scope exit, unless Release() has been called.
    219 class ScopedLibrary {
    220  public:
    221   ScopedLibrary() : lib_(NULL) {}
    222 
    223   ~ScopedLibrary() {
    224     if (lib_)
    225       crazy_library_close_with_context(lib_, GetCrazyContext());
    226   }
    227 
    228   crazy_library_t* Get() { return lib_; }
    229 
    230   crazy_library_t** GetPtr() { return &lib_; }
    231 
    232   crazy_library_t* Release() {
    233     crazy_library_t* ret = lib_;
    234     lib_ = NULL;
    235     return ret;
    236   }
    237 
    238  private:
    239   crazy_library_t* lib_;
    240 };
    241 
    242 // Load a library with the chromium linker. This will also call its
    243 // JNI_OnLoad() method, which shall register its methods. Note that
    244 // lazy native method resolution will _not_ work after this, because
    245 // Dalvik uses the system's dlsym() which won't see the new library,
    246 // so explicit registration is mandatory.
    247 // |env| is the current JNI environment handle.
    248 // |clazz| is the static class handle for org.chromium.base.Linker,
    249 // and is ignored here.
    250 // |library_name| is the library name (e.g. libfoo.so).
    251 // |load_address| is an explicit load address.
    252 // |library_info| is a LibInfo handle used to communicate information
    253 // with the Java side.
    254 // Return true on success.
    255 jboolean LoadLibrary(JNIEnv* env,
    256                      jclass clazz,
    257                      jstring library_name,
    258                      jlong load_address,
    259                      jobject lib_info_obj) {
    260   String lib_name(env, library_name);
    261   const char* UNUSED lib_basename = GetBaseNamePtr(lib_name.c_str());
    262 
    263   crazy_context_t* context = GetCrazyContext();
    264 
    265   if (!IsValidAddress(load_address)) {
    266     LOG_ERROR("%s: Invalid address 0x%llx", __FUNCTION__, load_address);
    267     return false;
    268   }
    269 
    270   // Set the desired load address (0 means randomize it).
    271   crazy_context_set_load_address(context, static_cast<size_t>(load_address));
    272 
    273   // Open the library now.
    274   LOG_INFO("%s: Opening shared library: %s", __FUNCTION__, lib_name.c_str());
    275 
    276   ScopedLibrary library;
    277   if (!crazy_library_open(library.GetPtr(), lib_name.c_str(), context)) {
    278     LOG_ERROR("%s: Could not open %s: %s",
    279               __FUNCTION__,
    280               lib_basename,
    281               crazy_context_get_error(context));
    282     return false;
    283   }
    284 
    285   crazy_library_info_t info;
    286   if (!crazy_library_get_info(library.Get(), context, &info)) {
    287     LOG_ERROR("%s: Could not get library information for %s: %s",
    288               __FUNCTION__,
    289               lib_basename,
    290               crazy_context_get_error(context));
    291     return false;
    292   }
    293 
    294   // Release library object to keep it alive after the function returns.
    295   library.Release();
    296 
    297   s_lib_info_fields.SetLoadInfo(
    298       env, lib_info_obj, info.load_address, info.load_size);
    299   LOG_INFO("%s: Success loading library %s", __FUNCTION__, lib_basename);
    300   return true;
    301 }
    302 
    303 // Class holding the Java class and method ID for the Java side Linker
    304 // postCallbackOnMainThread method.
    305 struct JavaCallbackBindings_class {
    306   jclass clazz;
    307   jmethodID method_id;
    308 
    309   // Initialize an instance.
    310   bool Init(JNIEnv* env, jclass linker_class) {
    311     clazz = reinterpret_cast<jclass>(env->NewGlobalRef(linker_class));
    312     return InitStaticMethodId(env,
    313                               linker_class,
    314                               "postCallbackOnMainThread",
    315                               "(J)V",
    316                               &method_id);
    317   }
    318 };
    319 
    320 static JavaCallbackBindings_class s_java_callback_bindings;
    321 
    322 // Designated receiver function for callbacks from Java. Its name is known
    323 // to the Java side.
    324 // |env| is the current JNI environment handle and is ignored here.
    325 // |clazz| is the static class handle for org.chromium.base.Linker,
    326 // and is ignored here.
    327 // |arg| is a pointer to an allocated crazy_callback_t, deleted after use.
    328 void RunCallbackOnUiThread(JNIEnv* env, jclass clazz, jlong arg) {
    329   crazy_callback_t* callback = reinterpret_cast<crazy_callback_t*>(arg);
    330 
    331   LOG_INFO("%s: Called back from java with handler %p, opaque %p",
    332            __FUNCTION__, callback->handler, callback->opaque);
    333 
    334   crazy_callback_run(callback);
    335   delete callback;
    336 }
    337 
    338 // Request a callback from Java. The supplied crazy_callback_t is valid only
    339 // for the duration of this call, so we copy it to a newly allocated
    340 // crazy_callback_t and then call the Java side's postCallbackOnMainThread.
    341 // This will call back to to our RunCallbackOnUiThread some time
    342 // later on the UI thread.
    343 // |callback_request| is a crazy_callback_t.
    344 // |poster_opaque| is unused.
    345 // Returns true if the callback request succeeds.
    346 static bool PostForLaterExecution(crazy_callback_t* callback_request,
    347                                   void* poster_opaque UNUSED) {
    348   crazy_context_t* context = GetCrazyContext();
    349 
    350   JavaVM* vm;
    351   int minimum_jni_version;
    352   crazy_context_get_java_vm(context,
    353                             reinterpret_cast<void**>(&vm),
    354                             &minimum_jni_version);
    355 
    356   // Do not reuse JNIEnv from JNI_OnLoad, but retrieve our own.
    357   JNIEnv* env;
    358   if (JNI_OK != vm->GetEnv(
    359       reinterpret_cast<void**>(&env), minimum_jni_version)) {
    360     LOG_ERROR("Could not create JNIEnv");
    361     return false;
    362   }
    363 
    364   // Copy the callback; the one passed as an argument may be temporary.
    365   crazy_callback_t* callback = new crazy_callback_t();
    366   *callback = *callback_request;
    367 
    368   LOG_INFO("%s: Calling back to java with handler %p, opaque %p",
    369            __FUNCTION__, callback->handler, callback->opaque);
    370 
    371   jlong arg = static_cast<jlong>(reinterpret_cast<intptr_t>(callback));
    372   env->CallStaticVoidMethod(
    373       s_java_callback_bindings.clazz, s_java_callback_bindings.method_id, arg);
    374 
    375   // Back out and return false if we encounter a JNI exception.
    376   if (env->ExceptionCheck() == JNI_TRUE) {
    377     env->ExceptionDescribe();
    378     env->ExceptionClear();
    379     delete callback;
    380     return false;
    381   }
    382 
    383   return true;
    384 }
    385 
    386 jboolean CreateSharedRelro(JNIEnv* env,
    387                            jclass clazz,
    388                            jstring library_name,
    389                            jlong load_address,
    390                            jobject lib_info_obj) {
    391   String lib_name(env, library_name);
    392 
    393   LOG_INFO("%s: Called for %s", __FUNCTION__, lib_name.c_str());
    394 
    395   if (!IsValidAddress(load_address)) {
    396     LOG_ERROR("%s: Invalid address 0x%llx", __FUNCTION__, load_address);
    397     return false;
    398   }
    399 
    400   ScopedLibrary library;
    401   if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
    402     LOG_ERROR("%s: Could not find %s", __FUNCTION__, lib_name.c_str());
    403     return false;
    404   }
    405 
    406   crazy_context_t* context = GetCrazyContext();
    407   size_t relro_start = 0;
    408   size_t relro_size = 0;
    409   int relro_fd = -1;
    410 
    411   if (!crazy_library_create_shared_relro(library.Get(),
    412                                          context,
    413                                          static_cast<size_t>(load_address),
    414                                          &relro_start,
    415                                          &relro_size,
    416                                          &relro_fd)) {
    417     LOG_ERROR("%s: Could not create shared RELRO sharing for %s: %s\n",
    418               __FUNCTION__,
    419               lib_name.c_str(),
    420               crazy_context_get_error(context));
    421     return false;
    422   }
    423 
    424   s_lib_info_fields.SetRelroInfo(
    425       env, lib_info_obj, relro_start, relro_size, relro_fd);
    426   return true;
    427 }
    428 
    429 jboolean UseSharedRelro(JNIEnv* env,
    430                         jclass clazz,
    431                         jstring library_name,
    432                         jobject lib_info_obj) {
    433   String lib_name(env, library_name);
    434 
    435   LOG_INFO("%s: called for %s, lib_info_ref=%p",
    436            __FUNCTION__,
    437            lib_name.c_str(),
    438            lib_info_obj);
    439 
    440   ScopedLibrary library;
    441   if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
    442     LOG_ERROR("%s: Could not find %s", __FUNCTION__, lib_name.c_str());
    443     return false;
    444   }
    445 
    446   crazy_context_t* context = GetCrazyContext();
    447   size_t relro_start = 0;
    448   size_t relro_size = 0;
    449   int relro_fd = -1;
    450   s_lib_info_fields.GetRelroInfo(
    451       env, lib_info_obj, &relro_start, &relro_size, &relro_fd);
    452 
    453   LOG_INFO("%s: library=%s relro start=%p size=%p fd=%d",
    454            __FUNCTION__,
    455            lib_name.c_str(),
    456            (void*)relro_start,
    457            (void*)relro_size,
    458            relro_fd);
    459 
    460   if (!crazy_library_use_shared_relro(
    461            library.Get(), context, relro_start, relro_size, relro_fd)) {
    462     LOG_ERROR("%s: Could not use shared RELRO for %s: %s",
    463               __FUNCTION__,
    464               lib_name.c_str(),
    465               crazy_context_get_error(context));
    466     return false;
    467   }
    468 
    469   LOG_INFO("%s: Library %s using shared RELRO section!",
    470            __FUNCTION__,
    471            lib_name.c_str());
    472 
    473   return true;
    474 }
    475 
    476 jboolean CanUseSharedRelro(JNIEnv* env, jclass clazz) {
    477   return crazy_system_can_share_relro();
    478 }
    479 
    480 jlong GetPageSize(JNIEnv* env, jclass clazz) {
    481   jlong result = static_cast<jlong>(sysconf(_SC_PAGESIZE));
    482   LOG_INFO("%s: System page size is %lld bytes\n", __FUNCTION__, result);
    483   return result;
    484 }
    485 
    486 const JNINativeMethod kNativeMethods[] = {
    487     {"nativeLoadLibrary",
    488      "("
    489      "Ljava/lang/String;"
    490      "J"
    491      "Lorg/chromium/base/library_loader/Linker$LibInfo;"
    492      ")"
    493      "Z",
    494      reinterpret_cast<void*>(&LoadLibrary)},
    495     {"nativeRunCallbackOnUiThread",
    496      "("
    497      "J"
    498      ")"
    499      "V",
    500      reinterpret_cast<void*>(&RunCallbackOnUiThread)},
    501     {"nativeCreateSharedRelro",
    502      "("
    503      "Ljava/lang/String;"
    504      "J"
    505      "Lorg/chromium/base/library_loader/Linker$LibInfo;"
    506      ")"
    507      "Z",
    508      reinterpret_cast<void*>(&CreateSharedRelro)},
    509     {"nativeUseSharedRelro",
    510      "("
    511      "Ljava/lang/String;"
    512      "Lorg/chromium/base/library_loader/Linker$LibInfo;"
    513      ")"
    514      "Z",
    515      reinterpret_cast<void*>(&UseSharedRelro)},
    516     {"nativeCanUseSharedRelro",
    517      "("
    518      ")"
    519      "Z",
    520      reinterpret_cast<void*>(&CanUseSharedRelro)},
    521     {"nativeGetPageSize",
    522      "("
    523      ")"
    524      "J",
    525      reinterpret_cast<void*>(&GetPageSize)}, };
    526 
    527 }  // namespace
    528 
    529 // JNI_OnLoad() hook called when the linker library is loaded through
    530 // the regular System.LoadLibrary) API. This shall save the Java VM
    531 // handle and initialize LibInfo fields.
    532 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    533   LOG_INFO("%s: Entering", __FUNCTION__);
    534   // Get new JNIEnv
    535   JNIEnv* env;
    536   if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) {
    537     LOG_ERROR("Could not create JNIEnv");
    538     return -1;
    539   }
    540 
    541   // Register native methods.
    542   jclass linker_class;
    543   if (!InitClassReference(env,
    544                           "org/chromium/base/library_loader/Linker",
    545                           &linker_class))
    546     return -1;
    547 
    548   LOG_INFO("%s: Registering native methods", __FUNCTION__);
    549   env->RegisterNatives(linker_class,
    550                        kNativeMethods,
    551                        sizeof(kNativeMethods) / sizeof(kNativeMethods[0]));
    552 
    553   // Find LibInfo field ids.
    554   LOG_INFO("%s: Caching field IDs", __FUNCTION__);
    555   if (!s_lib_info_fields.Init(env)) {
    556     return -1;
    557   }
    558 
    559   // Resolve and save the Java side Linker callback class and method.
    560   LOG_INFO("%s: Resolving callback bindings", __FUNCTION__);
    561   if (!s_java_callback_bindings.Init(env, linker_class)) {
    562     return -1;
    563   }
    564 
    565   // Save JavaVM* handle into context.
    566   crazy_context_t* context = GetCrazyContext();
    567   crazy_context_set_java_vm(context, vm, JNI_VERSION_1_4);
    568 
    569   // Register the function that the crazy linker can call to post code
    570   // for later execution.
    571   crazy_context_set_callback_poster(context, &PostForLaterExecution, NULL);
    572 
    573   LOG_INFO("%s: Done", __FUNCTION__);
    574   return JNI_VERSION_1_4;
    575 }
    576