1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.library_loader; 6 7 import android.os.Bundle; 8 import android.os.Parcel; 9 10 import org.chromium.base.Log; 11 import org.chromium.base.SysUtils; 12 import org.chromium.base.ThreadUtils; 13 import org.chromium.base.annotations.CalledByNative; 14 import org.chromium.base.annotations.MainDex; 15 16 import java.util.HashMap; 17 import java.util.Locale; 18 import java.util.Map; 19 20 import javax.annotation.Nullable; 21 22 /* 23 * For more, see Technical note, Security considerations, and the explanation 24 * of how this class is supposed to be used in Linker.java. 25 */ 26 27 /** 28 * Provides a concrete implementation of the Chromium Linker. 29 * 30 * This Linker implementation uses the crazy linker to map and then run Chrome 31 * for Android. 32 * 33 * For more on the operations performed by the Linker, see {@link Linker}. 34 */ 35 @MainDex 36 class LegacyLinker extends Linker { 37 // Log tag for this class. 38 private static final String TAG = "LibraryLoader"; 39 40 // Becomes true after linker initialization. 41 private boolean mInitialized = false; 42 43 // Set to true if this runs in the browser process. Disabled by initServiceProcess(). 44 private boolean mInBrowserProcess = true; 45 46 // Becomes true to indicate this process needs to wait for a shared RELRO in 47 // finishLibraryLoad(). 48 private boolean mWaitForSharedRelros = false; 49 50 // Becomes true when initialization determines that the browser process can use the 51 // shared RELRO. 52 private boolean mBrowserUsesSharedRelro = false; 53 54 // The map of all RELRO sections either created or used in this process. 55 private Bundle mSharedRelros = null; 56 57 // Current common random base load address. A value of -1 indicates not yet initialized. 58 private long mBaseLoadAddress = -1; 59 60 // Current fixed-location load address for the next library called by loadLibrary(). 61 // A value of -1 indicates not yet initialized. 62 private long mCurrentLoadAddress = -1; 63 64 // Becomes true once prepareLibraryLoad() has been called. 65 private boolean mPrepareLibraryLoadCalled = false; 66 67 // The map of libraries that are currently loaded in this process. 68 private HashMap<String, LibInfo> mLoadedLibraries = null; 69 70 // Private singleton constructor, and singleton factory method. LegacyLinker()71 private LegacyLinker() { } create()72 static Linker create() { 73 return new LegacyLinker(); 74 } 75 76 // Used internally to initialize the linker's data. Assumes lock is held. 77 // Loads JNI, and sets mMemoryDeviceConfig and mBrowserUsesSharedRelro. ensureInitializedLocked()78 private void ensureInitializedLocked() { 79 assert Thread.holdsLock(mLock); 80 81 if (mInitialized || !NativeLibraries.sUseLinker) { 82 return; 83 } 84 85 // On first call, load libchromium_android_linker.so. Cannot be done in the 86 // constructor because instantiation occurs on the UI thread. 87 loadLinkerJniLibrary(); 88 89 if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) { 90 if (SysUtils.isLowEndDevice()) { 91 mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_LOW; 92 } else { 93 mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_NORMAL; 94 } 95 } 96 97 // Cannot run in the constructor because SysUtils.isLowEndDevice() relies 98 // on CommandLine, which may not be available at instantiation. 99 switch (BROWSER_SHARED_RELRO_CONFIG) { 100 case BROWSER_SHARED_RELRO_CONFIG_NEVER: 101 mBrowserUsesSharedRelro = false; 102 break; 103 case BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY: 104 if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) { 105 mBrowserUsesSharedRelro = true; 106 Log.w(TAG, "Low-memory device: shared RELROs used in all processes"); 107 } else { 108 mBrowserUsesSharedRelro = false; 109 } 110 break; 111 case BROWSER_SHARED_RELRO_CONFIG_ALWAYS: 112 Log.w(TAG, "Beware: shared RELROs used in all processes!"); 113 mBrowserUsesSharedRelro = true; 114 break; 115 default: 116 Log.wtf(TAG, "FATAL: illegal shared RELRO config"); 117 throw new AssertionError(); 118 } 119 120 mInitialized = true; 121 } 122 123 /** 124 * Call this method to determine if the linker will try to use shared RELROs 125 * for the browser process. 126 */ 127 @Override isUsingBrowserSharedRelros()128 public boolean isUsingBrowserSharedRelros() { 129 synchronized (mLock) { 130 ensureInitializedLocked(); 131 return mInBrowserProcess && mBrowserUsesSharedRelro; 132 } 133 } 134 135 /** 136 * Call this method just before loading any native shared libraries in this process. 137 */ 138 @Override prepareLibraryLoad()139 public void prepareLibraryLoad() { 140 if (DEBUG) { 141 Log.i(TAG, "prepareLibraryLoad() called"); 142 } 143 synchronized (mLock) { 144 ensureInitializedLocked(); 145 mPrepareLibraryLoadCalled = true; 146 147 if (mInBrowserProcess) { 148 // Force generation of random base load address, as well 149 // as creation of shared RELRO sections in this process. 150 setupBaseLoadAddressLocked(); 151 } 152 } 153 } 154 155 /** 156 * Call this method just after loading all native shared libraries in this process. 157 * Note that when in a service process, this will block until the RELRO bundle is 158 * received, i.e. when another thread calls useSharedRelros(). 159 */ 160 @Override finishLibraryLoad()161 public void finishLibraryLoad() { 162 if (DEBUG) { 163 Log.i(TAG, "finishLibraryLoad() called"); 164 } 165 synchronized (mLock) { 166 ensureInitializedLocked(); 167 if (DEBUG) { 168 Log.i(TAG, String.format( 169 Locale.US, 170 "mInBrowserProcess=%b mBrowserUsesSharedRelro=%b mWaitForSharedRelros=%b", 171 mInBrowserProcess, mBrowserUsesSharedRelro, mWaitForSharedRelros)); 172 } 173 174 if (mLoadedLibraries == null) { 175 if (DEBUG) { 176 Log.i(TAG, "No libraries loaded"); 177 } 178 } else { 179 if (mInBrowserProcess) { 180 // Create new Bundle containing RELRO section information 181 // for all loaded libraries. Make it available to getSharedRelros(). 182 mSharedRelros = createBundleFromLibInfoMap(mLoadedLibraries); 183 if (DEBUG) { 184 Log.i(TAG, "Shared RELRO created"); 185 dumpBundle(mSharedRelros); 186 } 187 188 if (mBrowserUsesSharedRelro) { 189 useSharedRelrosLocked(mSharedRelros); 190 } 191 } 192 193 if (mWaitForSharedRelros) { 194 assert !mInBrowserProcess; 195 196 // Wait until the shared relro bundle is received from useSharedRelros(). 197 while (mSharedRelros == null) { 198 try { 199 mLock.wait(); 200 } catch (InterruptedException ie) { 201 // Restore the thread's interrupt status. 202 Thread.currentThread().interrupt(); 203 } 204 } 205 useSharedRelrosLocked(mSharedRelros); 206 // Clear the Bundle to ensure its file descriptor references can't be reused. 207 mSharedRelros.clear(); 208 mSharedRelros = null; 209 } 210 } 211 212 // If testing, run tests now that all libraries are loaded and initialized. 213 if (NativeLibraries.sEnableLinkerTests) { 214 runTestRunnerClassForTesting(mMemoryDeviceConfig, mInBrowserProcess); 215 } 216 } 217 if (DEBUG) { 218 Log.i(TAG, "finishLibraryLoad() exiting"); 219 } 220 } 221 222 /** 223 * Call this to send a Bundle containing the shared RELRO sections to be 224 * used in this process. If initServiceProcess() was previously called, 225 * finishLibraryLoad() will not exit until this method is called in another 226 * thread with a non-null value. 227 * 228 * @param bundle The Bundle instance containing a map of shared RELRO sections 229 * to use in this process. 230 */ 231 @Override useSharedRelros(Bundle bundle)232 public void useSharedRelros(Bundle bundle) { 233 // Ensure the bundle uses the application's class loader, not the framework 234 // one which doesn't know anything about LibInfo. 235 // Also, hold a fresh copy of it so the caller can't recycle it. 236 Bundle clonedBundle = null; 237 if (bundle != null) { 238 bundle.setClassLoader(LibInfo.class.getClassLoader()); 239 clonedBundle = new Bundle(LibInfo.class.getClassLoader()); 240 Parcel parcel = Parcel.obtain(); 241 bundle.writeToParcel(parcel, 0); 242 parcel.setDataPosition(0); 243 clonedBundle.readFromParcel(parcel); 244 parcel.recycle(); 245 } 246 if (DEBUG) { 247 Log.i(TAG, "useSharedRelros() called with " + bundle 248 + ", cloned " + clonedBundle); 249 } 250 synchronized (mLock) { 251 // Note that in certain cases, this can be called before 252 // initServiceProcess() in service processes. 253 mSharedRelros = clonedBundle; 254 // Tell any listener blocked in finishLibraryLoad() about it. 255 mLock.notifyAll(); 256 } 257 } 258 259 /** 260 * Call this to retrieve the shared RELRO sections created in this process, 261 * after loading all libraries. 262 * 263 * @return a new Bundle instance, or null if RELRO sharing is disabled on 264 * this system, or if initServiceProcess() was called previously. 265 */ 266 @Override getSharedRelros()267 public Bundle getSharedRelros() { 268 if (DEBUG) { 269 Log.i(TAG, "getSharedRelros() called"); 270 } 271 synchronized (mLock) { 272 if (!mInBrowserProcess) { 273 if (DEBUG) { 274 Log.i(TAG, "... returning null Bundle"); 275 } 276 return null; 277 } 278 279 // Return the Bundle created in finishLibraryLoad(). 280 if (DEBUG) { 281 Log.i(TAG, "... returning " + mSharedRelros); 282 } 283 return mSharedRelros; 284 } 285 } 286 287 /** 288 * Call this method before loading any libraries to indicate that this 289 * process shall neither create or reuse shared RELRO sections. 290 */ 291 @Override disableSharedRelros()292 public void disableSharedRelros() { 293 if (DEBUG) { 294 Log.i(TAG, "disableSharedRelros() called"); 295 } 296 synchronized (mLock) { 297 ensureInitializedLocked(); 298 mInBrowserProcess = false; 299 mWaitForSharedRelros = false; 300 mBrowserUsesSharedRelro = false; 301 } 302 } 303 304 /** 305 * Call this method before loading any libraries to indicate that this 306 * process is ready to reuse shared RELRO sections from another one. 307 * Typically used when starting service processes. 308 * 309 * @param baseLoadAddress the base library load address to use. 310 */ 311 @Override initServiceProcess(long baseLoadAddress)312 public void initServiceProcess(long baseLoadAddress) { 313 if (DEBUG) { 314 Log.i(TAG, String.format( 315 Locale.US, "initServiceProcess(0x%x) called", 316 baseLoadAddress)); 317 } 318 synchronized (mLock) { 319 ensureInitializedLocked(); 320 mInBrowserProcess = false; 321 mBrowserUsesSharedRelro = false; 322 mWaitForSharedRelros = true; 323 mBaseLoadAddress = baseLoadAddress; 324 mCurrentLoadAddress = baseLoadAddress; 325 } 326 } 327 328 /** 329 * Retrieve the base load address of all shared RELRO sections. 330 * This also enforces the creation of shared RELRO sections in 331 * prepareLibraryLoad(), which can later be retrieved with getSharedRelros(). 332 * 333 * @return a common, random base load address, or 0 if RELRO sharing is 334 * disabled. 335 */ 336 @Override getBaseLoadAddress()337 public long getBaseLoadAddress() { 338 synchronized (mLock) { 339 ensureInitializedLocked(); 340 if (!mInBrowserProcess) { 341 Log.w(TAG, "Shared RELRO sections are disabled in this process!"); 342 return 0; 343 } 344 345 setupBaseLoadAddressLocked(); 346 if (DEBUG) { 347 Log.i(TAG, String.format( 348 Locale.US, "getBaseLoadAddress() returns 0x%x", 349 mBaseLoadAddress)); 350 } 351 return mBaseLoadAddress; 352 } 353 } 354 355 // Used internally to lazily setup the common random base load address. setupBaseLoadAddressLocked()356 private void setupBaseLoadAddressLocked() { 357 assert Thread.holdsLock(mLock); 358 if (mBaseLoadAddress == -1) { 359 mBaseLoadAddress = getRandomBaseLoadAddress(); 360 mCurrentLoadAddress = mBaseLoadAddress; 361 if (mBaseLoadAddress == 0) { 362 // If the random address is 0 there are issues with finding enough 363 // free address space, so disable RELRO shared / fixed load addresses. 364 Log.w(TAG, "Disabling shared RELROs due address space pressure"); 365 mBrowserUsesSharedRelro = false; 366 mWaitForSharedRelros = false; 367 } 368 } 369 } 370 371 // Used for debugging only. dumpBundle(Bundle bundle)372 private void dumpBundle(Bundle bundle) { 373 if (DEBUG) { 374 Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle); 375 } 376 } 377 378 /** 379 * Use the shared RELRO section from a Bundle received form another process. 380 * Call this after calling setBaseLoadAddress() then loading all libraries 381 * with loadLibrary(). 382 * 383 * @param bundle Bundle instance generated with createSharedRelroBundle() in 384 * another process. 385 */ useSharedRelrosLocked(Bundle bundle)386 private void useSharedRelrosLocked(Bundle bundle) { 387 assert Thread.holdsLock(mLock); 388 389 if (DEBUG) { 390 Log.i(TAG, "Linker.useSharedRelrosLocked() called"); 391 } 392 393 if (bundle == null) { 394 if (DEBUG) { 395 Log.i(TAG, "null bundle!"); 396 } 397 return; 398 } 399 400 if (mLoadedLibraries == null) { 401 if (DEBUG) { 402 Log.i(TAG, "No libraries loaded!"); 403 } 404 return; 405 } 406 407 if (DEBUG) { 408 dumpBundle(bundle); 409 } 410 HashMap<String, LibInfo> relroMap = createLibInfoMapFromBundle(bundle); 411 412 // Apply the RELRO section to all libraries that were already loaded. 413 for (Map.Entry<String, LibInfo> entry : relroMap.entrySet()) { 414 String libName = entry.getKey(); 415 LibInfo libInfo = entry.getValue(); 416 if (!nativeUseSharedRelro(libName, libInfo)) { 417 Log.w(TAG, "Could not use shared RELRO section for " + libName); 418 } else { 419 if (DEBUG) { 420 Log.i(TAG, "Using shared RELRO section for " + libName); 421 } 422 } 423 } 424 425 // In service processes, close all file descriptors from the map now. 426 if (!mInBrowserProcess) { 427 closeLibInfoMap(relroMap); 428 } 429 430 if (DEBUG) { 431 Log.i(TAG, "Linker.useSharedRelrosLocked() exiting"); 432 } 433 } 434 435 /** 436 * Implements loading a native shared library with the Chromium linker. 437 * 438 * Load a native shared library with the Chromium linker. If the zip file 439 * is not null, the shared library must be uncompressed and page aligned 440 * inside the zipfile. Note the crazy linker treats libraries and files as 441 * equivalent, so you can only open one library in a given zip file. The 442 * library must not be the Chromium linker library. 443 * 444 * @param zipFilePath The path of the zip file containing the library (or null). 445 * @param libFilePath The path of the library (possibly in the zip file). 446 * @param isFixedAddressPermitted If true, uses a fixed load address if one was 447 * supplied, otherwise ignores the fixed address and loads wherever available. 448 */ 449 @Override loadLibraryImpl(@ullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted)450 void loadLibraryImpl(@Nullable String zipFilePath, 451 String libFilePath, 452 boolean isFixedAddressPermitted) { 453 if (DEBUG) { 454 Log.i(TAG, "loadLibraryImpl: " 455 + zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted); 456 } 457 synchronized (mLock) { 458 ensureInitializedLocked(); 459 460 // Security: Ensure prepareLibraryLoad() was called before. 461 // In theory, this can be done lazily here, but it's more consistent 462 // to use a pair of functions (i.e. prepareLibraryLoad() + finishLibraryLoad()) 463 // that wrap all calls to loadLibrary() in the library loader. 464 assert mPrepareLibraryLoadCalled; 465 466 if (mLoadedLibraries == null) { 467 mLoadedLibraries = new HashMap<String, LibInfo>(); 468 } 469 470 if (mLoadedLibraries.containsKey(libFilePath)) { 471 if (DEBUG) { 472 Log.i(TAG, "Not loading " + libFilePath + " twice"); 473 } 474 return; 475 } 476 477 LibInfo libInfo = new LibInfo(); 478 long loadAddress = 0; 479 if (isFixedAddressPermitted) { 480 if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) { 481 // Load the library at a fixed address. 482 loadAddress = mCurrentLoadAddress; 483 484 // For multiple libraries, ensure we stay within reservation range. 485 if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) { 486 String errorMessage = 487 "Load address outside reservation, for: " + libFilePath; 488 Log.e(TAG, errorMessage); 489 throw new UnsatisfiedLinkError(errorMessage); 490 } 491 } 492 } 493 494 String sharedRelRoName = libFilePath; 495 if (zipFilePath != null) { 496 if (!nativeLoadLibraryInZipFile(zipFilePath, libFilePath, loadAddress, libInfo)) { 497 String errorMessage = "Unable to load library: " + libFilePath 498 + ", in: " + zipFilePath; 499 Log.e(TAG, errorMessage); 500 throw new UnsatisfiedLinkError(errorMessage); 501 } 502 sharedRelRoName = zipFilePath; 503 } else { 504 if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) { 505 String errorMessage = "Unable to load library: " + libFilePath; 506 Log.e(TAG, errorMessage); 507 throw new UnsatisfiedLinkError(errorMessage); 508 } 509 } 510 511 // Print the load address to the logcat when testing the linker. The format 512 // of the string is expected by the Python test_runner script as one of: 513 // BROWSER_LIBRARY_ADDRESS: <library-name> <address> 514 // RENDERER_LIBRARY_ADDRESS: <library-name> <address> 515 // Where <library-name> is the library name, and <address> is the hexadecimal load 516 // address. 517 if (NativeLibraries.sEnableLinkerTests) { 518 String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" 519 : "RENDERER_LIBRARY_ADDRESS"; 520 Log.i(TAG, String.format( 521 Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress)); 522 } 523 524 if (mInBrowserProcess) { 525 // Create a new shared RELRO section at the 'current' fixed load address. 526 if (!nativeCreateSharedRelro(sharedRelRoName, mCurrentLoadAddress, libInfo)) { 527 Log.w(TAG, String.format( 528 Locale.US, "Could not create shared RELRO for %s at %x", 529 libFilePath, 530 mCurrentLoadAddress)); 531 } else { 532 if (DEBUG) { 533 Log.i(TAG, String.format( 534 Locale.US, 535 "Created shared RELRO for %s at %x: %s", 536 sharedRelRoName, 537 mCurrentLoadAddress, 538 libInfo.toString())); 539 } 540 } 541 } 542 543 if (loadAddress != 0 && mCurrentLoadAddress != 0) { 544 // Compute the next current load address. If mCurrentLoadAddress 545 // is not 0, this is an explicit library load address. Otherwise, 546 // this is an explicit load address for relocated RELRO sections 547 // only. 548 mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize 549 + BREAKPAD_GUARD_REGION_BYTES; 550 } 551 552 mLoadedLibraries.put(sharedRelRoName, libInfo); 553 if (DEBUG) { 554 Log.i(TAG, "Library details " + libInfo.toString()); 555 } 556 } 557 } 558 559 /** 560 * Move activity from the native thread to the main UI thread. 561 * Called from native code on its own thread. Posts a callback from 562 * the UI thread back to native code. 563 * 564 * @param opaque Opaque argument. 565 */ 566 @CalledByNative postCallbackOnMainThread(final long opaque)567 public static void postCallbackOnMainThread(final long opaque) { 568 ThreadUtils.postOnUiThread(new Runnable() { 569 @Override 570 public void run() { 571 nativeRunCallbackOnUiThread(opaque); 572 } 573 }); 574 } 575 576 /** 577 * Native method to run callbacks on the main UI thread. 578 * Supplied by the crazy linker and called by postCallbackOnMainThread. 579 * 580 * @param opaque Opaque crazy linker arguments. 581 */ nativeRunCallbackOnUiThread(long opaque)582 private static native void nativeRunCallbackOnUiThread(long opaque); 583 584 /** 585 * Native method used to load a library. 586 * 587 * @param library Platform specific library name (e.g. libfoo.so) 588 * @param loadAddress Explicit load address, or 0 for randomized one. 589 * @param libInfo If not null, the mLoadAddress and mLoadSize fields 590 * of this LibInfo instance will set on success. 591 * @return true for success, false otherwise. 592 */ nativeLoadLibrary(String library, long loadAddress, LibInfo libInfo)593 private static native boolean nativeLoadLibrary(String library, 594 long loadAddress, 595 LibInfo libInfo); 596 597 /** 598 * Native method used to load a library which is inside a zipfile. 599 * 600 * @param zipfileName Filename of the zip file containing the library. 601 * @param library Platform specific library name (e.g. libfoo.so) 602 * @param loadAddress Explicit load address, or 0 for randomized one. 603 * @param libInfo If not null, the mLoadAddress and mLoadSize fields 604 * of this LibInfo instance will set on success. 605 * @return true for success, false otherwise. 606 */ nativeLoadLibraryInZipFile(@ullable String zipfileName, String libraryName, long loadAddress, LibInfo libInfo)607 private static native boolean nativeLoadLibraryInZipFile(@Nullable String zipfileName, 608 String libraryName, 609 long loadAddress, 610 LibInfo libInfo); 611 612 /** 613 * Native method used to create a shared RELRO section. 614 * If the library was already loaded at the same address using 615 * nativeLoadLibrary(), this creates the RELRO for it. Otherwise, 616 * this loads a new temporary library at the specified address, 617 * creates and extracts the RELRO section from it, then unloads it. 618 * 619 * @param library Library name. 620 * @param loadAddress load address, which can be different from the one 621 * used to load the library in the current process! 622 * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize 623 * and mRelroFd will be set. 624 * @return true on success, false otherwise. 625 */ nativeCreateSharedRelro(String library, long loadAddress, LibInfo libInfo)626 private static native boolean nativeCreateSharedRelro(String library, 627 long loadAddress, 628 LibInfo libInfo); 629 630 /** 631 * Native method used to use a shared RELRO section. 632 * 633 * @param library Library name. 634 * @param libInfo A LibInfo instance containing valid RELRO information 635 * @return true on success. 636 */ nativeUseSharedRelro(String library, LibInfo libInfo)637 private static native boolean nativeUseSharedRelro(String library, 638 LibInfo libInfo); 639 } 640