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