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