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