• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Chromium Authors
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.annotation.SuppressLint;
8 import android.os.Bundle;
9 import android.os.Parcel;
10 import android.os.ParcelFileDescriptor;
11 import android.os.Parcelable;
12 
13 import androidx.annotation.IntDef;
14 import androidx.annotation.NonNull;
15 import androidx.annotation.VisibleForTesting;
16 
17 import org.chromium.base.Log;
18 import org.chromium.base.StreamUtil;
19 import org.chromium.base.TimeUtils.UptimeMillisTimer;
20 import org.chromium.base.annotations.AccessedByNative;
21 import org.chromium.base.annotations.JniIgnoreNatives;
22 import org.chromium.base.metrics.RecordHistogram;
23 
24 import java.io.BufferedReader;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 
31 import javax.annotation.concurrent.GuardedBy;
32 
33 /*
34  * This class provides a way to load the native library as an alternative to System.loadLibrary().
35  * It has the ability to save RAM by placing the PT_GNU_RELRO segments in a shared memory region and
36  * memory-mapping this region from different processes. This approach saves a few MiB RAM compared
37  * to the normal placement of the segment in private dirty memory.
38  *
39  * In the main library only one PT_GNU_RELRO segment is present, and it maps only one section
40  * (.data.rel.ro), so here and below it will be referred as a "RELRO section".
41  *
42  * When two processes load the same native library at the _same_ memory address, the content of
43  * their RELRO section (which includes C++ vtables or any constants that contain pointers) will be
44  * largely identical. The exceptions are pointers to external, randomized, symbols, like those from
45  * some system libraries, but these are very rare in practice.
46  *
47  * In order to establish usage of the same shared region in different processes, the Linker can
48  * serialize/deserialize the relevant information to/from a Bundle. Providing the RELRO shared
49  * memory region is done by loading the library normally, then replacing the virtual address mapping
50  * behind the RELRO section with the one backed by the shared memory, with identical contents.
51  *
52  * Security considerations:
53  *
54  * - The shared RELRO memory region is always forced read-only after creation, which means it is
55  *   impossible for a compromised process to map it read-write (e.g. by calling mmap() or
56  *   mprotect()) and modify its content, altering values seen in other processes.
57  *
58  * - The common library load addresses are randomized for each instance of the program on the
59  *   device.
60  *
61  * Usage:
62  *
63  * - The native shared library must be loaded with Linker.loadLibrary(), instead of
64  *   System.loadLibrary(). The two functions should behave the same (at a high level).
65  *
66  * - Early on, before the attempt to load the library, the linker needs to be initialized either as
67  *   a producer or a consumer of the RELRO region by invoking ensureInitialized(). Since various
68  *   Chromium projects have vastly different initialization paths, for convenience the
69  *   initialization runs implicitly as part of loading the library. In this case the behaviour is of
70  *   a producer.
71  *
72  * - After loading the native library as a RELRO producer, the putSharedRelrosToBundle() becomes
73  *   available to then send the Bundle to Linkers in other processes, consumed
74  *   by takeSharedRelrosFromBundle().
75  */
76 @JniIgnoreNatives
77 class Linker {
78     private static final String TAG = "Linker";
79 
80     // Name of the library that contains the JNI code.
81     private static final String LINKER_JNI_LIBRARY = "chromium_android_linker";
82 
83     // Constant guarding debug logging.
84     private static final boolean DEBUG = LibraryLoader.DEBUG;
85 
86     // Constants used to pass the shared RELRO Bundle through Binder.
87     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
88     static final String SHARED_RELROS = "org.chromium.base.android.linker.shared_relros";
89     private static final String BASE_LOAD_ADDRESS =
90             "org.chromium.base.android.linker.base_load_address";
91 
92     private final Object mLock = new Object();
93 
94     // Holds the address and the size of the reserved address range until the library is loaded.
95     // After that its |mLoadAddress| and |mLoadSize| will reflect the state of the loaded library.
96     // Further, when the RELRO region is extracted into shared memory, the |mRelroFd| is initialized
97     // along with |mRelro{Start,Size}|. This object is serialized for use in other processes if the
98     // process is a "RELRO producer".
99     @GuardedBy("mLock")
100     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
101     protected LibInfo mLocalLibInfo;
102 
103     // The library info that was transferred from another process. Only useful if it contains RELRO
104     // FD.
105     @GuardedBy("mLock")
106     private LibInfo mRemoteLibInfo;
107 
108     // Whether this Linker instance should potentially create the RELRO region. Even if true, the
109     // library loading can fall back to the system linker without producing the region. The default
110     // value is used in tests, it is set to true so that the Linker does not have to wait for RELRO
111     // to arrive from another process.
112     @GuardedBy("mLock")
113     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
114     boolean mRelroProducer = true;
115 
116     // Keeps stats about searching the WebView memory reservation. After each _successful_ library
117     // load a UMA histogram is recorded using this data.
118     @GuardedBy("mLock")
119     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
120     WebViewReservationSearchResult mWebviewReservationSearchResult;
121 
122     /**
123      * The state machine of library loading.
124      *
125      * The states are:
126      * - UNINITIALIZED: Initial state.
127      * - INITIALIZED: After linker initialization. Required for using the linker.
128      *
129      * When loading a library, there are two possibilities:
130      *
131      * - RELRO is not shared.
132      *
133      * - RELRO is shared: the producer process loads the library, consumers load the
134      *   native library without waiting, they use the RELRO bundle later when it arrives, or
135      *   immediately if it arrived before load
136      *
137      * Once the library has been loaded, in the producer process the state is DONE_PROVIDE_RELRO,
138      * and in consumer processes it is DONE.
139      *
140      * Transitions are:
141      * All processes: UNINITIALIZED -> INITIALIZED
142      * Producer: INITIALIZED -> DONE_PROVIDE_RELRO
143      * Consumer: INITIALIZED -> DONE
144      *
145      * When RELRO sharing failed for one reason or another, the state transitions remain the same,
146      * despite DONE_PROVIDE_RELRO being not appropriate as a name for this case.
147      */
148     @IntDef({State.UNINITIALIZED, State.INITIALIZED, State.DONE_PROVIDE_RELRO, State.DONE})
149     @Retention(RetentionPolicy.SOURCE)
150     private @interface State {
151         int UNINITIALIZED = 0;
152         int INITIALIZED = 1;
153         int DONE_PROVIDE_RELRO = 2;
154         int DONE = 3;
155     }
156 
157     @GuardedBy("mLock")
158     @State
159     private int mState = State.UNINITIALIZED;
160 
161     private static final String DETAILED_LOAD_TIME_HISTOGRAM_PREFIX =
162             "ChromiumAndroidLinker.ModernLinkerDetailedLoadTime.";
163 
164     private static final String DETAILED_LOAD_TIME_HISTOGRAM_PREFIX_BLKIO_CGROUP =
165             "ChromiumAndroidLinker.ModernLinkerDetailedLoadTimeByBlkioCgroup.";
166 
167     private static final String SUFFIX_UNKNOWN = "Unknown";
168 
169     private static final String SELF_CGROUP_FILE_NAME = "/proc/self/cgroup";
170 
pretendLibraryIsLoadedForTesting()171     void pretendLibraryIsLoadedForTesting() {
172         synchronized (mLock) {
173             mState = State.DONE;
174         }
175     }
176 
177     private static Linker sLinkerForAssert;
178 
Linker()179     Linker() {
180         // Only one instance is allowed in a given process because effects of loading a library are
181         // global, and the list of loaded libraries is not maintained at this level.
182         assert sLinkerForAssert == null;
183         sLinkerForAssert = this;
184     }
185 
186     @IntDef({PreferAddress.FIND_RESERVED, PreferAddress.RESERVE_HINT, PreferAddress.RESERVE_RANDOM})
187     @Retention(RetentionPolicy.SOURCE)
188     public @interface PreferAddress {
189         int FIND_RESERVED = 0;
190         int RESERVE_HINT = 1;
191         int RESERVE_RANDOM = 2;
192     }
preferAddressToString(@referAddress int a)193     private String preferAddressToString(@PreferAddress int a) {
194         switch (a) {
195             case PreferAddress.FIND_RESERVED:
196                 return "FIND_RESERVED";
197             case PreferAddress.RESERVE_HINT:
198                 return "RESERVE_HINT";
199             case PreferAddress.RESERVE_RANDOM:
200                 return "RESERVE_RANDOM";
201             default:
202                 return String.valueOf(a);
203         }
204     }
205 
206     /**
207      * A helper class to group a couple of stats related to WebView reservation lookup, and
208      * recording a histogram after that.
209      */
210     private static class WebViewReservationSearchResult {
211         private final boolean mSuccess;
212         private final long mDurationMs;
213 
WebViewReservationSearchResult(boolean searchSucceeded, long searchDurationMs)214         WebViewReservationSearchResult(boolean searchSucceeded, long searchDurationMs) {
215             mSuccess = searchSucceeded;
216             mDurationMs = searchDurationMs;
217         }
218 
recordHistograms(String suffix)219         private void recordHistograms(String suffix) {
220             String successAsString = mSuccess ? "Found" : "NotFound";
221             RecordHistogram.recordTimesHistogram(
222                     "ChromiumAndroidLinker.TimeToFindWebViewReservation." + successAsString + "."
223                             + suffix,
224                     mDurationMs);
225         }
226     }
227 
228     // Exposed to be able to mock out an assertion.
229     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
isNonZeroLoadAddress(LibInfo libInfo)230     boolean isNonZeroLoadAddress(LibInfo libInfo) {
231         return libInfo != null && libInfo.mLoadAddress != 0;
232     }
233 
234     /**
235      * Initializes the Linker. This is the first method to be called on the instance.
236      *
237      * The Linker abstracts away from knowing process types and what the role of each process is.
238      * The LibraryLoader and the layers above tell the singleton linker whether it needs to produce
239      * the RELRO region, consume it, whether to use the address hint or to synthesize according to a
240      * strategy.
241      *
242      * In many cases finding the library load address is on the critical path, and needs to be
243      * transferred to other processes as soon as possible. For this purpose initialization is
244      * invoked separately from loading.
245      *
246      * The caller should provide the |preference| for obtaining the address at which to load the
247      * native library. The value is one of these:
248      *
249      *     FIND_RESERVED, RESERVE_HINT, RESERVE_RANDOM.
250      *
251      * In the vast majority of cases the chosen preference will be fulfilled and the address (with
252      * the size) will be saved for use during {@link Linker#loadLibrary(String)}. In case the
253      * preferred way fails (due to address fragmentation, for example), a fallback attempt will be
254      * made with |preference| to the right of the current one in the list above. There is no
255      * fallback for RESERVE_RANDOM.
256      *
257      * FIND_RESERVED: Finds the (named) reserved address range for library loading. The caller needs
258      *     to make sure that this is only called on platform versions supporting this memory
259      *     reservation (Android Q+).
260      *
261      * RESERVE_HINT: Reserves enough of address range for loading a library, starting at the
262      *     |addressHint| provided. The latter is expected to arrive from another process (randomized
263      *     there), hence sometimes the address range may not be available.
264      *
265      * RESERVE_RANDOM: Finds a free random address range and reserves it.
266      *
267      * @param asRelroProducer whether the Linker instance will need to produce the shared memory
268      *                        region as part of work in {@link Linker#loadLibrary(String)}.
269      * @param preference the preference for obtaining the address, with fallback to a less memory
270      *                   efficient method
271      * @param addressHint the hint to be used when RESERVE_HINT is provided as |preference|
272      *
273      */
ensureInitialized( boolean asRelroProducer, @PreferAddress int preference, long addressHint)274     final void ensureInitialized(
275             boolean asRelroProducer, @PreferAddress int preference, long addressHint) {
276         if (DEBUG) {
277             Log.i(TAG,
278                     "ensureInitialized(asRelroProducer=%b, preference=%s, "
279                             + "loadAddressHint=0x%x)",
280                     asRelroProducer, preferAddressToString(preference), addressHint);
281         }
282         assert !asRelroProducer
283                 || preference
284                         != PreferAddress.RESERVE_HINT
285             : "Producer does not accept hints from outside";
286         synchronized (mLock) {
287             if (mState != State.UNINITIALIZED) return;
288             chooseAndReserveMemoryRange(asRelroProducer, preference, addressHint);
289             if (DEBUG) {
290                 Log.i(TAG, "ensureInitialized: chose address=0x%x", mLocalLibInfo.mLoadAddress);
291             }
292             mState = State.INITIALIZED;
293         }
294     }
295 
296     // Initializes the |mLocalLibInfo| and reserves the address range chosen.
297     @GuardedBy("mLock")
chooseAndReserveMemoryRange( boolean asRelroProducer, @PreferAddress int preference, long addressHint)298     private void chooseAndReserveMemoryRange(
299             boolean asRelroProducer, @PreferAddress int preference, long addressHint) {
300         mLocalLibInfo = new LibInfo();
301         mRelroProducer = asRelroProducer;
302         loadLinkerJniLibraryLocked();
303         switch (preference) {
304             case PreferAddress.FIND_RESERVED:
305                 UptimeMillisTimer timer = new UptimeMillisTimer();
306                 boolean reservationFound =
307                         getLinkerJni().findRegionReservedByWebViewZygote(mLocalLibInfo);
308                 saveWebviewReservationSearchStats(reservationFound, timer.getElapsedMillis());
309                 if (reservationFound) {
310                     assert isNonZeroLoadAddress(mLocalLibInfo);
311                     if (addressHint == 0 || addressHint == mLocalLibInfo.mLoadAddress) {
312                         // Subtle: Both the producer and the consumer are expected to find the same
313                         // address reservation. When |addressHint != 0| the producer was quick
314                         // enough to provide the address before the consumer started initialization.
315                         // Choosing the hint sounds like the right thing to do, and faster than
316                         // looking up the named address range again. However, there is not enough
317                         // information on how the hint was obtained by the producer. If it was found
318                         // by a fallback (less likely) then the region must be reserved in this
319                         // process. On systems where FIND_RESERVED is the preference, the most
320                         // likely variant is that it is already reserved, hence check for it first.
321                         return;
322                     }
323                 }
324                 // Intentional fallthrough.
325             case PreferAddress.RESERVE_HINT:
326                 mLocalLibInfo.mLoadAddress = addressHint;
327                 if (addressHint != 0) {
328                     getLinkerJni().reserveMemoryForLibrary(mLocalLibInfo);
329                     if (mLocalLibInfo.mLoadAddress != 0) return;
330                 }
331                 // Intentional fallthrough.
332             case PreferAddress.RESERVE_RANDOM:
333                 getLinkerJni().findMemoryRegionAtRandomAddress(mLocalLibInfo);
334         }
335     }
336 
337     @GuardedBy("mLock")
saveWebviewReservationSearchStats(boolean succeeded, long durationMs)338     private void saveWebviewReservationSearchStats(boolean succeeded, long durationMs) {
339         assert mState == State.UNINITIALIZED;
340         assert mWebviewReservationSearchResult == null;
341         mWebviewReservationSearchResult = new WebViewReservationSearchResult(succeeded, durationMs);
342     }
343 
344     /**
345      * Records UMA histograms related to library loading.
346      *
347      * @param suffix to append to the histogram name before recording it. A process type
348      * (e.g. "Browser") can be used here to avoid making the Linker aware of the process type.
349      */
recordHistograms(String suffix)350     void recordHistograms(String suffix) {
351         synchronized (mLock) {
352             if (mWebviewReservationSearchResult == null) return;
353             mWebviewReservationSearchResult.recordHistograms(suffix);
354         }
355     }
356 
357     /**
358      * Extracts the native library start address as serialized by
359      * {@link #putLoadAddressToBundle(Bundle)} in a Linker instance from another process.
360      */
extractLoadAddressFromBundle(Bundle bundle)361     static long extractLoadAddressFromBundle(Bundle bundle) {
362         return bundle.getLong(BASE_LOAD_ADDRESS, 0);
363     }
364 
365     /**
366      * Serializes the native library start address. If not asked to be initialized previously,
367      * initializes the Linker as a RELRO producer.
368      * @param bundle Bundle to put the address to.
369      */
putLoadAddressToBundle(Bundle bundle)370     void putLoadAddressToBundle(Bundle bundle) {
371         if (DEBUG) Log.i(TAG, "putLoadAddressToBundle");
372         synchronized (mLock) {
373             assert mState != State.UNINITIALIZED;
374             if (mLocalLibInfo != null && mLocalLibInfo.mLoadAddress != 0) {
375                 bundle.putLong(BASE_LOAD_ADDRESS, mLocalLibInfo.mLoadAddress);
376             }
377         }
378     }
379 
380     /**
381      * Tells whether atomic replacement of RELRO after library load should be performed. The linker
382      * should give up with RELRO on the retry that uses the RelroSharingMode.NO_SHARING. This method
383      * should be called after loading the library.
384      */
385     @GuardedBy("mLock")
shouldAtomicallyReplaceRelroAfterLoad()386     private boolean shouldAtomicallyReplaceRelroAfterLoad() {
387         if (mRemoteLibInfo != null && mState == State.DONE) {
388             // In the State.DONE while mRemoteLibInfo is not nullified yet. With an invalid load
389             // address it is impossible to locate the RELRO region in the current process. This
390             // could happen when the library loaded successfully only after the fallback to no
391             // sharing.
392             //
393             // TODO(pasko): There is no need to check for |mLoadAddress| here because in the worst
394             // case the zero address will be ignored on the native side of the
395             // atomicReplaceRelroLocked(). The takeSharedRelrosFromBundle() relies on zero addresses
396             // being ignored in native anyway. It seems the only effect of removing this check here
397             // will be extra added samples to the RelroSharingStatus2 histogram. This will be a tiny
398             // bit smoother to do after M99.
399             return mLocalLibInfo.mLoadAddress != 0;
400         }
401         return false;
402     }
403 
404     /**
405      * Loads the native library using a given mode.
406      *
407      * @param library The library name to load.
408      * @param relroMode Tells whether and how to share relocations.
409      */
410     @GuardedBy("mLock")
attemptLoadLibraryLocked(String library, @RelroSharingMode int relroMode)411     private void attemptLoadLibraryLocked(String library, @RelroSharingMode int relroMode) {
412         if (DEBUG) Log.i(TAG, "attemptLoadLibraryLocked: %s", library);
413         assert !library.equals(LINKER_JNI_LIBRARY);
414         loadLibraryImplLocked(library, relroMode);
415         if (DEBUG) {
416             Log.i(TAG, "Attempt to replace RELRO: remotenonnull=%b, state=%d",
417                     mRemoteLibInfo != null, mState);
418         }
419         if (shouldAtomicallyReplaceRelroAfterLoad()) {
420             atomicReplaceRelroLocked(/* relroAvailableImmediately= */ true);
421         }
422     }
423 
424     /**
425      * Loads the native shared library.
426      *
427      * The library must not be the Chromium linker library.
428      *
429      * @param library The library name to load.
430      */
loadLibrary(String library)431     final void loadLibrary(String library) {
432         synchronized (mLock) {
433             try {
434                 // Normally Chrome/Webview/Weblayer processes initialize when they choose whether to
435                 // produce or consume the shared relocations. Initialization here is the last resort
436                 // to choose the load address in tests that forget to decide whether they are a
437                 // producer or a consumer.
438                 ensureInitializedImplicitlyAsLastResort();
439 
440                 // Load the library. During initialization Linker subclass reserves the address
441                 // range where the library will be loaded and keeps it in |mLocalLibInfo|.
442                 attemptLoadLibraryLocked(library,
443                         mRelroProducer ? RelroSharingMode.PRODUCE : RelroSharingMode.CONSUME);
444             } catch (UnsatisfiedLinkError e) {
445                 Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
446                 try {
447                     // Retry without relocation sharing.
448                     mLocalLibInfo.mLoadAddress = 0;
449                     attemptLoadLibraryLocked(library, RelroSharingMode.NO_SHARING);
450                 } catch (UnsatisfiedLinkError e2) {
451                     Log.w(TAG, "Failed to load native library without RELRO sharing");
452                     throw e2;
453                 }
454             }
455         }
456     }
457 
458     /**
459      * Serializes information about the RELRO region to be passed to a Linker in another process.
460      * @param bundle The Bundle to serialize to.
461      */
putSharedRelrosToBundle(Bundle bundle)462     void putSharedRelrosToBundle(Bundle bundle) {
463         Bundle relros = null;
464         synchronized (mLock) {
465             if (DEBUG) Log.i(TAG, "putSharedRelrosToBundle: state=%d", mState);
466             if (mState == State.DONE_PROVIDE_RELRO) {
467                 assert mRelroProducer;
468                 relros = mLocalLibInfo.toBundle();
469             }
470             bundle.putBundle(SHARED_RELROS, relros);
471             if (DEBUG && relros != null) {
472                 Log.i(TAG,
473                         "putSharedRelrosToBundle() puts mLoadAddress=0x%x, mLoadSize=%d, "
474                                 + "mRelroFd=%d",
475                         mLocalLibInfo.mLoadAddress, mLocalLibInfo.mLoadSize,
476                         mLocalLibInfo.mRelroFd);
477             }
478         }
479     }
480 
481     /**
482      * Deserializes the RELRO region information that was marshalled by
483      * {@link #putLoadAddressToBundle(Bundle)} and wakes up the threads waiting for it to replace
484      * the RELRO section in this process with shared memory.
485      * @param bundle The Bundle to extract the information from.
486      */
takeSharedRelrosFromBundle(Bundle bundle)487     void takeSharedRelrosFromBundle(Bundle bundle) {
488         if (DEBUG) Log.i(TAG, "called takeSharedRelrosFromBundle(%s)", bundle);
489         Bundle relros = bundle.getBundle(SHARED_RELROS);
490         if (relros == null) return;
491         LibInfo newRemote = LibInfo.fromBundle(relros);
492         if (newRemote == null) return;
493         synchronized (mLock) {
494             if (mRemoteLibInfo != null && mRemoteLibInfo.mRelroFd != -1) {
495                 if (DEBUG) {
496                     Log.i(TAG,
497                             "Attempt to replace RELRO a second time "
498                                     + "library addr=0x%x, with new library addr=0x%x",
499                             mRemoteLibInfo.mLoadAddress, newRemote.mLoadAddress);
500                 }
501                 return;
502             }
503             mRemoteLibInfo = newRemote;
504             if (mState == State.DONE) {
505                 atomicReplaceRelroLocked(/* relroAvailableImmediately= */ false);
506             }
507         }
508     }
509 
510     @IntDef({Linker.RelroSharingMode.NO_SHARING, Linker.RelroSharingMode.PRODUCE,
511             Linker.RelroSharingMode.CONSUME})
512     @Retention(RetentionPolicy.SOURCE)
513     private @interface RelroSharingMode {
514         // Do not attempt to create or use a RELRO region.
515         int NO_SHARING = 0;
516 
517         // Produce a shared memory region with relocations.
518         int PRODUCE = 1;
519 
520         // Load the library and (potentially later) use the externally provided region.
521         int CONSUME = 2;
522     }
523 
524     // Loads the library via Linker for later consumption of the RELRO region, throws on
525     // failure to allow a safe retry.
526     @GuardedBy("mLock")
loadWithoutProducingRelro(String libFilePath)527     private void loadWithoutProducingRelro(String libFilePath) {
528         assert mRemoteLibInfo == null || libFilePath.equals(mRemoteLibInfo.mLibFilePath);
529         if (!getLinkerJni().loadLibrary(libFilePath, mLocalLibInfo, false /* spawnRelroRegion */)) {
530             resetAndThrow(String.format("Unable to load library: %s", libFilePath));
531         }
532         assert mLocalLibInfo.mRelroFd == -1;
533     }
534 
535     // Loads the library via Linker. Does not throw on failure because in both cases
536     // System.loadLibrary() is useful. Records a histogram to count failures.
537     @GuardedBy("mLock")
loadAndProduceSharedRelro(String libFilePath)538     private void loadAndProduceSharedRelro(String libFilePath) {
539         mLocalLibInfo.mLibFilePath = libFilePath;
540         if (getLinkerJni().loadLibrary(libFilePath, mLocalLibInfo, true /* spawnRelroRegion */)) {
541             if (DEBUG) {
542                 Log.i(TAG, "Successfully spawned RELRO: mLoadAddress=0x%x, mLoadSize=%d",
543                         mLocalLibInfo.mLoadAddress, mLocalLibInfo.mLoadSize);
544             }
545         } else {
546             Log.e(TAG, "Unable to load with Linker, using the system linker instead");
547             // System.loadLibrary() below implements the fallback.
548             mLocalLibInfo.mRelroFd = -1;
549         }
550         RecordHistogram.recordBooleanHistogram(
551                 "ChromiumAndroidLinker.RelroProvidedSuccessfully", mLocalLibInfo.mRelroFd != -1);
552     }
553 
554     /**
555      * Linker-specific entry point for library loading. Loads the library into the address range
556      * provided by mLocalLibInfo. Assumes that the range is reserved with mmap(2).
557      *
558      * If the library is within a zip file, it must be uncompressed and page aligned in this file.
559      *
560      * The {@link #atomicReplaceRelroLocked(boolean)} must be implemented to *atomically* replace
561      * the RELRO region. Atomicity is required because the library code can be running concurrently
562      * on another thread.
563      *
564      * @param library The name of the library to load.
565      * @param relroMode Tells whether to use RELRO sharing and whether to produce or consume the
566      *                  RELRO region.
567      */
568     @GuardedBy("mLock")
loadLibraryImplLocked(String library, @RelroSharingMode int relroMode)569     private void loadLibraryImplLocked(String library, @RelroSharingMode int relroMode) {
570         // Only loading monochrome is supported.
571         if (!"monochrome".equals(library) || DEBUG) {
572             Log.i(TAG, "loadLibraryImplLocked: %s, relroMode=%d", library, relroMode);
573         }
574         assert mState == State.INITIALIZED; // Only one successful call.
575 
576         // Determine whether library loading starts in a foreground or a background cgroup for the
577         // 'blkio' controller.
578         String backgroundStateBeforeLoad = readBackgroundStateFromCgroups();
579 
580         // Load or declare fallback to System.loadLibrary.
581         UptimeMillisTimer timer = new UptimeMillisTimer();
582         String libFilePath = System.mapLibraryName(library);
583         boolean performedModernLoad = true;
584         if (relroMode == RelroSharingMode.NO_SHARING) {
585             // System.loadLibrary() below implements the fallback.
586             performedModernLoad = false;
587             mState = State.DONE;
588         } else if (relroMode == RelroSharingMode.PRODUCE) {
589             loadAndProduceSharedRelro(libFilePath); // Throws on a failed load.
590             // Next state is still to "provide relro", even if there is none, to indicate that
591             // consuming RELRO is not expected with this Linker instance.
592             mState = State.DONE_PROVIDE_RELRO;
593         } else {
594             assert relroMode == RelroSharingMode.CONSUME;
595             loadWithoutProducingRelro(libFilePath); // Does not throw.
596             // Done loading the library, but using an externally provided RELRO may happen later.
597             mState = State.DONE;
598         }
599 
600         // The app can change the bg/fg state while loading the native library, but mostly only
601         // once. To reduce the likelihood of a foreground sample to be affected by partially
602         // backgrounded state, move the mixed samples to a separate category. The data collected may
603         // help proving this hypothesis: "The Linker is not a lot slower than the system
604         // linker when running in foreground".
605         String backgroundStateAfterLoad = readBackgroundStateFromCgroups();
606         if (!backgroundStateBeforeLoad.equals(backgroundStateAfterLoad)) {
607             if (backgroundStateBeforeLoad.equals(SUFFIX_UNKNOWN)
608                     || backgroundStateAfterLoad.equals(SUFFIX_UNKNOWN)) {
609                 backgroundStateBeforeLoad = SUFFIX_UNKNOWN;
610             } else {
611                 backgroundStateBeforeLoad = "Mixed";
612             }
613         }
614 
615         if (performedModernLoad) {
616             recordDetailedLoadTimeSince(timer,
617                     relroMode == RelroSharingMode.PRODUCE ? "Produce" : "Consume",
618                     backgroundStateBeforeLoad);
619         }
620 
621         // Load the library a second time, in order to keep using lazy JNI registration. When
622         // loading the library with the Chromium linker, ART doesn't know about our library, so
623         // cannot resolve JNI methods lazily. Loading the library a second time makes sure it
624         // knows about us.
625         //
626         // This is not wasteful though, as libraries are reference-counted, and as a consequence the
627         // library is not really loaded a second time, and we keep relocation sharing.
628         timer = new UptimeMillisTimer();
629         try {
630             System.loadLibrary(library);
631         } catch (UnsatisfiedLinkError e) {
632             resetAndThrow("Failed at System.loadLibrary()");
633         }
634         recordDetailedLoadTimeSince(
635                 timer, performedModernLoad ? "Second" : "NoSharing", backgroundStateBeforeLoad);
636     }
637 
638     /**
639      * Atomically replaces the RELRO with the shared memory region described in the
640      * |mRemoteLibInfo|. In order to perform the replacement verifies that the replacement is safe
641      * by inspecting |mLocalLibInfo| for equality of the library address range and the contents of
642      * the RELRO region.
643      *
644      * @param relroAvailableImmediately Whether the RELRO bundle arrived before
645      * {@link #loadLibraryImplLocked(String, int)} was called.
646      */
647     @GuardedBy("mLock")
atomicReplaceRelroLocked(boolean relroAvailableImmediately)648     private void atomicReplaceRelroLocked(boolean relroAvailableImmediately) {
649         assert mRemoteLibInfo != null;
650         assert mState == State.DONE;
651         if (mRemoteLibInfo.mRelroFd == -1) return;
652         if (DEBUG) {
653             Log.i(TAG, "Received mRemoteLibInfo: mLoadAddress=0x%x, mLoadSize=%d",
654                     mRemoteLibInfo.mLoadAddress, mRemoteLibInfo.mLoadSize);
655         }
656         if (mLocalLibInfo == null) return;
657         getLinkerJni().useRelros(mLocalLibInfo.mLoadAddress, mRemoteLibInfo);
658         // *Not* closing the RELRO FD after using it because the FD may need to be transferred to
659         // another process after this point.
660         if (DEBUG) Log.i(TAG, "Immediate RELRO availability: %b", relroAvailableImmediately);
661         RecordHistogram.recordBooleanHistogram(
662                 "ChromiumAndroidLinker.RelroAvailableImmediately", relroAvailableImmediately);
663         int status = getLinkerJni().getRelroSharingResult();
664         assert status != RelroSharingStatus.NOT_ATTEMPTED;
665         RecordHistogram.recordEnumeratedHistogram(
666                 "ChromiumAndroidLinker.RelroSharingStatus2", status, RelroSharingStatus.COUNT);
667     }
668 
669     /** Loads the Linker JNI library. Throws UnsatisfiedLinkError on error. */
670     @SuppressLint({"UnsafeDynamicallyLoadedCode"})
671     @GuardedBy("mLock")
672     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
loadLinkerJniLibraryLocked()673     void loadLinkerJniLibraryLocked() {
674         assert mState == State.UNINITIALIZED;
675 
676         LibraryLoader.setEnvForNative();
677         if (DEBUG) Log.i(TAG, "Loading lib%s.so", LINKER_JNI_LIBRARY);
678 
679         // May throw UnsatisfiedLinkError, we do not catch it as we cannot continue if we cannot
680         // load the linker. Technically we could try to load the library with the system linker on
681         // Android M+, but this should never happen, better to catch it in crash reports.
682         System.loadLibrary(LINKER_JNI_LIBRARY);
683     }
684 
685     /**
686      * Initializes the auxiliary native library unless it was initialized before.
687      *
688      * Initializes as a RELRO producer without knowledge about preferred placement of the RELRO
689      * region. Should only be used as the last resort: when the simplicity of avoiding the explicit
690      * initialization is preferred over memory savings, such as in tests.
691      */
ensureInitializedImplicitlyAsLastResort()692     private void ensureInitializedImplicitlyAsLastResort() {
693         ensureInitialized(
694                 /* asRelroProducer= */ true, PreferAddress.RESERVE_RANDOM, /* addressHint= */ 0);
695     }
696 
extractBlkioCgroupFromLine(String line)697     private static String extractBlkioCgroupFromLine(String line) {
698         // The contents of /proc/self/cgroup for a background app looks like this:
699         // 5:schedtune:/background
700         // 4:memory:/
701         // 3:cpuset:/background
702         // 2:cpu:/system
703         // 1:blkio:/background
704         // 0::/uid_10179/pid_11869
705         //
706         // For a foreground app the relevant line looks like this:
707         // 1:blkio:/
708         int blkioStartsAt = line.indexOf(":blkio:");
709         if (blkioStartsAt == -1) return "";
710         return line.substring(blkioStartsAt + 7);
711     }
712 
readBackgroundStateFromCgroups()713     private String readBackgroundStateFromCgroups() {
714         String groupName = null;
715         try (BufferedReader reader = new BufferedReader(
716                      new InputStreamReader(new FileInputStream(SELF_CGROUP_FILE_NAME)));) {
717             String line;
718             while ((line = reader.readLine()) != null) {
719                 groupName = extractBlkioCgroupFromLine(line);
720                 if (!groupName.equals("")) break;
721             }
722             if (groupName == null || groupName.equals("")) return SUFFIX_UNKNOWN;
723         } catch (IOException e) {
724             Log.e(TAG, "IOException while reading %s", SELF_CGROUP_FILE_NAME);
725             return SUFFIX_UNKNOWN;
726         }
727         if (groupName.equals("/")) {
728             return "Foreground";
729         }
730         if (groupName.equals("/background")) {
731             return "Background";
732         }
733         Log.e(TAG, "blkio cgroup with unexpected name: '%s'", groupName);
734         return SUFFIX_UNKNOWN;
735     }
736 
recordDetailedLoadTimeSince( UptimeMillisTimer timer, String suffix, String backgroundStateSuffix)737     private void recordDetailedLoadTimeSince(
738             UptimeMillisTimer timer, String suffix, String backgroundStateSuffix) {
739         long durationMs = timer.getElapsedMillis();
740         RecordHistogram.recordTimesHistogram(
741                 DETAILED_LOAD_TIME_HISTOGRAM_PREFIX + suffix, durationMs);
742         RecordHistogram.recordTimesHistogram(DETAILED_LOAD_TIME_HISTOGRAM_PREFIX_BLKIO_CGROUP
743                         + suffix + "." + backgroundStateSuffix,
744                 durationMs);
745     }
746 
747     @GuardedBy("mLock")
resetAndThrow(String message)748     private void resetAndThrow(String message) {
749         mState = State.INITIALIZED;
750         Log.e(TAG, message);
751         throw new UnsatisfiedLinkError(message);
752     }
753 
reportDlopenExtTime(long millis)754     public static void reportDlopenExtTime(long millis) {
755         RecordHistogram.recordTimesHistogram(
756                 "ChromiumAndroidLinker.ModernLinkerDlopenExtTime", millis);
757     }
758 
reportIteratePhdrTime(long millis)759     public static void reportIteratePhdrTime(long millis) {
760         RecordHistogram.recordTimesHistogram(
761                 "ChromiumAndroidLinker.ModernLinkerIteratePhdrTime", millis);
762     }
763 
764     /**
765      * Holds the information for a given native library or the address range for the future library
766      * load. Owns the shared RELRO file descriptor.
767      *
768      * Native code accesses the fields of this class by name. Renaming should be done on C++ size as
769      * well.
770      */
771     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
772     @JniIgnoreNatives
773     static class LibInfo implements Parcelable {
774         private static final String EXTRA_LINKER_LIB_INFO = "libinfo";
775 
LibInfo()776         LibInfo() {}
777 
778         // from Parcelable
LibInfo(Parcel in)779         LibInfo(Parcel in) {
780             // See below in writeToParcel() for the serialization protocol.
781             mLibFilePath = in.readString();
782             mLoadAddress = in.readLong();
783             mLoadSize = in.readLong();
784             mRelroStart = in.readLong();
785             mRelroSize = in.readLong();
786             boolean hasRelroFd = in.readInt() != 0;
787             if (hasRelroFd) {
788                 ParcelFileDescriptor fd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
789                 // If CreateSharedRelro fails, the OS file descriptor will be -1 and |fd| will be
790                 // null.
791                 if (fd != null) {
792                     mRelroFd = fd.detachFd();
793                 }
794             } else {
795                 mRelroFd = -1;
796             }
797         }
798 
close()799         public void close() {
800             if (mRelroFd >= 0) {
801                 StreamUtil.closeQuietly(ParcelFileDescriptor.adoptFd(mRelroFd));
802                 mRelroFd = -1;
803             }
804         }
805 
fromBundle(Bundle bundle)806         public static LibInfo fromBundle(Bundle bundle) {
807             bundle.setClassLoader(Linker.class.getClassLoader());
808             return bundle.getParcelable(EXTRA_LINKER_LIB_INFO);
809         }
810 
toBundle()811         public Bundle toBundle() {
812             Bundle bundle = new Bundle();
813             bundle.putParcelable(EXTRA_LINKER_LIB_INFO, this);
814             return bundle;
815         }
816 
817         @Override
writeToParcel(Parcel out, int flags)818         public void writeToParcel(Parcel out, int flags) {
819             out.writeString(mLibFilePath);
820             out.writeLong(mLoadAddress);
821             out.writeLong(mLoadSize);
822             out.writeLong(mRelroStart);
823             out.writeLong(mRelroSize);
824             // Parcel#writeBoolean() is API level 29, so use an int instead.
825             // We use this as a flag as we cannot serialize an invalid fd.
826             out.writeInt(mRelroFd >= 0 ? 1 : 0);
827             if (mRelroFd >= 0) {
828                 try {
829                     ParcelFileDescriptor fd = ParcelFileDescriptor.fromFd(mRelroFd);
830                     fd.writeToParcel(out, 0);
831                     fd.close();
832                 } catch (java.io.IOException e) {
833                     Log.e(TAG, "Can't write LibInfo file descriptor to parcel", e);
834                 }
835             }
836         }
837 
838         @Override
describeContents()839         public int describeContents() {
840             return Parcelable.CONTENTS_FILE_DESCRIPTOR;
841         }
842 
843         // From Parcelable
844         public static final Parcelable.Creator<LibInfo> CREATOR =
845                 new Parcelable.Creator<LibInfo>() {
846                     @Override
847                     public LibInfo createFromParcel(Parcel in) {
848                         return new LibInfo(in);
849                     }
850 
851                     @Override
852                     public LibInfo[] newArray(int size) {
853                         return new LibInfo[size];
854                     }
855                 };
856 
857         public String mLibFilePath;
858 
859         // IMPORTANT: Don't change these fields without modifying the
860         // native code that accesses them directly!
861         @AccessedByNative
862         public long mLoadAddress; // page-aligned library load address.
863         @AccessedByNative
864         public long mLoadSize;    // page-aligned library load size.
865         @AccessedByNative
866         public long mRelroStart;  // page-aligned address in memory, or 0 if none.
867         @AccessedByNative
868         public long mRelroSize;   // page-aligned size in memory, or 0.
869         @AccessedByNative
870         public int mRelroFd = -1; // shared RELRO file descriptor, or -1
871     }
872 
873     // Intentionally omitting @NativeMethods because generation of the stubs it requires (as
874     // GEN_JNI.java) is disabled by the @JniIgnoreNatives.
875     interface Natives {
876         /**
877          * Reserves a memory region (=mapping) of sufficient size to hold the loaded library before
878          * the real size is known. The mmap(2) being used here provides built in randomization.
879          *
880          * On failure |libInfo.mLoadAddress| should be set to 0 and the LibraryLoader will fall back
881          * to loading using the system linker.
882          *
883          * @param libInfo holds the output values: |mLoadAddress| and |mLoadSize|. On failure sets
884          *                the |libInfo.mLoadAddress| to 0.
885          */
findMemoryRegionAtRandomAddress(@onNull LibInfo libInfo)886         void findMemoryRegionAtRandomAddress(@NonNull LibInfo libInfo);
887 
888         /**
889          * Reserves the fixed address range starting at |libInfo.mLoadAddress| big enough to load
890          * the main native library. The size of the range is an internal detail of the native
891          * implementation.
892          *
893          * @param libInfo holds the output values: |mLoadAddress| and |mLoadSize|. On success
894          *                returns the size in |libInfo.mLoadSize|. On failure sets the
895          *                |libInfo.mLoadAddress| to 0.
896          */
reserveMemoryForLibrary(@onNull LibInfo libInfo)897         void reserveMemoryForLibrary(@NonNull LibInfo libInfo);
898 
899         /**
900          * Finds the (named) address range reservation made by the system zygote and dedicated for
901          * loading the native library. Reads /proc/self/maps, which is a slow operation (up to a few
902          * ms).
903          *
904          * @param libInfo holds the output values: |mLoadAddress| and |mLoadSize|. On success saves
905          *                the start address and the size of the webview memory reservation to them.
906          * @return whether the region was found.
907          */
findRegionReservedByWebViewZygote(@onNull LibInfo libInfo)908         boolean findRegionReservedByWebViewZygote(@NonNull LibInfo libInfo);
909 
910         /**
911          * Load the native library.
912          *
913          * @param libFilePath library file name.
914          * @param libInfo holds the information about the loaded library and the associated RELRO
915          *        region if the latter was created.
916          * @param spawnRelroRegion whether to spawn a new RELRO region.
917          * @return false on failure.
918          */
loadLibrary(String libFilePath, LibInfo libInfo, boolean spawnRelroRegion)919         boolean loadLibrary(String libFilePath, LibInfo libInfo, boolean spawnRelroRegion);
920 
921         /**
922          * Replace the current RELRO data in memory with the incoming RELRO region.
923          *
924          * @param localLoadAddress the address at which this Linker loaded the  native library.
925          * @param remoteLibInfo contains the RELRO region for replacement, and the start address
926          *        required for the library to be able to use this region.
927          * @return whether the operation was a success.
928          */
useRelros(long localLoadAddress, LibInfo remoteLibInfo)929         boolean useRelros(long localLoadAddress, LibInfo remoteLibInfo);
930 
931         /**
932          * Reveals the result of RELRO sharing after the library has been loaded.
933          *
934          * @return RelroSharingStatus.
935          */
getRelroSharingResult()936         int getRelroSharingResult();
937     }
938 
939     private static Linker.Natives sNativesInstance;
940 
setLinkerNativesForTesting(Natives instance)941     static void setLinkerNativesForTesting(Natives instance) {
942         sNativesInstance = instance;
943         sLinkerForAssert = null; // Also allow to create Linker multiple times in tests.
944     }
945 
946     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
getLinkerJni()947     static Linker.Natives getLinkerJni() {
948         if (sNativesInstance != null) return sNativesInstance;
949         return new LinkerJni(); // R8 optimizes away all construction except the initial one.
950     }
951 }
952