• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
ModernLinker()66     private ModernLinker() { }
create()67     static Linker create() {
68         return new ModernLinker();
69     }
70 
71     // Used internally to initialize the linker's data. Assumes lock is held.
ensureInitializedLocked()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
isUsingBrowserSharedRelros()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
prepareLibraryLoad()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
finishLibraryLoad()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")
waitForSharedRelrosLocked()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
useSharedRelros(Bundle bundle)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
getSharedRelros()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
disableSharedRelros()249     public void disableSharedRelros() {
250         if (DEBUG) {
251             Log.i(TAG, "disableSharedRelros() called");
252         }
253         synchronized (mLock) {
254             assert !mPrepareLibraryLoadCalled;
255 
256             // Mark this as a service process, and disable wait for shared RELRO.
257             mInBrowserProcess = false;
258             mWaitForSharedRelros = false;
259         }
260     }
261 
262     /**
263      * Call this method before loading any libraries to indicate that this
264      * process is ready to reuse shared RELRO sections from another one.
265      * Typically used when starting service processes.
266      *
267      * @param baseLoadAddress the base library load address to use.
268      */
269     @Override
initServiceProcess(long baseLoadAddress)270     public void initServiceProcess(long baseLoadAddress) {
271         if (DEBUG) {
272             Log.i(TAG, String.format(
273                     Locale.US, "initServiceProcess(0x%x) called",
274                     baseLoadAddress));
275         }
276         synchronized (mLock) {
277             assert !mPrepareLibraryLoadCalled;
278 
279             // Mark this as a service process, and flag wait for shared RELRO.
280             // Save the base load address passed in.
281             mInBrowserProcess = false;
282             mWaitForSharedRelros = true;
283             mBaseLoadAddress = baseLoadAddress;
284         }
285     }
286 
287     /**
288      * Retrieve the base load address for libraries that share RELROs.
289      *
290      * @return a common, random base load address, or 0 if RELRO sharing is
291      * disabled.
292      */
293     @Override
getBaseLoadAddress()294     public long getBaseLoadAddress() {
295         synchronized (mLock) {
296             ensureInitializedLocked();
297             setupBaseLoadAddressLocked();
298             if (DEBUG) {
299                 Log.i(TAG, String.format(
300                         Locale.US, "getBaseLoadAddress() returns 0x%x",
301                         mBaseLoadAddress));
302             }
303             return mBaseLoadAddress;
304         }
305     }
306 
307     // Used internally to lazily setup the common random base load address.
setupBaseLoadAddressLocked()308     private void setupBaseLoadAddressLocked() {
309         assert Thread.holdsLock(mLock);
310 
311         // No-op if the base load address is already set up.
312         if (mBaseLoadAddress == -1) {
313             mBaseLoadAddress = getRandomBaseLoadAddress();
314         }
315         if (mBaseLoadAddress == 0) {
316             // If the random address is 0 there are issues with finding enough
317             // free address space, so disable RELRO shared / fixed load addresses.
318             Log.w(TAG, "Disabling shared RELROs due address space pressure");
319             mWaitForSharedRelros = false;
320         }
321     }
322 
323     /**
324      * Load a native shared library with the Chromium linker. If the zip file
325      * is not null, the shared library must be uncompressed and page aligned
326      * inside the zipfile. The library must not be the Chromium linker library.
327      *
328      * If asked to wait for shared RELROs, this function will block library loads
329      * until the shared RELROs bundle is received by useSharedRelros().
330      *
331      * @param zipFilePath The path of the zip file containing the library (or null).
332      * @param libFilePath The path of the library (possibly in the zip file).
333      * @param isFixedAddressPermitted If true, uses a fixed load address if one was
334      * supplied, otherwise ignores the fixed address and loads wherever available.
335      */
336     @Override
loadLibraryImpl(@ullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted)337     void loadLibraryImpl(@Nullable String zipFilePath,
338                          String libFilePath,
339                          boolean isFixedAddressPermitted) {
340         if (DEBUG) {
341             Log.i(TAG, "loadLibraryImpl: "
342                     + zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted);
343         }
344 
345         synchronized (mLock) {
346             assert mPrepareLibraryLoadCalled;
347 
348             String dlopenExtPath;
349             if (zipFilePath != null) {
350                 // The android_dlopen_ext() function understands strings with the format
351                 // <zip_path>!/<file_path> to represent the file_path element within the zip
352                 // file at zip_path. This enables directly loading from APK. We add the
353                 // "crazy." prefix to the path in the zip file to prevent the Android package
354                 // manager from seeing this as a library and so extracting it from the APK.
355                 String cpuAbi = nativeGetCpuAbi();
356                 dlopenExtPath = zipFilePath + "!/lib/" + cpuAbi + "/crazy." + libFilePath;
357             } else {
358                 // Not loading from APK directly, so simply pass the library name to
359                 // android_dlopen_ext().
360                 dlopenExtPath = libFilePath;
361             }
362 
363             if (mLoadedLibraries.containsKey(dlopenExtPath)) {
364                 if (DEBUG) {
365                     Log.i(TAG, "Not loading " + libFilePath + " twice");
366                 }
367                 return;
368             }
369 
370             // If not in the browser, shared RELROs are not disabled, and fixed addresses
371             // have not been disallowed, load the library at a fixed address. Otherwise,
372             // load anywhere.
373             long loadAddress = 0;
374             if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) {
375                 loadAddress = mCurrentLoadAddress;
376 
377                 // For multiple libraries, ensure we stay within reservation range.
378                 if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
379                     String errorMessage = "Load address outside reservation, for: " + libFilePath;
380                     Log.e(TAG, errorMessage);
381                     throw new UnsatisfiedLinkError(errorMessage);
382                 }
383             }
384 
385             LibInfo libInfo = new LibInfo();
386 
387             if (mInBrowserProcess && mCurrentLoadAddress != 0) {
388                 // We are in the browser, and with a current load address that indicates that
389                 // there is enough address space for shared RELRO to operate. Create the
390                 // shared RELRO, and store it in the map.
391                 String relroPath = PathUtils.getDataDirectory(null) + "/RELRO:" + libFilePath;
392                 if (nativeCreateSharedRelro(dlopenExtPath,
393                                             mCurrentLoadAddress, relroPath, libInfo)) {
394                     mSharedRelros.put(dlopenExtPath, libInfo);
395                 } else {
396                     String errorMessage = "Unable to create shared relro: " + relroPath;
397                     Log.w(TAG, errorMessage);
398                 }
399             } else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) {
400                 // We are in a service process, again with a current load address that is
401                 // suitable for shared RELRO, and we are to wait for shared RELROs. So
402                 // do that, then use the map we receive to provide libinfo for library load.
403                 waitForSharedRelrosLocked();
404                 if (mSharedRelros.containsKey(dlopenExtPath)) {
405                     libInfo = mSharedRelros.get(dlopenExtPath);
406                 }
407             }
408 
409             // Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary()
410             // will load without shared RELRO. Otherwise, it uses shared RELRO if the attached
411             // libInfo is usable.
412             if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) {
413                 String errorMessage = "Unable to load library: " + dlopenExtPath;
414                 Log.e(TAG, errorMessage);
415                 throw new UnsatisfiedLinkError(errorMessage);
416             }
417 
418             // Print the load address to the logcat when testing the linker. The format
419             // of the string is expected by the Python test_runner script as one of:
420             //    BROWSER_LIBRARY_ADDRESS: <library-name> <address>
421             //    RENDERER_LIBRARY_ADDRESS: <library-name> <address>
422             // Where <library-name> is the library name, and <address> is the hexadecimal load
423             // address.
424             if (NativeLibraries.sEnableLinkerTests) {
425                 String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS"
426                                                : "RENDERER_LIBRARY_ADDRESS";
427                 Log.i(TAG, String.format(
428                         Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
429             }
430 
431             if (loadAddress != 0 && mCurrentLoadAddress != 0) {
432                 // Compute the next current load address. If mCurrentLoadAddress
433                 // is not 0, this is an explicit library load address.
434                 mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize
435                                       + BREAKPAD_GUARD_REGION_BYTES;
436             }
437 
438             mLoadedLibraries.put(dlopenExtPath, libInfo);
439             if (DEBUG) {
440                 Log.i(TAG, "Library details " + libInfo.toString());
441             }
442         }
443     }
444 
445     /**
446      * Native method to return the CPU ABI.
447      * Obtaining it from the linker's native code means that we always correctly
448      * match the loaded library's ABI to the linker's ABI.
449      *
450      * @return CPU ABI string.
451      */
nativeGetCpuAbi()452     private static native String nativeGetCpuAbi();
453 
454     /**
455      * Native method used to load a library.
456      *
457      * @param dlopenExtPath For load from APK, the path to the enclosing
458      * zipfile concatenated with "!/" and the path to the library within the zipfile;
459      * otherwise the platform specific library name (e.g. libfoo.so).
460      * @param loadAddress Explicit load address, or 0 for randomized one.
461      * @param libInfo If not null, the mLoadAddress and mLoadSize fields
462      * of this LibInfo instance will set on success.
463      * @return true for success, false otherwise.
464      */
nativeLoadLibrary(String dlopenExtPath, long loadAddress, LibInfo libInfo)465     private static native boolean nativeLoadLibrary(String dlopenExtPath,
466                                                     long loadAddress,
467                                                     LibInfo libInfo);
468 
469     /**
470      * Native method used to create a shared RELRO section.
471      * Creates a shared RELRO file for the given library. Done by loading a
472      * a new temporary library at the specified address, saving the RELRO section
473      * from it, then unloading.
474      *
475      * @param dlopenExtPath For load from APK, the path to the enclosing
476      * zipfile concatenated with "!/" and the path to the library within the zipfile;
477      * otherwise the platform specific library name (e.g. libfoo.so).
478      * @param loadAddress load address, which can be different from the one
479      * used to load the library in the current process!
480      * @param relroPath Path to the shared RELRO file for this library.
481      * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize
482      * and mRelroFd will be set.
483      * @return true on success, false otherwise.
484      */
nativeCreateSharedRelro(String dlopenExtPath, long loadAddress, String relroPath, LibInfo libInfo)485     private static native boolean nativeCreateSharedRelro(String dlopenExtPath,
486                                                           long loadAddress,
487                                                           String relroPath,
488                                                           LibInfo libInfo);
489 }
490