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
5package org.chromium.base.library_loader;
6
7import android.os.Bundle;
8import android.os.Parcel;
9
10import org.chromium.base.Log;
11import org.chromium.base.SysUtils;
12import org.chromium.base.ThreadUtils;
13import org.chromium.base.annotations.CalledByNative;
14import org.chromium.base.annotations.MainDex;
15
16import java.util.HashMap;
17import java.util.Locale;
18import java.util.Map;
19
20import 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
36class 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.
71    private LegacyLinker() { }
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.
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
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
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
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
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
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
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
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
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.
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.
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     */
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
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
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     */
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     */
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     */
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     */
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     */
637    private static native boolean nativeUseSharedRelro(String library,
638                                                       LibInfo libInfo);
639}
640