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.SystemClock; 9 10 import org.chromium.base.Log; 11 import org.chromium.base.PathUtils; 12 import org.chromium.base.annotations.SuppressFBWarnings; 13 14 import java.util.HashMap; 15 import java.util.Locale; 16 17 import javax.annotation.Nullable; 18 19 /* 20 * For more, see Technical note, Security considerations, and the explanation 21 * of how this class is supposed to be used in Linker.java. 22 */ 23 24 /** 25 * Provides a concrete implementation of the Chromium Linker. 26 * 27 * This Linker implementation uses the Android M and later system linker to map and then 28 * run Chrome for Android. 29 * 30 * For more on the operations performed by the Linker, see {@link Linker}. 31 */ 32 class ModernLinker extends Linker { 33 // Log tag for this class. 34 private static final String TAG = "LibraryLoader"; 35 36 // Becomes true after linker initialization. 37 private boolean mInitialized = false; 38 39 // Becomes true to indicate this process needs to wait for a shared RELRO in LibraryLoad(). 40 private boolean mWaitForSharedRelros = false; 41 42 // The map of all RELRO sections either created or used in this process. 43 private HashMap<String, LibInfo> mSharedRelros = null; 44 45 // Cached Bundle representation of the RELRO sections map for transfer across processes. 46 private Bundle mSharedRelrosBundle = null; 47 48 // Set to true if this runs in the browser process. Disabled by initServiceProcess(). 49 private boolean mInBrowserProcess = true; 50 51 // Current common random base load address. A value of -1 indicates not yet initialized. 52 private long mBaseLoadAddress = -1; 53 54 // Current fixed-location load address for the next library called by loadLibrary(). 55 // Initialized to mBaseLoadAddress in prepareLibraryLoad(), and then adjusted as each 56 // library is loaded by loadLibrary(). 57 private long mCurrentLoadAddress = -1; 58 59 // Becomes true once prepareLibraryLoad() has been called. 60 private boolean mPrepareLibraryLoadCalled = false; 61 62 // The map of libraries that are currently loaded in this process. 63 private HashMap<String, LibInfo> mLoadedLibraries = null; 64 65 // Private singleton constructor, and singleton factory method. ModernLinker()66 private ModernLinker() { } create()67 static Linker create() { 68 return new ModernLinker(); 69 } 70 71 // Used internally to initialize the linker's data. Assumes lock is held. ensureInitializedLocked()72 private void ensureInitializedLocked() { 73 assert Thread.holdsLock(mLock); 74 assert NativeLibraries.sUseLinker; 75 76 // On first call, load libchromium_android_linker.so. Cannot be done in the 77 // constructor because the instance is constructed on the UI thread. 78 if (!mInitialized) { 79 loadLinkerJniLibrary(); 80 mInitialized = true; 81 } 82 } 83 84 /** 85 * Call this method to determine if the linker will try to use shared RELROs 86 * for the browser process. 87 */ 88 @Override isUsingBrowserSharedRelros()89 public boolean isUsingBrowserSharedRelros() { 90 // This Linker does not attempt to share RELROS between the browser and 91 // the renderers, but only between renderers. 92 return false; 93 } 94 95 /** 96 * Call this method just before loading any native shared libraries in this process. 97 * Loads the Linker's JNI library, and initializes the variables involved in the 98 * implementation of shared RELROs. 99 */ 100 @Override prepareLibraryLoad()101 public void prepareLibraryLoad() { 102 if (DEBUG) { 103 Log.i(TAG, "prepareLibraryLoad() called"); 104 } 105 assert NativeLibraries.sUseLinker; 106 107 synchronized (mLock) { 108 assert !mPrepareLibraryLoadCalled; 109 ensureInitializedLocked(); 110 111 // If in the browser, generate a random base load address for service processes 112 // and create an empty shared RELROs map. For service processes, the shared 113 // RELROs map must remain null until set by useSharedRelros(). 114 if (mInBrowserProcess) { 115 setupBaseLoadAddressLocked(); 116 mSharedRelros = new HashMap<String, LibInfo>(); 117 } 118 119 // Create an empty loaded libraries map. 120 mLoadedLibraries = new HashMap<String, LibInfo>(); 121 122 // Start the current load address at the base load address. 123 mCurrentLoadAddress = mBaseLoadAddress; 124 125 mPrepareLibraryLoadCalled = true; 126 } 127 } 128 129 /** 130 * Call this method just after loading all native shared libraries in this process. 131 * If not in the browser, closes the LibInfo entries used for RELRO sharing. 132 */ 133 @Override finishLibraryLoad()134 public void finishLibraryLoad() { 135 if (DEBUG) { 136 Log.i(TAG, "finishLibraryLoad() called"); 137 } 138 139 synchronized (mLock) { 140 assert mPrepareLibraryLoadCalled; 141 142 // Close shared RELRO file descriptors if not in the browser. 143 if (!mInBrowserProcess && mSharedRelros != null) { 144 closeLibInfoMap(mSharedRelros); 145 mSharedRelros = null; 146 } 147 148 // If testing, run tests now that all libraries are loaded and initialized. 149 if (NativeLibraries.sEnableLinkerTests) { 150 runTestRunnerClassForTesting(0, mInBrowserProcess); 151 } 152 } 153 } 154 155 // Used internally to wait for shared RELROs. Returns once useSharedRelros() has been 156 // called to supply a valid shared RELROs bundle. 157 @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE") waitForSharedRelrosLocked()158 private void waitForSharedRelrosLocked() { 159 if (DEBUG) { 160 Log.i(TAG, "waitForSharedRelros called"); 161 } 162 assert Thread.holdsLock(mLock); 163 164 // Return immediately if shared RELROs are already available. 165 if (mSharedRelros != null) { 166 return; 167 } 168 169 // Wait until notified by useSharedRelros() that shared RELROs have arrived. 170 long startTime = DEBUG ? SystemClock.uptimeMillis() : 0; 171 while (mSharedRelros == null) { 172 try { 173 mLock.wait(); 174 } catch (InterruptedException e) { 175 // Restore the thread's interrupt status. 176 Thread.currentThread().interrupt(); 177 } 178 } 179 180 if (DEBUG) { 181 Log.i(TAG, String.format( 182 Locale.US, "Time to wait for shared RELRO: %d ms", 183 SystemClock.uptimeMillis() - startTime)); 184 } 185 } 186 187 /** 188 * Call this to send a Bundle containing the shared RELRO sections to be 189 * used in this process. If initServiceProcess() was previously called, 190 * libraryLoad() will wait until this method is called in another 191 * thread with a non-null value. 192 * 193 * @param bundle The Bundle instance containing a map of shared RELRO sections 194 * to use in this process. 195 */ 196 @Override useSharedRelros(Bundle bundle)197 public void useSharedRelros(Bundle bundle) { 198 if (DEBUG) { 199 Log.i(TAG, "useSharedRelros() called with " + bundle); 200 } 201 202 synchronized (mLock) { 203 mSharedRelros = createLibInfoMapFromBundle(bundle); 204 mLock.notifyAll(); 205 } 206 } 207 208 /** 209 * Call this to retrieve the shared RELRO sections created in this process, 210 * after loading all libraries. 211 * 212 * @return a new Bundle instance, or null if RELRO sharing is disabled on 213 * this system, or if initServiceProcess() was called previously. 214 */ 215 @Override getSharedRelros()216 public Bundle getSharedRelros() { 217 if (DEBUG) { 218 Log.i(TAG, "getSharedRelros() called"); 219 } 220 synchronized (mLock) { 221 if (!mInBrowserProcess) { 222 if (DEBUG) { 223 Log.i(TAG, "Not in browser, so returning null Bundle"); 224 } 225 return null; 226 } 227 228 // Create a new Bundle containing RELRO section information for all the shared 229 // RELROs created while loading libraries. 230 if (mSharedRelrosBundle == null && mSharedRelros != null) { 231 mSharedRelrosBundle = createBundleFromLibInfoMap(mSharedRelros); 232 if (DEBUG) { 233 Log.i(TAG, "Shared RELRO bundle created from map: " + mSharedRelrosBundle); 234 } 235 } 236 if (DEBUG) { 237 Log.i(TAG, "Returning " + mSharedRelrosBundle); 238 } 239 return mSharedRelrosBundle; 240 } 241 } 242 243 244 /** 245 * Call this method before loading any libraries to indicate that this 246 * process shall neither create or reuse shared RELRO sections. 247 */ 248 @Override disableSharedRelros()249 public void disableSharedRelros() { 250 if (DEBUG) { 251 Log.i(TAG, "disableSharedRelros() called"); 252 } 253 synchronized (mLock) { 254 assert !mPrepareLibraryLoadCalled; 255 256 // Mark this as a service process, and disable wait for shared RELRO. 257 mInBrowserProcess = false; 258 mWaitForSharedRelros = false; 259 } 260 } 261 262 /** 263 * Call this method before loading any libraries to indicate that this 264 * process is ready to reuse shared RELRO sections from another one. 265 * Typically used when starting service processes. 266 * 267 * @param baseLoadAddress the base library load address to use. 268 */ 269 @Override initServiceProcess(long baseLoadAddress)270 public void initServiceProcess(long baseLoadAddress) { 271 if (DEBUG) { 272 Log.i(TAG, String.format( 273 Locale.US, "initServiceProcess(0x%x) called", 274 baseLoadAddress)); 275 } 276 synchronized (mLock) { 277 assert !mPrepareLibraryLoadCalled; 278 279 // Mark this as a service process, and flag wait for shared RELRO. 280 // Save the base load address passed in. 281 mInBrowserProcess = false; 282 mWaitForSharedRelros = true; 283 mBaseLoadAddress = baseLoadAddress; 284 } 285 } 286 287 /** 288 * Retrieve the base load address for libraries that share RELROs. 289 * 290 * @return a common, random base load address, or 0 if RELRO sharing is 291 * disabled. 292 */ 293 @Override getBaseLoadAddress()294 public long getBaseLoadAddress() { 295 synchronized (mLock) { 296 ensureInitializedLocked(); 297 setupBaseLoadAddressLocked(); 298 if (DEBUG) { 299 Log.i(TAG, String.format( 300 Locale.US, "getBaseLoadAddress() returns 0x%x", 301 mBaseLoadAddress)); 302 } 303 return mBaseLoadAddress; 304 } 305 } 306 307 // Used internally to lazily setup the common random base load address. setupBaseLoadAddressLocked()308 private void setupBaseLoadAddressLocked() { 309 assert Thread.holdsLock(mLock); 310 311 // No-op if the base load address is already set up. 312 if (mBaseLoadAddress == -1) { 313 mBaseLoadAddress = getRandomBaseLoadAddress(); 314 } 315 if (mBaseLoadAddress == 0) { 316 // If the random address is 0 there are issues with finding enough 317 // free address space, so disable RELRO shared / fixed load addresses. 318 Log.w(TAG, "Disabling shared RELROs due address space pressure"); 319 mWaitForSharedRelros = false; 320 } 321 } 322 323 /** 324 * Load a native shared library with the Chromium linker. If the zip file 325 * is not null, the shared library must be uncompressed and page aligned 326 * inside the zipfile. The library must not be the Chromium linker library. 327 * 328 * If asked to wait for shared RELROs, this function will block library loads 329 * until the shared RELROs bundle is received by useSharedRelros(). 330 * 331 * @param zipFilePath The path of the zip file containing the library (or null). 332 * @param libFilePath The path of the library (possibly in the zip file). 333 * @param isFixedAddressPermitted If true, uses a fixed load address if one was 334 * supplied, otherwise ignores the fixed address and loads wherever available. 335 */ 336 @Override loadLibraryImpl(@ullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted)337 void loadLibraryImpl(@Nullable String zipFilePath, 338 String libFilePath, 339 boolean isFixedAddressPermitted) { 340 if (DEBUG) { 341 Log.i(TAG, "loadLibraryImpl: " 342 + zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted); 343 } 344 345 synchronized (mLock) { 346 assert mPrepareLibraryLoadCalled; 347 348 String dlopenExtPath; 349 if (zipFilePath != null) { 350 // The android_dlopen_ext() function understands strings with the format 351 // <zip_path>!/<file_path> to represent the file_path element within the zip 352 // file at zip_path. This enables directly loading from APK. We add the 353 // "crazy." prefix to the path in the zip file to prevent the Android package 354 // manager from seeing this as a library and so extracting it from the APK. 355 String cpuAbi = nativeGetCpuAbi(); 356 dlopenExtPath = zipFilePath + "!/lib/" + cpuAbi + "/crazy." + libFilePath; 357 } else { 358 // Not loading from APK directly, so simply pass the library name to 359 // android_dlopen_ext(). 360 dlopenExtPath = libFilePath; 361 } 362 363 if (mLoadedLibraries.containsKey(dlopenExtPath)) { 364 if (DEBUG) { 365 Log.i(TAG, "Not loading " + libFilePath + " twice"); 366 } 367 return; 368 } 369 370 // If not in the browser, shared RELROs are not disabled, and fixed addresses 371 // have not been disallowed, load the library at a fixed address. Otherwise, 372 // load anywhere. 373 long loadAddress = 0; 374 if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) { 375 loadAddress = mCurrentLoadAddress; 376 377 // For multiple libraries, ensure we stay within reservation range. 378 if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) { 379 String errorMessage = "Load address outside reservation, for: " + libFilePath; 380 Log.e(TAG, errorMessage); 381 throw new UnsatisfiedLinkError(errorMessage); 382 } 383 } 384 385 LibInfo libInfo = new LibInfo(); 386 387 if (mInBrowserProcess && mCurrentLoadAddress != 0) { 388 // We are in the browser, and with a current load address that indicates that 389 // there is enough address space for shared RELRO to operate. Create the 390 // shared RELRO, and store it in the map. 391 String relroPath = PathUtils.getDataDirectory(null) + "/RELRO:" + libFilePath; 392 if (nativeCreateSharedRelro(dlopenExtPath, 393 mCurrentLoadAddress, relroPath, libInfo)) { 394 mSharedRelros.put(dlopenExtPath, libInfo); 395 } else { 396 String errorMessage = "Unable to create shared relro: " + relroPath; 397 Log.w(TAG, errorMessage); 398 } 399 } else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) { 400 // We are in a service process, again with a current load address that is 401 // suitable for shared RELRO, and we are to wait for shared RELROs. So 402 // do that, then use the map we receive to provide libinfo for library load. 403 waitForSharedRelrosLocked(); 404 if (mSharedRelros.containsKey(dlopenExtPath)) { 405 libInfo = mSharedRelros.get(dlopenExtPath); 406 } 407 } 408 409 // Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary() 410 // will load without shared RELRO. Otherwise, it uses shared RELRO if the attached 411 // libInfo is usable. 412 if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) { 413 String errorMessage = "Unable to load library: " + dlopenExtPath; 414 Log.e(TAG, errorMessage); 415 throw new UnsatisfiedLinkError(errorMessage); 416 } 417 418 // Print the load address to the logcat when testing the linker. The format 419 // of the string is expected by the Python test_runner script as one of: 420 // BROWSER_LIBRARY_ADDRESS: <library-name> <address> 421 // RENDERER_LIBRARY_ADDRESS: <library-name> <address> 422 // Where <library-name> is the library name, and <address> is the hexadecimal load 423 // address. 424 if (NativeLibraries.sEnableLinkerTests) { 425 String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" 426 : "RENDERER_LIBRARY_ADDRESS"; 427 Log.i(TAG, String.format( 428 Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress)); 429 } 430 431 if (loadAddress != 0 && mCurrentLoadAddress != 0) { 432 // Compute the next current load address. If mCurrentLoadAddress 433 // is not 0, this is an explicit library load address. 434 mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize 435 + BREAKPAD_GUARD_REGION_BYTES; 436 } 437 438 mLoadedLibraries.put(dlopenExtPath, libInfo); 439 if (DEBUG) { 440 Log.i(TAG, "Library details " + libInfo.toString()); 441 } 442 } 443 } 444 445 /** 446 * Native method to return the CPU ABI. 447 * Obtaining it from the linker's native code means that we always correctly 448 * match the loaded library's ABI to the linker's ABI. 449 * 450 * @return CPU ABI string. 451 */ nativeGetCpuAbi()452 private static native String nativeGetCpuAbi(); 453 454 /** 455 * Native method used to load a library. 456 * 457 * @param dlopenExtPath For load from APK, the path to the enclosing 458 * zipfile concatenated with "!/" and the path to the library within the zipfile; 459 * otherwise the platform specific library name (e.g. libfoo.so). 460 * @param loadAddress Explicit load address, or 0 for randomized one. 461 * @param libInfo If not null, the mLoadAddress and mLoadSize fields 462 * of this LibInfo instance will set on success. 463 * @return true for success, false otherwise. 464 */ nativeLoadLibrary(String dlopenExtPath, long loadAddress, LibInfo libInfo)465 private static native boolean nativeLoadLibrary(String dlopenExtPath, 466 long loadAddress, 467 LibInfo libInfo); 468 469 /** 470 * Native method used to create a shared RELRO section. 471 * Creates a shared RELRO file for the given library. Done by loading a 472 * a new temporary library at the specified address, saving the RELRO section 473 * from it, then unloading. 474 * 475 * @param dlopenExtPath For load from APK, the path to the enclosing 476 * zipfile concatenated with "!/" and the path to the library within the zipfile; 477 * otherwise the platform specific library name (e.g. libfoo.so). 478 * @param loadAddress load address, which can be different from the one 479 * used to load the library in the current process! 480 * @param relroPath Path to the shared RELRO file for this library. 481 * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize 482 * and mRelroFd will be set. 483 * @return true on success, false otherwise. 484 */ nativeCreateSharedRelro(String dlopenExtPath, long loadAddress, String relroPath, LibInfo libInfo)485 private static native boolean nativeCreateSharedRelro(String dlopenExtPath, 486 long loadAddress, 487 String relroPath, 488 LibInfo libInfo); 489 } 490