Home | History | Annotate | Download | only in library_loader
      1 // Copyright 2015 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 package org.chromium.base.library_loader;
      6 
      7 import android.os.Bundle;
      8 import android.os.SystemClock;
      9 
     10 import org.chromium.base.Log;
     11 import org.chromium.base.PathUtils;
     12 import org.chromium.base.annotations.SuppressFBWarnings;
     13 
     14 import java.util.HashMap;
     15 import java.util.Locale;
     16 
     17 import javax.annotation.Nullable;
     18 
     19 /*
     20  * For more, see Technical note, Security considerations, and the explanation
     21  * of how this class is supposed to be used in Linker.java.
     22  */
     23 
     24 /**
     25  * Provides a concrete implementation of the Chromium Linker.
     26  *
     27  * This Linker implementation uses the Android M and later system linker to map and then
     28  * run Chrome for Android.
     29  *
     30  * For more on the operations performed by the Linker, see {@link Linker}.
     31  */
     32 class ModernLinker extends Linker {
     33     // Log tag for this class.
     34     private static final String TAG = "LibraryLoader";
     35 
     36     // Becomes true after linker initialization.
     37     private boolean mInitialized = false;
     38 
     39     // Becomes true to indicate this process needs to wait for a shared RELRO in LibraryLoad().
     40     private boolean mWaitForSharedRelros = false;
     41 
     42     // The map of all RELRO sections either created or used in this process.
     43     private HashMap<String, LibInfo> mSharedRelros = null;
     44 
     45     // Cached Bundle representation of the RELRO sections map for transfer across processes.
     46     private Bundle mSharedRelrosBundle = null;
     47 
     48     // Set to true if this runs in the browser process. Disabled by initServiceProcess().
     49     private boolean mInBrowserProcess = true;
     50 
     51     // Current common random base load address. A value of -1 indicates not yet initialized.
     52     private long mBaseLoadAddress = -1;
     53 
     54     // Current fixed-location load address for the next library called by loadLibrary().
     55     // Initialized to mBaseLoadAddress in prepareLibraryLoad(), and then adjusted as each
     56     // library is loaded by loadLibrary().
     57     private long mCurrentLoadAddress = -1;
     58 
     59     // Becomes true once prepareLibraryLoad() has been called.
     60     private boolean mPrepareLibraryLoadCalled = false;
     61 
     62     // The map of libraries that are currently loaded in this process.
     63     private HashMap<String, LibInfo> mLoadedLibraries = null;
     64 
     65     // Private singleton constructor, and singleton factory method.
     66     private ModernLinker() { }
     67     static Linker create() {
     68         return new ModernLinker();
     69     }
     70 
     71     // Used internally to initialize the linker's data. Assumes lock is held.
     72     private void ensureInitializedLocked() {
     73         assert Thread.holdsLock(mLock);
     74         assert NativeLibraries.sUseLinker;
     75 
     76         // On first call, load libchromium_android_linker.so. Cannot be done in the
     77         // constructor because the instance is constructed on the UI thread.
     78         if (!mInitialized) {
     79             loadLinkerJniLibrary();
     80             mInitialized = true;
     81         }
     82     }
     83 
     84     /**
     85      * Call this method to determine if the linker will try to use shared RELROs
     86      * for the browser process.
     87      */
     88     @Override
     89     public boolean isUsingBrowserSharedRelros() {
     90         // This Linker does not attempt to share RELROS between the browser and
     91         // the renderers, but only between renderers.
     92         return false;
     93     }
     94 
     95     /**
     96      * Call this method just before loading any native shared libraries in this process.
     97      * Loads the Linker's JNI library, and initializes the variables involved in the
     98      * implementation of shared RELROs.
     99      */
    100     @Override
    101     public void prepareLibraryLoad() {
    102         if (DEBUG) {
    103             Log.i(TAG, "prepareLibraryLoad() called");
    104         }
    105         assert NativeLibraries.sUseLinker;
    106 
    107         synchronized (mLock) {
    108             assert !mPrepareLibraryLoadCalled;
    109             ensureInitializedLocked();
    110 
    111             // If in the browser, generate a random base load address for service processes
    112             // and create an empty shared RELROs map. For service processes, the shared
    113             // RELROs map must remain null until set by useSharedRelros().
    114             if (mInBrowserProcess) {
    115                 setupBaseLoadAddressLocked();
    116                 mSharedRelros = new HashMap<String, LibInfo>();
    117             }
    118 
    119             // Create an empty loaded libraries map.
    120             mLoadedLibraries = new HashMap<String, LibInfo>();
    121 
    122             // Start the current load address at the base load address.
    123             mCurrentLoadAddress = mBaseLoadAddress;
    124 
    125             mPrepareLibraryLoadCalled = true;
    126         }
    127     }
    128 
    129     /**
    130      * Call this method just after loading all native shared libraries in this process.
    131      * If not in the browser, closes the LibInfo entries used for RELRO sharing.
    132      */
    133     @Override
    134     public void finishLibraryLoad() {
    135         if (DEBUG) {
    136             Log.i(TAG, "finishLibraryLoad() called");
    137         }
    138 
    139         synchronized (mLock) {
    140             assert mPrepareLibraryLoadCalled;
    141 
    142             // Close shared RELRO file descriptors if not in the browser.
    143             if (!mInBrowserProcess && mSharedRelros != null) {
    144                 closeLibInfoMap(mSharedRelros);
    145                 mSharedRelros = null;
    146             }
    147 
    148             // If testing, run tests now that all libraries are loaded and initialized.
    149             if (NativeLibraries.sEnableLinkerTests) {
    150                 runTestRunnerClassForTesting(0, mInBrowserProcess);
    151             }
    152         }
    153     }
    154 
    155     // Used internally to wait for shared RELROs. Returns once useSharedRelros() has been
    156     // called to supply a valid shared RELROs bundle.
    157     @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    158     private void waitForSharedRelrosLocked() {
    159         if (DEBUG) {
    160             Log.i(TAG, "waitForSharedRelros called");
    161         }
    162         assert Thread.holdsLock(mLock);
    163 
    164         // Return immediately if shared RELROs are already available.
    165         if (mSharedRelros != null) {
    166             return;
    167         }
    168 
    169         // Wait until notified by useSharedRelros() that shared RELROs have arrived.
    170         long startTime = DEBUG ? SystemClock.uptimeMillis() : 0;
    171         while (mSharedRelros == null) {
    172             try {
    173                 mLock.wait();
    174             } catch (InterruptedException e) {
    175                 // Restore the thread's interrupt status.
    176                 Thread.currentThread().interrupt();
    177             }
    178         }
    179 
    180         if (DEBUG) {
    181             Log.i(TAG, String.format(
    182                     Locale.US, "Time to wait for shared RELRO: %d ms",
    183                     SystemClock.uptimeMillis() - startTime));
    184         }
    185     }
    186 
    187     /**
    188      * Call this to send a Bundle containing the shared RELRO sections to be
    189      * used in this process. If initServiceProcess() was previously called,
    190      * libraryLoad() will wait until this method is called in another
    191      * thread with a non-null value.
    192      *
    193      * @param bundle The Bundle instance containing a map of shared RELRO sections
    194      * to use in this process.
    195      */
    196     @Override
    197     public void useSharedRelros(Bundle bundle) {
    198         if (DEBUG) {
    199             Log.i(TAG, "useSharedRelros() called with " + bundle);
    200         }
    201 
    202         synchronized (mLock) {
    203             mSharedRelros = createLibInfoMapFromBundle(bundle);
    204             mLock.notifyAll();
    205         }
    206     }
    207 
    208     /**
    209      * Call this to retrieve the shared RELRO sections created in this process,
    210      * after loading all libraries.
    211      *
    212      * @return a new Bundle instance, or null if RELRO sharing is disabled on
    213      * this system, or if initServiceProcess() was called previously.
    214      */
    215     @Override
    216     public Bundle getSharedRelros() {
    217         if (DEBUG) {
    218             Log.i(TAG, "getSharedRelros() called");
    219         }
    220         synchronized (mLock) {
    221             if (!mInBrowserProcess) {
    222                 if (DEBUG) {
    223                     Log.i(TAG, "Not in browser, so returning null Bundle");
    224                 }
    225                 return null;
    226             }
    227 
    228             // Create a new Bundle containing RELRO section information for all the shared
    229             // RELROs created while loading libraries.
    230             if (mSharedRelrosBundle == null && mSharedRelros != null) {
    231                 mSharedRelrosBundle = createBundleFromLibInfoMap(mSharedRelros);
    232                 if (DEBUG) {
    233                     Log.i(TAG, "Shared RELRO bundle created from map: " + mSharedRelrosBundle);
    234                 }
    235             }
    236             if (DEBUG) {
    237                 Log.i(TAG, "Returning " + mSharedRelrosBundle);
    238             }
    239             return mSharedRelrosBundle;
    240         }
    241     }
    242 
    243 
    244     /**
    245      * Call this method before loading any libraries to indicate that this
    246      * process shall neither create or reuse shared RELRO sections.
    247      */
    248     @Override
    249     public void disableSharedRelros() {
    250         if (DEBUG) {
    251             Log.i(TAG, "disableSharedRelros() called");
    252         }
    253         synchronized (mLock) {
    254             // Mark this as a service process, and disable wait for shared RELRO.
    255             mInBrowserProcess = false;
    256             mWaitForSharedRelros = false;
    257         }
    258     }
    259 
    260     /**
    261      * Call this method before loading any libraries to indicate that this
    262      * process is ready to reuse shared RELRO sections from another one.
    263      * Typically used when starting service processes.
    264      *
    265      * @param baseLoadAddress the base library load address to use.
    266      */
    267     @Override
    268     public void initServiceProcess(long baseLoadAddress) {
    269         if (DEBUG) {
    270             Log.i(TAG, String.format(
    271                     Locale.US, "initServiceProcess(0x%x) called",
    272                     baseLoadAddress));
    273         }
    274         synchronized (mLock) {
    275             assert !mPrepareLibraryLoadCalled;
    276 
    277             // Mark this as a service process, and flag wait for shared RELRO.
    278             // Save the base load address passed in.
    279             mInBrowserProcess = false;
    280             mWaitForSharedRelros = true;
    281             mBaseLoadAddress = baseLoadAddress;
    282         }
    283     }
    284 
    285     /**
    286      * Retrieve the base load address for libraries that share RELROs.
    287      *
    288      * @return a common, random base load address, or 0 if RELRO sharing is
    289      * disabled.
    290      */
    291     @Override
    292     public long getBaseLoadAddress() {
    293         synchronized (mLock) {
    294             ensureInitializedLocked();
    295             setupBaseLoadAddressLocked();
    296             if (DEBUG) {
    297                 Log.i(TAG, String.format(
    298                         Locale.US, "getBaseLoadAddress() returns 0x%x",
    299                         mBaseLoadAddress));
    300             }
    301             return mBaseLoadAddress;
    302         }
    303     }
    304 
    305     // Used internally to lazily setup the common random base load address.
    306     private void setupBaseLoadAddressLocked() {
    307         assert Thread.holdsLock(mLock);
    308 
    309         // No-op if the base load address is already set up.
    310         if (mBaseLoadAddress == -1) {
    311             mBaseLoadAddress = getRandomBaseLoadAddress();
    312         }
    313         if (mBaseLoadAddress == 0) {
    314             // If the random address is 0 there are issues with finding enough
    315             // free address space, so disable RELRO shared / fixed load addresses.
    316             Log.w(TAG, "Disabling shared RELROs due address space pressure");
    317             mWaitForSharedRelros = false;
    318         }
    319     }
    320 
    321     /**
    322      * Load a native shared library with the Chromium linker. If the zip file
    323      * is not null, the shared library must be uncompressed and page aligned
    324      * inside the zipfile. The library must not be the Chromium linker library.
    325      *
    326      * If asked to wait for shared RELROs, this function will block library loads
    327      * until the shared RELROs bundle is received by useSharedRelros().
    328      *
    329      * @param zipFilePath The path of the zip file containing the library (or null).
    330      * @param libFilePath The path of the library (possibly in the zip file).
    331      * @param isFixedAddressPermitted If true, uses a fixed load address if one was
    332      * supplied, otherwise ignores the fixed address and loads wherever available.
    333      */
    334     @Override
    335     void loadLibraryImpl(@Nullable String zipFilePath,
    336                          String libFilePath,
    337                          boolean isFixedAddressPermitted) {
    338         if (DEBUG) {
    339             Log.i(TAG, "loadLibraryImpl: "
    340                     + zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted);
    341         }
    342 
    343         synchronized (mLock) {
    344             assert mPrepareLibraryLoadCalled;
    345 
    346             String dlopenExtPath;
    347             if (zipFilePath != null) {
    348                 // The android_dlopen_ext() function understands strings with the format
    349                 // <zip_path>!/<file_path> to represent the file_path element within the zip
    350                 // file at zip_path. This enables directly loading from APK. We add the
    351                 // "crazy." prefix to the path in the zip file to prevent the Android package
    352                 // manager from seeing this as a library and so extracting it from the APK.
    353                 String cpuAbi = nativeGetCpuAbi();
    354                 dlopenExtPath = zipFilePath + "!/lib/" + cpuAbi + "/crazy." + libFilePath;
    355             } else {
    356                 // Not loading from APK directly, so simply pass the library name to
    357                 // android_dlopen_ext().
    358                 dlopenExtPath = libFilePath;
    359             }
    360 
    361             if (mLoadedLibraries.containsKey(dlopenExtPath)) {
    362                 if (DEBUG) {
    363                     Log.i(TAG, "Not loading " + libFilePath + " twice");
    364                 }
    365                 return;
    366             }
    367 
    368             // If not in the browser, shared RELROs are not disabled, and fixed addresses
    369             // have not been disallowed, load the library at a fixed address. Otherwise,
    370             // load anywhere.
    371             long loadAddress = 0;
    372             if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) {
    373                 loadAddress = mCurrentLoadAddress;
    374 
    375                 // For multiple libraries, ensure we stay within reservation range.
    376                 if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
    377                     String errorMessage = "Load address outside reservation, for: " + libFilePath;
    378                     Log.e(TAG, errorMessage);
    379                     throw new UnsatisfiedLinkError(errorMessage);
    380                 }
    381             }
    382 
    383             LibInfo libInfo = new LibInfo();
    384 
    385             if (mInBrowserProcess && mCurrentLoadAddress != 0) {
    386                 // We are in the browser, and with a current load address that indicates that
    387                 // there is enough address space for shared RELRO to operate. Create the
    388                 // shared RELRO, and store it in the map.
    389                 String relroPath = PathUtils.getDataDirectory() + "/RELRO:" + libFilePath;
    390                 if (nativeCreateSharedRelro(dlopenExtPath,
    391                                             mCurrentLoadAddress, relroPath, libInfo)) {
    392                     mSharedRelros.put(dlopenExtPath, libInfo);
    393                 } else {
    394                     String errorMessage = "Unable to create shared relro: " + relroPath;
    395                     Log.w(TAG, errorMessage);
    396                 }
    397             } else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) {
    398                 // We are in a service process, again with a current load address that is
    399                 // suitable for shared RELRO, and we are to wait for shared RELROs. So
    400                 // do that, then use the map we receive to provide libinfo for library load.
    401                 waitForSharedRelrosLocked();
    402                 if (mSharedRelros.containsKey(dlopenExtPath)) {
    403                     libInfo = mSharedRelros.get(dlopenExtPath);
    404                 }
    405             }
    406 
    407             // Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary()
    408             // will load without shared RELRO. Otherwise, it uses shared RELRO if the attached
    409             // libInfo is usable.
    410             if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) {
    411                 String errorMessage = "Unable to load library: " + dlopenExtPath;
    412                 Log.e(TAG, errorMessage);
    413                 throw new UnsatisfiedLinkError(errorMessage);
    414             }
    415 
    416             // Print the load address to the logcat when testing the linker. The format
    417             // of the string is expected by the Python test_runner script as one of:
    418             //    BROWSER_LIBRARY_ADDRESS: <library-name> <address>
    419             //    RENDERER_LIBRARY_ADDRESS: <library-name> <address>
    420             // Where <library-name> is the library name, and <address> is the hexadecimal load
    421             // address.
    422             if (NativeLibraries.sEnableLinkerTests) {
    423                 String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS"
    424                                                : "RENDERER_LIBRARY_ADDRESS";
    425                 Log.i(TAG, String.format(
    426                         Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
    427             }
    428 
    429             if (loadAddress != 0 && mCurrentLoadAddress != 0) {
    430                 // Compute the next current load address. If mCurrentLoadAddress
    431                 // is not 0, this is an explicit library load address.
    432                 mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize
    433                                       + BREAKPAD_GUARD_REGION_BYTES;
    434             }
    435 
    436             mLoadedLibraries.put(dlopenExtPath, libInfo);
    437             if (DEBUG) {
    438                 Log.i(TAG, "Library details " + libInfo.toString());
    439             }
    440         }
    441     }
    442 
    443     /**
    444      * Native method to return the CPU ABI.
    445      * Obtaining it from the linker's native code means that we always correctly
    446      * match the loaded library's ABI to the linker's ABI.
    447      *
    448      * @return CPU ABI string.
    449      */
    450     private static native String nativeGetCpuAbi();
    451 
    452     /**
    453      * Native method used to load a library.
    454      *
    455      * @param dlopenExtPath For load from APK, the path to the enclosing
    456      * zipfile concatenated with "!/" and the path to the library within the zipfile;
    457      * otherwise the platform specific library name (e.g. libfoo.so).
    458      * @param loadAddress Explicit load address, or 0 for randomized one.
    459      * @param libInfo If not null, the mLoadAddress and mLoadSize fields
    460      * of this LibInfo instance will set on success.
    461      * @return true for success, false otherwise.
    462      */
    463     private static native boolean nativeLoadLibrary(String dlopenExtPath,
    464                                                     long loadAddress,
    465                                                     LibInfo libInfo);
    466 
    467     /**
    468      * Native method used to create a shared RELRO section.
    469      * Creates a shared RELRO file for the given library. Done by loading a
    470      * a new temporary library at the specified address, saving the RELRO section
    471      * from it, then unloading.
    472      *
    473      * @param dlopenExtPath For load from APK, the path to the enclosing
    474      * zipfile concatenated with "!/" and the path to the library within the zipfile;
    475      * otherwise the platform specific library name (e.g. libfoo.so).
    476      * @param loadAddress load address, which can be different from the one
    477      * used to load the library in the current process!
    478      * @param relroPath Path to the shared RELRO file for this library.
    479      * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize
    480      * and mRelroFd will be set.
    481      * @return true on success, false otherwise.
    482      */
    483     private static native boolean nativeCreateSharedRelro(String dlopenExtPath,
    484                                                           long loadAddress,
    485                                                           String relroPath,
    486                                                           LibInfo libInfo);
    487 }
    488