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. 66 private ModernLinker() { } 67 static Linker create() { 68 return new ModernLinker(); 69 } 70 71 // Used internally to initialize the linker's data. Assumes lock is held. 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 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 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 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") 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 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 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 249 public void disableSharedRelros() { 250 if (DEBUG) { 251 Log.i(TAG, "disableSharedRelros() called"); 252 } 253 synchronized (mLock) { 254 // Mark this as a service process, and disable wait for shared RELRO. 255 mInBrowserProcess = false; 256 mWaitForSharedRelros = false; 257 } 258 } 259 260 /** 261 * Call this method before loading any libraries to indicate that this 262 * process is ready to reuse shared RELRO sections from another one. 263 * Typically used when starting service processes. 264 * 265 * @param baseLoadAddress the base library load address to use. 266 */ 267 @Override 268 public void initServiceProcess(long baseLoadAddress) { 269 if (DEBUG) { 270 Log.i(TAG, String.format( 271 Locale.US, "initServiceProcess(0x%x) called", 272 baseLoadAddress)); 273 } 274 synchronized (mLock) { 275 assert !mPrepareLibraryLoadCalled; 276 277 // Mark this as a service process, and flag wait for shared RELRO. 278 // Save the base load address passed in. 279 mInBrowserProcess = false; 280 mWaitForSharedRelros = true; 281 mBaseLoadAddress = baseLoadAddress; 282 } 283 } 284 285 /** 286 * Retrieve the base load address for libraries that share RELROs. 287 * 288 * @return a common, random base load address, or 0 if RELRO sharing is 289 * disabled. 290 */ 291 @Override 292 public long getBaseLoadAddress() { 293 synchronized (mLock) { 294 ensureInitializedLocked(); 295 setupBaseLoadAddressLocked(); 296 if (DEBUG) { 297 Log.i(TAG, String.format( 298 Locale.US, "getBaseLoadAddress() returns 0x%x", 299 mBaseLoadAddress)); 300 } 301 return mBaseLoadAddress; 302 } 303 } 304 305 // Used internally to lazily setup the common random base load address. 306 private void setupBaseLoadAddressLocked() { 307 assert Thread.holdsLock(mLock); 308 309 // No-op if the base load address is already set up. 310 if (mBaseLoadAddress == -1) { 311 mBaseLoadAddress = getRandomBaseLoadAddress(); 312 } 313 if (mBaseLoadAddress == 0) { 314 // If the random address is 0 there are issues with finding enough 315 // free address space, so disable RELRO shared / fixed load addresses. 316 Log.w(TAG, "Disabling shared RELROs due address space pressure"); 317 mWaitForSharedRelros = false; 318 } 319 } 320 321 /** 322 * Load a native shared library with the Chromium linker. If the zip file 323 * is not null, the shared library must be uncompressed and page aligned 324 * inside the zipfile. The library must not be the Chromium linker library. 325 * 326 * If asked to wait for shared RELROs, this function will block library loads 327 * until the shared RELROs bundle is received by useSharedRelros(). 328 * 329 * @param zipFilePath The path of the zip file containing the library (or null). 330 * @param libFilePath The path of the library (possibly in the zip file). 331 * @param isFixedAddressPermitted If true, uses a fixed load address if one was 332 * supplied, otherwise ignores the fixed address and loads wherever available. 333 */ 334 @Override 335 void loadLibraryImpl(@Nullable String zipFilePath, 336 String libFilePath, 337 boolean isFixedAddressPermitted) { 338 if (DEBUG) { 339 Log.i(TAG, "loadLibraryImpl: " 340 + zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted); 341 } 342 343 synchronized (mLock) { 344 assert mPrepareLibraryLoadCalled; 345 346 String dlopenExtPath; 347 if (zipFilePath != null) { 348 // The android_dlopen_ext() function understands strings with the format 349 // <zip_path>!/<file_path> to represent the file_path element within the zip 350 // file at zip_path. This enables directly loading from APK. We add the 351 // "crazy." prefix to the path in the zip file to prevent the Android package 352 // manager from seeing this as a library and so extracting it from the APK. 353 String cpuAbi = nativeGetCpuAbi(); 354 dlopenExtPath = zipFilePath + "!/lib/" + cpuAbi + "/crazy." + libFilePath; 355 } else { 356 // Not loading from APK directly, so simply pass the library name to 357 // android_dlopen_ext(). 358 dlopenExtPath = libFilePath; 359 } 360 361 if (mLoadedLibraries.containsKey(dlopenExtPath)) { 362 if (DEBUG) { 363 Log.i(TAG, "Not loading " + libFilePath + " twice"); 364 } 365 return; 366 } 367 368 // If not in the browser, shared RELROs are not disabled, and fixed addresses 369 // have not been disallowed, load the library at a fixed address. Otherwise, 370 // load anywhere. 371 long loadAddress = 0; 372 if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) { 373 loadAddress = mCurrentLoadAddress; 374 375 // For multiple libraries, ensure we stay within reservation range. 376 if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) { 377 String errorMessage = "Load address outside reservation, for: " + libFilePath; 378 Log.e(TAG, errorMessage); 379 throw new UnsatisfiedLinkError(errorMessage); 380 } 381 } 382 383 LibInfo libInfo = new LibInfo(); 384 385 if (mInBrowserProcess && mCurrentLoadAddress != 0) { 386 // We are in the browser, and with a current load address that indicates that 387 // there is enough address space for shared RELRO to operate. Create the 388 // shared RELRO, and store it in the map. 389 String relroPath = PathUtils.getDataDirectory() + "/RELRO:" + libFilePath; 390 if (nativeCreateSharedRelro(dlopenExtPath, 391 mCurrentLoadAddress, relroPath, libInfo)) { 392 mSharedRelros.put(dlopenExtPath, libInfo); 393 } else { 394 String errorMessage = "Unable to create shared relro: " + relroPath; 395 Log.w(TAG, errorMessage); 396 } 397 } else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) { 398 // We are in a service process, again with a current load address that is 399 // suitable for shared RELRO, and we are to wait for shared RELROs. So 400 // do that, then use the map we receive to provide libinfo for library load. 401 waitForSharedRelrosLocked(); 402 if (mSharedRelros.containsKey(dlopenExtPath)) { 403 libInfo = mSharedRelros.get(dlopenExtPath); 404 } 405 } 406 407 // Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary() 408 // will load without shared RELRO. Otherwise, it uses shared RELRO if the attached 409 // libInfo is usable. 410 if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) { 411 String errorMessage = "Unable to load library: " + dlopenExtPath; 412 Log.e(TAG, errorMessage); 413 throw new UnsatisfiedLinkError(errorMessage); 414 } 415 416 // Print the load address to the logcat when testing the linker. The format 417 // of the string is expected by the Python test_runner script as one of: 418 // BROWSER_LIBRARY_ADDRESS: <library-name> <address> 419 // RENDERER_LIBRARY_ADDRESS: <library-name> <address> 420 // Where <library-name> is the library name, and <address> is the hexadecimal load 421 // address. 422 if (NativeLibraries.sEnableLinkerTests) { 423 String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" 424 : "RENDERER_LIBRARY_ADDRESS"; 425 Log.i(TAG, String.format( 426 Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress)); 427 } 428 429 if (loadAddress != 0 && mCurrentLoadAddress != 0) { 430 // Compute the next current load address. If mCurrentLoadAddress 431 // is not 0, this is an explicit library load address. 432 mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize 433 + BREAKPAD_GUARD_REGION_BYTES; 434 } 435 436 mLoadedLibraries.put(dlopenExtPath, libInfo); 437 if (DEBUG) { 438 Log.i(TAG, "Library details " + libInfo.toString()); 439 } 440 } 441 } 442 443 /** 444 * Native method to return the CPU ABI. 445 * Obtaining it from the linker's native code means that we always correctly 446 * match the loaded library's ABI to the linker's ABI. 447 * 448 * @return CPU ABI string. 449 */ 450 private static native String nativeGetCpuAbi(); 451 452 /** 453 * Native method used to load a library. 454 * 455 * @param dlopenExtPath For load from APK, the path to the enclosing 456 * zipfile concatenated with "!/" and the path to the library within the zipfile; 457 * otherwise the platform specific library name (e.g. libfoo.so). 458 * @param loadAddress Explicit load address, or 0 for randomized one. 459 * @param libInfo If not null, the mLoadAddress and mLoadSize fields 460 * of this LibInfo instance will set on success. 461 * @return true for success, false otherwise. 462 */ 463 private static native boolean nativeLoadLibrary(String dlopenExtPath, 464 long loadAddress, 465 LibInfo libInfo); 466 467 /** 468 * Native method used to create a shared RELRO section. 469 * Creates a shared RELRO file for the given library. Done by loading a 470 * a new temporary library at the specified address, saving the RELRO section 471 * from it, then unloading. 472 * 473 * @param dlopenExtPath For load from APK, the path to the enclosing 474 * zipfile concatenated with "!/" and the path to the library within the zipfile; 475 * otherwise the platform specific library name (e.g. libfoo.so). 476 * @param loadAddress load address, which can be different from the one 477 * used to load the library in the current process! 478 * @param relroPath Path to the shared RELRO file for this library. 479 * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize 480 * and mRelroFd will be set. 481 * @return true on success, false otherwise. 482 */ 483 private static native boolean nativeCreateSharedRelro(String dlopenExtPath, 484 long loadAddress, 485 String relroPath, 486 LibInfo libInfo); 487 } 488