• 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.Parcel;
9 
10 import org.chromium.base.Log;
11 import org.chromium.base.SysUtils;
12 import org.chromium.base.ThreadUtils;
13 import org.chromium.base.annotations.CalledByNative;
14 import org.chromium.base.annotations.MainDex;
15 
16 import java.util.HashMap;
17 import java.util.Locale;
18 import java.util.Map;
19 
20 import javax.annotation.Nullable;
21 
22 /*
23  * For more, see Technical note, Security considerations, and the explanation
24  * of how this class is supposed to be used in Linker.java.
25  */
26 
27 /**
28  * Provides a concrete implementation of the Chromium Linker.
29  *
30  * This Linker implementation uses the crazy linker to map and then run Chrome
31  * for Android.
32  *
33  * For more on the operations performed by the Linker, see {@link Linker}.
34  */
35 @MainDex
36 class LegacyLinker extends Linker {
37     // Log tag for this class.
38     private static final String TAG = "LibraryLoader";
39 
40     // Becomes true after linker initialization.
41     private boolean mInitialized = false;
42 
43     // Set to true if this runs in the browser process. Disabled by initServiceProcess().
44     private boolean mInBrowserProcess = true;
45 
46     // Becomes true to indicate this process needs to wait for a shared RELRO in
47     // finishLibraryLoad().
48     private boolean mWaitForSharedRelros = false;
49 
50     // Becomes true when initialization determines that the browser process can use the
51     // shared RELRO.
52     private boolean mBrowserUsesSharedRelro = false;
53 
54     // The map of all RELRO sections either created or used in this process.
55     private Bundle mSharedRelros = null;
56 
57     // Current common random base load address. A value of -1 indicates not yet initialized.
58     private long mBaseLoadAddress = -1;
59 
60     // Current fixed-location load address for the next library called by loadLibrary().
61     // A value of -1 indicates not yet initialized.
62     private long mCurrentLoadAddress = -1;
63 
64     // Becomes true once prepareLibraryLoad() has been called.
65     private boolean mPrepareLibraryLoadCalled = false;
66 
67     // The map of libraries that are currently loaded in this process.
68     private HashMap<String, LibInfo> mLoadedLibraries = null;
69 
70     // Private singleton constructor, and singleton factory method.
LegacyLinker()71     private LegacyLinker() { }
create()72     static Linker create() {
73         return new LegacyLinker();
74     }
75 
76     // Used internally to initialize the linker's data. Assumes lock is held.
77     // Loads JNI, and sets mMemoryDeviceConfig and mBrowserUsesSharedRelro.
ensureInitializedLocked()78     private void ensureInitializedLocked() {
79         assert Thread.holdsLock(mLock);
80 
81         if (mInitialized || !NativeLibraries.sUseLinker) {
82             return;
83         }
84 
85         // On first call, load libchromium_android_linker.so. Cannot be done in the
86         // constructor because instantiation occurs on the UI thread.
87         loadLinkerJniLibrary();
88 
89         if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) {
90             if (SysUtils.isLowEndDevice()) {
91                 mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_LOW;
92             } else {
93                 mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_NORMAL;
94             }
95         }
96 
97         // Cannot run in the constructor because SysUtils.isLowEndDevice() relies
98         // on CommandLine, which may not be available at instantiation.
99         switch (BROWSER_SHARED_RELRO_CONFIG) {
100             case BROWSER_SHARED_RELRO_CONFIG_NEVER:
101                 mBrowserUsesSharedRelro = false;
102                 break;
103             case BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY:
104                 if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) {
105                     mBrowserUsesSharedRelro = true;
106                     Log.w(TAG, "Low-memory device: shared RELROs used in all processes");
107                 } else {
108                     mBrowserUsesSharedRelro = false;
109                 }
110                 break;
111             case BROWSER_SHARED_RELRO_CONFIG_ALWAYS:
112                 Log.w(TAG, "Beware: shared RELROs used in all processes!");
113                 mBrowserUsesSharedRelro = true;
114                 break;
115             default:
116                 Log.wtf(TAG, "FATAL: illegal shared RELRO config");
117                 throw new AssertionError();
118         }
119 
120         mInitialized = true;
121     }
122 
123     /**
124      * Call this method to determine if the linker will try to use shared RELROs
125      * for the browser process.
126      */
127     @Override
isUsingBrowserSharedRelros()128     public boolean isUsingBrowserSharedRelros() {
129         synchronized (mLock) {
130             ensureInitializedLocked();
131             return mInBrowserProcess && mBrowserUsesSharedRelro;
132         }
133     }
134 
135     /**
136      * Call this method just before loading any native shared libraries in this process.
137      */
138     @Override
prepareLibraryLoad()139     public void prepareLibraryLoad() {
140         if (DEBUG) {
141             Log.i(TAG, "prepareLibraryLoad() called");
142         }
143         synchronized (mLock) {
144             ensureInitializedLocked();
145             mPrepareLibraryLoadCalled = true;
146 
147             if (mInBrowserProcess) {
148                 // Force generation of random base load address, as well
149                 // as creation of shared RELRO sections in this process.
150                 setupBaseLoadAddressLocked();
151             }
152         }
153     }
154 
155     /**
156      * Call this method just after loading all native shared libraries in this process.
157      * Note that when in a service process, this will block until the RELRO bundle is
158      * received, i.e. when another thread calls useSharedRelros().
159      */
160     @Override
finishLibraryLoad()161     public void finishLibraryLoad() {
162         if (DEBUG) {
163             Log.i(TAG, "finishLibraryLoad() called");
164         }
165         synchronized (mLock) {
166             ensureInitializedLocked();
167             if (DEBUG) {
168                 Log.i(TAG, String.format(
169                         Locale.US,
170                         "mInBrowserProcess=%b mBrowserUsesSharedRelro=%b mWaitForSharedRelros=%b",
171                         mInBrowserProcess, mBrowserUsesSharedRelro, mWaitForSharedRelros));
172             }
173 
174             if (mLoadedLibraries == null) {
175                 if (DEBUG) {
176                     Log.i(TAG, "No libraries loaded");
177                 }
178             } else {
179                 if (mInBrowserProcess) {
180                     // Create new Bundle containing RELRO section information
181                     // for all loaded libraries. Make it available to getSharedRelros().
182                     mSharedRelros = createBundleFromLibInfoMap(mLoadedLibraries);
183                     if (DEBUG) {
184                         Log.i(TAG, "Shared RELRO created");
185                         dumpBundle(mSharedRelros);
186                     }
187 
188                     if (mBrowserUsesSharedRelro) {
189                         useSharedRelrosLocked(mSharedRelros);
190                     }
191                 }
192 
193                 if (mWaitForSharedRelros) {
194                     assert !mInBrowserProcess;
195 
196                     // Wait until the shared relro bundle is received from useSharedRelros().
197                     while (mSharedRelros == null) {
198                         try {
199                             mLock.wait();
200                         } catch (InterruptedException ie) {
201                             // Restore the thread's interrupt status.
202                             Thread.currentThread().interrupt();
203                         }
204                     }
205                     useSharedRelrosLocked(mSharedRelros);
206                     // Clear the Bundle to ensure its file descriptor references can't be reused.
207                     mSharedRelros.clear();
208                     mSharedRelros = null;
209                 }
210             }
211 
212             // If testing, run tests now that all libraries are loaded and initialized.
213             if (NativeLibraries.sEnableLinkerTests) {
214                 runTestRunnerClassForTesting(mMemoryDeviceConfig, mInBrowserProcess);
215             }
216         }
217         if (DEBUG) {
218             Log.i(TAG, "finishLibraryLoad() exiting");
219         }
220     }
221 
222     /**
223      * Call this to send a Bundle containing the shared RELRO sections to be
224      * used in this process. If initServiceProcess() was previously called,
225      * finishLibraryLoad() will not exit until this method is called in another
226      * thread with a non-null value.
227      *
228      * @param bundle The Bundle instance containing a map of shared RELRO sections
229      * to use in this process.
230      */
231     @Override
useSharedRelros(Bundle bundle)232     public void useSharedRelros(Bundle bundle) {
233         // Ensure the bundle uses the application's class loader, not the framework
234         // one which doesn't know anything about LibInfo.
235         // Also, hold a fresh copy of it so the caller can't recycle it.
236         Bundle clonedBundle = null;
237         if (bundle != null) {
238             bundle.setClassLoader(LibInfo.class.getClassLoader());
239             clonedBundle = new Bundle(LibInfo.class.getClassLoader());
240             Parcel parcel = Parcel.obtain();
241             bundle.writeToParcel(parcel, 0);
242             parcel.setDataPosition(0);
243             clonedBundle.readFromParcel(parcel);
244             parcel.recycle();
245         }
246         if (DEBUG) {
247             Log.i(TAG, "useSharedRelros() called with " + bundle
248                     + ", cloned " + clonedBundle);
249         }
250         synchronized (mLock) {
251             // Note that in certain cases, this can be called before
252             // initServiceProcess() in service processes.
253             mSharedRelros = clonedBundle;
254             // Tell any listener blocked in finishLibraryLoad() about it.
255             mLock.notifyAll();
256         }
257     }
258 
259     /**
260      * Call this to retrieve the shared RELRO sections created in this process,
261      * after loading all libraries.
262      *
263      * @return a new Bundle instance, or null if RELRO sharing is disabled on
264      * this system, or if initServiceProcess() was called previously.
265      */
266     @Override
getSharedRelros()267     public Bundle getSharedRelros() {
268         if (DEBUG) {
269             Log.i(TAG, "getSharedRelros() called");
270         }
271         synchronized (mLock) {
272             if (!mInBrowserProcess) {
273                 if (DEBUG) {
274                     Log.i(TAG, "... returning null Bundle");
275                 }
276                 return null;
277             }
278 
279             // Return the Bundle created in finishLibraryLoad().
280             if (DEBUG) {
281                 Log.i(TAG, "... returning " + mSharedRelros);
282             }
283             return mSharedRelros;
284         }
285     }
286 
287     /**
288      * Call this method before loading any libraries to indicate that this
289      * process shall neither create or reuse shared RELRO sections.
290      */
291     @Override
disableSharedRelros()292     public void disableSharedRelros() {
293         if (DEBUG) {
294             Log.i(TAG, "disableSharedRelros() called");
295         }
296         synchronized (mLock) {
297             ensureInitializedLocked();
298             mInBrowserProcess = false;
299             mWaitForSharedRelros = false;
300             mBrowserUsesSharedRelro = false;
301         }
302     }
303 
304     /**
305      * Call this method before loading any libraries to indicate that this
306      * process is ready to reuse shared RELRO sections from another one.
307      * Typically used when starting service processes.
308      *
309      * @param baseLoadAddress the base library load address to use.
310      */
311     @Override
initServiceProcess(long baseLoadAddress)312     public void initServiceProcess(long baseLoadAddress) {
313         if (DEBUG) {
314             Log.i(TAG, String.format(
315                     Locale.US, "initServiceProcess(0x%x) called",
316                     baseLoadAddress));
317         }
318         synchronized (mLock) {
319             ensureInitializedLocked();
320             mInBrowserProcess = false;
321             mBrowserUsesSharedRelro = false;
322             mWaitForSharedRelros = true;
323             mBaseLoadAddress = baseLoadAddress;
324             mCurrentLoadAddress = baseLoadAddress;
325         }
326     }
327 
328     /**
329      * Retrieve the base load address of all shared RELRO sections.
330      * This also enforces the creation of shared RELRO sections in
331      * prepareLibraryLoad(), which can later be retrieved with getSharedRelros().
332      *
333      * @return a common, random base load address, or 0 if RELRO sharing is
334      * disabled.
335      */
336     @Override
getBaseLoadAddress()337     public long getBaseLoadAddress() {
338         synchronized (mLock) {
339             ensureInitializedLocked();
340             if (!mInBrowserProcess) {
341                 Log.w(TAG, "Shared RELRO sections are disabled in this process!");
342                 return 0;
343             }
344 
345             setupBaseLoadAddressLocked();
346             if (DEBUG) {
347                 Log.i(TAG, String.format(
348                         Locale.US, "getBaseLoadAddress() returns 0x%x",
349                         mBaseLoadAddress));
350             }
351             return mBaseLoadAddress;
352         }
353     }
354 
355     // Used internally to lazily setup the common random base load address.
setupBaseLoadAddressLocked()356     private void setupBaseLoadAddressLocked() {
357         assert Thread.holdsLock(mLock);
358         if (mBaseLoadAddress == -1) {
359             mBaseLoadAddress = getRandomBaseLoadAddress();
360             mCurrentLoadAddress = mBaseLoadAddress;
361             if (mBaseLoadAddress == 0) {
362                 // If the random address is 0 there are issues with finding enough
363                 // free address space, so disable RELRO shared / fixed load addresses.
364                 Log.w(TAG, "Disabling shared RELROs due address space pressure");
365                 mBrowserUsesSharedRelro = false;
366                 mWaitForSharedRelros = false;
367             }
368         }
369     }
370 
371     // Used for debugging only.
dumpBundle(Bundle bundle)372     private void dumpBundle(Bundle bundle) {
373         if (DEBUG) {
374             Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle);
375         }
376     }
377 
378     /**
379      * Use the shared RELRO section from a Bundle received form another process.
380      * Call this after calling setBaseLoadAddress() then loading all libraries
381      * with loadLibrary().
382      *
383      * @param bundle Bundle instance generated with createSharedRelroBundle() in
384      * another process.
385      */
useSharedRelrosLocked(Bundle bundle)386     private void useSharedRelrosLocked(Bundle bundle) {
387         assert Thread.holdsLock(mLock);
388 
389         if (DEBUG) {
390             Log.i(TAG, "Linker.useSharedRelrosLocked() called");
391         }
392 
393         if (bundle == null) {
394             if (DEBUG) {
395                 Log.i(TAG, "null bundle!");
396             }
397             return;
398         }
399 
400         if (mLoadedLibraries == null) {
401             if (DEBUG) {
402                 Log.i(TAG, "No libraries loaded!");
403             }
404             return;
405         }
406 
407         if (DEBUG) {
408             dumpBundle(bundle);
409         }
410         HashMap<String, LibInfo> relroMap = createLibInfoMapFromBundle(bundle);
411 
412         // Apply the RELRO section to all libraries that were already loaded.
413         for (Map.Entry<String, LibInfo> entry : relroMap.entrySet()) {
414             String libName = entry.getKey();
415             LibInfo libInfo = entry.getValue();
416             if (!nativeUseSharedRelro(libName, libInfo)) {
417                 Log.w(TAG, "Could not use shared RELRO section for " + libName);
418             } else {
419                 if (DEBUG) {
420                     Log.i(TAG, "Using shared RELRO section for " + libName);
421                 }
422             }
423         }
424 
425         // In service processes, close all file descriptors from the map now.
426         if (!mInBrowserProcess) {
427             closeLibInfoMap(relroMap);
428         }
429 
430         if (DEBUG) {
431             Log.i(TAG, "Linker.useSharedRelrosLocked() exiting");
432         }
433     }
434 
435     /**
436      * Implements loading a native shared library with the Chromium linker.
437      *
438      * Load a native shared library with the Chromium linker. If the zip file
439      * is not null, the shared library must be uncompressed and page aligned
440      * inside the zipfile. Note the crazy linker treats libraries and files as
441      * equivalent, so you can only open one library in a given zip file. The
442      * library must not be the Chromium linker library.
443      *
444      * @param zipFilePath The path of the zip file containing the library (or null).
445      * @param libFilePath The path of the library (possibly in the zip file).
446      * @param isFixedAddressPermitted If true, uses a fixed load address if one was
447      * supplied, otherwise ignores the fixed address and loads wherever available.
448      */
449     @Override
loadLibraryImpl(@ullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted)450     void loadLibraryImpl(@Nullable String zipFilePath,
451                          String libFilePath,
452                          boolean isFixedAddressPermitted) {
453         if (DEBUG) {
454             Log.i(TAG, "loadLibraryImpl: "
455                     + zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted);
456         }
457         synchronized (mLock) {
458             ensureInitializedLocked();
459 
460             // Security: Ensure prepareLibraryLoad() was called before.
461             // In theory, this can be done lazily here, but it's more consistent
462             // to use a pair of functions (i.e. prepareLibraryLoad() + finishLibraryLoad())
463             // that wrap all calls to loadLibrary() in the library loader.
464             assert mPrepareLibraryLoadCalled;
465 
466             if (mLoadedLibraries == null) {
467                 mLoadedLibraries = new HashMap<String, LibInfo>();
468             }
469 
470             if (mLoadedLibraries.containsKey(libFilePath)) {
471                 if (DEBUG) {
472                     Log.i(TAG, "Not loading " + libFilePath + " twice");
473                 }
474                 return;
475             }
476 
477             LibInfo libInfo = new LibInfo();
478             long loadAddress = 0;
479             if (isFixedAddressPermitted) {
480                 if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) {
481                     // Load the library at a fixed address.
482                     loadAddress = mCurrentLoadAddress;
483 
484                     // For multiple libraries, ensure we stay within reservation range.
485                     if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
486                         String errorMessage =
487                                 "Load address outside reservation, for: " + libFilePath;
488                         Log.e(TAG, errorMessage);
489                         throw new UnsatisfiedLinkError(errorMessage);
490                     }
491                 }
492             }
493 
494             String sharedRelRoName = libFilePath;
495             if (zipFilePath != null) {
496                 if (!nativeLoadLibraryInZipFile(zipFilePath, libFilePath, loadAddress, libInfo)) {
497                     String errorMessage = "Unable to load library: " + libFilePath
498                                           + ", in: " + zipFilePath;
499                     Log.e(TAG, errorMessage);
500                     throw new UnsatisfiedLinkError(errorMessage);
501                 }
502                 sharedRelRoName = zipFilePath;
503             } else {
504                 if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) {
505                     String errorMessage = "Unable to load library: " + libFilePath;
506                     Log.e(TAG, errorMessage);
507                     throw new UnsatisfiedLinkError(errorMessage);
508                 }
509             }
510 
511             // Print the load address to the logcat when testing the linker. The format
512             // of the string is expected by the Python test_runner script as one of:
513             //    BROWSER_LIBRARY_ADDRESS: <library-name> <address>
514             //    RENDERER_LIBRARY_ADDRESS: <library-name> <address>
515             // Where <library-name> is the library name, and <address> is the hexadecimal load
516             // address.
517             if (NativeLibraries.sEnableLinkerTests) {
518                 String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS"
519                                                : "RENDERER_LIBRARY_ADDRESS";
520                 Log.i(TAG, String.format(
521                         Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
522             }
523 
524             if (mInBrowserProcess) {
525                 // Create a new shared RELRO section at the 'current' fixed load address.
526                 if (!nativeCreateSharedRelro(sharedRelRoName, mCurrentLoadAddress, libInfo)) {
527                     Log.w(TAG, String.format(
528                             Locale.US, "Could not create shared RELRO for %s at %x",
529                             libFilePath,
530                             mCurrentLoadAddress));
531                 } else {
532                     if (DEBUG) {
533                         Log.i(TAG, String.format(
534                                 Locale.US,
535                                 "Created shared RELRO for %s at %x: %s",
536                                 sharedRelRoName,
537                                 mCurrentLoadAddress,
538                                 libInfo.toString()));
539                     }
540                 }
541             }
542 
543             if (loadAddress != 0 && mCurrentLoadAddress != 0) {
544                 // Compute the next current load address. If mCurrentLoadAddress
545                 // is not 0, this is an explicit library load address. Otherwise,
546                 // this is an explicit load address for relocated RELRO sections
547                 // only.
548                 mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize
549                                       + BREAKPAD_GUARD_REGION_BYTES;
550             }
551 
552             mLoadedLibraries.put(sharedRelRoName, libInfo);
553             if (DEBUG) {
554                 Log.i(TAG, "Library details " + libInfo.toString());
555             }
556         }
557     }
558 
559     /**
560      * Move activity from the native thread to the main UI thread.
561      * Called from native code on its own thread. Posts a callback from
562      * the UI thread back to native code.
563      *
564      * @param opaque Opaque argument.
565      */
566     @CalledByNative
postCallbackOnMainThread(final long opaque)567     public static void postCallbackOnMainThread(final long opaque) {
568         ThreadUtils.postOnUiThread(new Runnable() {
569             @Override
570             public void run() {
571                 nativeRunCallbackOnUiThread(opaque);
572             }
573         });
574     }
575 
576     /**
577      * Native method to run callbacks on the main UI thread.
578      * Supplied by the crazy linker and called by postCallbackOnMainThread.
579      *
580      * @param opaque Opaque crazy linker arguments.
581      */
nativeRunCallbackOnUiThread(long opaque)582     private static native void nativeRunCallbackOnUiThread(long opaque);
583 
584     /**
585      * Native method used to load a library.
586      *
587      * @param library Platform specific library name (e.g. libfoo.so)
588      * @param loadAddress Explicit load address, or 0 for randomized one.
589      * @param libInfo If not null, the mLoadAddress and mLoadSize fields
590      * of this LibInfo instance will set on success.
591      * @return true for success, false otherwise.
592      */
nativeLoadLibrary(String library, long loadAddress, LibInfo libInfo)593     private static native boolean nativeLoadLibrary(String library,
594                                                     long loadAddress,
595                                                     LibInfo libInfo);
596 
597     /**
598      * Native method used to load a library which is inside a zipfile.
599      *
600      * @param zipfileName Filename of the zip file containing the library.
601      * @param library Platform specific library name (e.g. libfoo.so)
602      * @param loadAddress Explicit load address, or 0 for randomized one.
603      * @param libInfo If not null, the mLoadAddress and mLoadSize fields
604      * of this LibInfo instance will set on success.
605      * @return true for success, false otherwise.
606      */
nativeLoadLibraryInZipFile(@ullable String zipfileName, String libraryName, long loadAddress, LibInfo libInfo)607     private static native boolean nativeLoadLibraryInZipFile(@Nullable String zipfileName,
608                                                              String libraryName,
609                                                              long loadAddress,
610                                                              LibInfo libInfo);
611 
612     /**
613      * Native method used to create a shared RELRO section.
614      * If the library was already loaded at the same address using
615      * nativeLoadLibrary(), this creates the RELRO for it. Otherwise,
616      * this loads a new temporary library at the specified address,
617      * creates and extracts the RELRO section from it, then unloads it.
618      *
619      * @param library Library name.
620      * @param loadAddress load address, which can be different from the one
621      * used to load the library in the current process!
622      * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize
623      * and mRelroFd will be set.
624      * @return true on success, false otherwise.
625      */
nativeCreateSharedRelro(String library, long loadAddress, LibInfo libInfo)626     private static native boolean nativeCreateSharedRelro(String library,
627                                                           long loadAddress,
628                                                           LibInfo libInfo);
629 
630     /**
631      * Native method used to use a shared RELRO section.
632      *
633      * @param library Library name.
634      * @param libInfo A LibInfo instance containing valid RELRO information
635      * @return true on success.
636      */
nativeUseSharedRelro(String library, LibInfo libInfo)637     private static native boolean nativeUseSharedRelro(String library,
638                                                        LibInfo libInfo);
639 }
640