1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.webkit; 18 19 import android.app.ActivityManagerInternal; 20 import android.app.Application; 21 import android.app.AppGlobals; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.os.Build; 27 import android.os.Process; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.StrictMode; 31 import android.os.SystemProperties; 32 import android.os.Trace; 33 import android.text.TextUtils; 34 import android.util.AndroidRuntimeException; 35 import android.util.Log; 36 import com.android.server.LocalServices; 37 import dalvik.system.VMRuntime; 38 39 import java.io.File; 40 import java.util.Arrays; 41 42 import com.android.internal.os.Zygote; 43 44 /** 45 * Top level factory, used creating all the main WebView implementation classes. 46 * 47 * @hide 48 */ 49 public final class WebViewFactory { 50 51 private static final String CHROMIUM_WEBVIEW_FACTORY = 52 "com.android.webview.chromium.WebViewChromiumFactoryProvider"; 53 54 private static final String NULL_WEBVIEW_FACTORY = 55 "com.android.webview.nullwebview.NullWebViewFactoryProvider"; 56 57 private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 = 58 "/data/misc/shared_relro/libwebviewchromium32.relro"; 59 private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 = 60 "/data/misc/shared_relro/libwebviewchromium64.relro"; 61 62 public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY = 63 "persist.sys.webview.vmsize"; 64 private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024; 65 66 private static final String LOGTAG = "WebViewFactory"; 67 68 private static final boolean DEBUG = false; 69 70 // Cache the factory both for efficiency, and ensure any one process gets all webviews from the 71 // same provider. 72 private static WebViewFactoryProvider sProviderInstance; 73 private static final Object sProviderLock = new Object(); 74 private static boolean sAddressSpaceReserved = false; 75 private static PackageInfo sPackageInfo; 76 getWebViewPackageName()77 public static String getWebViewPackageName() { 78 return AppGlobals.getInitialApplication().getString( 79 com.android.internal.R.string.config_webViewPackageName); 80 } 81 getLoadedPackageInfo()82 public static PackageInfo getLoadedPackageInfo() { 83 return sPackageInfo; 84 } 85 getProvider()86 static WebViewFactoryProvider getProvider() { 87 synchronized (sProviderLock) { 88 // For now the main purpose of this function (and the factory abstraction) is to keep 89 // us honest and minimize usage of WebView internals when binding the proxy. 90 if (sProviderInstance != null) return sProviderInstance; 91 92 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); 93 try { 94 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); 95 loadNativeLibrary(); 96 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 97 98 Class<WebViewFactoryProvider> providerClass; 99 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getFactoryClass()"); 100 try { 101 providerClass = getFactoryClass(); 102 } catch (ClassNotFoundException e) { 103 Log.e(LOGTAG, "error loading provider", e); 104 throw new AndroidRuntimeException(e); 105 } finally { 106 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 107 } 108 109 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); 110 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()"); 111 try { 112 sProviderInstance = providerClass.newInstance(); 113 if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance); 114 return sProviderInstance; 115 } catch (Exception e) { 116 Log.e(LOGTAG, "error instantiating provider", e); 117 throw new AndroidRuntimeException(e); 118 } finally { 119 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 120 StrictMode.setThreadPolicy(oldPolicy); 121 } 122 } finally { 123 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 124 } 125 } 126 } 127 getFactoryClass()128 private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException { 129 Application initialApplication = AppGlobals.getInitialApplication(); 130 try { 131 // First fetch the package info so we can log the webview package version. 132 String packageName = getWebViewPackageName(); 133 sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0); 134 Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName + 135 " (code " + sPackageInfo.versionCode + ")"); 136 137 // Construct a package context to load the Java code into the current app. 138 Context webViewContext = initialApplication.createPackageContext(packageName, 139 Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); 140 initialApplication.getAssets().addAssetPath( 141 webViewContext.getApplicationInfo().sourceDir); 142 ClassLoader clazzLoader = webViewContext.getClassLoader(); 143 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()"); 144 try { 145 return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true, 146 clazzLoader); 147 } finally { 148 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 149 } 150 } catch (PackageManager.NameNotFoundException e) { 151 // If the package doesn't exist, then try loading the null WebView instead. 152 // If that succeeds, then this is a device without WebView support; if it fails then 153 // swallow the failure, complain that the real WebView is missing and rethrow the 154 // original exception. 155 try { 156 return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY); 157 } catch (ClassNotFoundException e2) { 158 // Ignore. 159 } 160 Log.e(LOGTAG, "Chromium WebView package does not exist", e); 161 throw new AndroidRuntimeException(e); 162 } 163 } 164 165 /** 166 * Perform any WebView loading preparations that must happen in the zygote. 167 * Currently, this means allocating address space to load the real JNI library later. 168 */ prepareWebViewInZygote()169 public static void prepareWebViewInZygote() { 170 try { 171 System.loadLibrary("webviewchromium_loader"); 172 long addressSpaceToReserve = 173 SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY, 174 CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); 175 sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve); 176 177 if (sAddressSpaceReserved) { 178 if (DEBUG) { 179 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes"); 180 } 181 } else { 182 Log.e(LOGTAG, "reserving " + addressSpaceToReserve + 183 " bytes of address space failed"); 184 } 185 } catch (Throwable t) { 186 // Log and discard errors at this stage as we must not crash the zygote. 187 Log.e(LOGTAG, "error preparing native loader", t); 188 } 189 } 190 191 /** 192 * Perform any WebView loading preparations that must happen at boot from the system server, 193 * after the package manager has started or after an update to the webview is installed. 194 * This must be called in the system server. 195 * Currently, this means spawning the child processes which will create the relro files. 196 */ prepareWebViewInSystemServer()197 public static void prepareWebViewInSystemServer() { 198 String[] nativePaths = null; 199 try { 200 nativePaths = getWebViewNativeLibraryPaths(); 201 } catch (Throwable t) { 202 // Log and discard errors at this stage as we must not crash the system server. 203 Log.e(LOGTAG, "error preparing webview native library", t); 204 } 205 prepareWebViewInSystemServer(nativePaths); 206 } 207 prepareWebViewInSystemServer(String[] nativeLibraryPaths)208 private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) { 209 if (DEBUG) Log.v(LOGTAG, "creating relro files"); 210 211 // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any 212 // unexpected values will be handled there to ensure that we trigger notifying any process 213 // waiting on relreo creation. 214 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { 215 if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); 216 createRelroFile(false /* is64Bit */, nativeLibraryPaths); 217 } 218 219 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { 220 if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); 221 createRelroFile(true /* is64Bit */, nativeLibraryPaths); 222 } 223 } 224 onWebViewUpdateInstalled()225 public static void onWebViewUpdateInstalled() { 226 String[] nativeLibs = null; 227 try { 228 nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths(); 229 if (nativeLibs != null) { 230 long newVmSize = 0L; 231 232 for (String path : nativeLibs) { 233 if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path); 234 if (path == null) continue; 235 File f = new File(path); 236 if (f.exists()) { 237 long length = f.length(); 238 if (length > newVmSize) { 239 newVmSize = length; 240 } 241 } 242 } 243 244 if (DEBUG) { 245 Log.v(LOGTAG, "Based on library size, need " + newVmSize + 246 " bytes of address space."); 247 } 248 // The required memory can be larger than the file on disk (due to .bss), and an 249 // upgraded version of the library will likely be larger, so always attempt to 250 // reserve twice as much as we think to allow for the library to grow during this 251 // boot cycle. 252 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); 253 Log.d(LOGTAG, "Setting new address space to " + newVmSize); 254 SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY, 255 Long.toString(newVmSize)); 256 } 257 } catch (Throwable t) { 258 // Log and discard errors at this stage as we must not crash the system server. 259 Log.e(LOGTAG, "error preparing webview native library", t); 260 } 261 prepareWebViewInSystemServer(nativeLibs); 262 } 263 getWebViewNativeLibraryPaths()264 private static String[] getWebViewNativeLibraryPaths() 265 throws PackageManager.NameNotFoundException { 266 final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so"; 267 268 PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); 269 ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0); 270 271 String path32; 272 String path64; 273 boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi); 274 if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { 275 // Multi-arch case. 276 if (primaryArchIs64bit) { 277 // Primary arch: 64-bit, secondary: 32-bit. 278 path64 = ai.nativeLibraryDir; 279 path32 = ai.secondaryNativeLibraryDir; 280 } else { 281 // Primary arch: 32-bit, secondary: 64-bit. 282 path64 = ai.secondaryNativeLibraryDir; 283 path32 = ai.nativeLibraryDir; 284 } 285 } else if (primaryArchIs64bit) { 286 // Single-arch 64-bit. 287 path64 = ai.nativeLibraryDir; 288 path32 = ""; 289 } else { 290 // Single-arch 32-bit. 291 path32 = ai.nativeLibraryDir; 292 path64 = ""; 293 } 294 if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME; 295 if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME; 296 return new String[] { path32, path64 }; 297 } 298 createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths)299 private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) { 300 final String abi = 301 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]; 302 303 // crashHandler is invoked by the ActivityManagerService when the isolated process crashes. 304 Runnable crashHandler = new Runnable() { 305 @Override 306 public void run() { 307 try { 308 Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without"); 309 getUpdateService().notifyRelroCreationCompleted(is64Bit, false); 310 } catch (RemoteException e) { 311 Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage()); 312 } 313 } 314 }; 315 316 try { 317 if (nativeLibraryPaths == null 318 || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) { 319 throw new IllegalArgumentException( 320 "Native library paths to the WebView RelRo process must not be null!"); 321 } 322 int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess( 323 RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi, 324 Process.SHARED_RELRO_UID, crashHandler); 325 if (pid <= 0) throw new Exception("Failed to start the relro file creator process"); 326 } catch (Throwable t) { 327 // Log and discard errors as we must not crash the system server. 328 Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t); 329 crashHandler.run(); 330 } 331 } 332 333 private static class RelroFileCreator { 334 // Called in an unprivileged child process to create the relro file. main(String[] args)335 public static void main(String[] args) { 336 boolean result = false; 337 boolean is64Bit = VMRuntime.getRuntime().is64Bit(); 338 try{ 339 if (args.length != 2 || args[0] == null || args[1] == null) { 340 Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args)); 341 return; 342 } 343 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " + 344 " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]); 345 if (!sAddressSpaceReserved) { 346 Log.e(LOGTAG, "can't create relro file; address space not reserved"); 347 return; 348 } 349 result = nativeCreateRelroFile(args[0] /* path32 */, 350 args[1] /* path64 */, 351 CHROMIUM_WEBVIEW_NATIVE_RELRO_32, 352 CHROMIUM_WEBVIEW_NATIVE_RELRO_64); 353 if (result && DEBUG) Log.v(LOGTAG, "created relro file"); 354 } finally { 355 // We must do our best to always notify the update service, even if something fails. 356 try { 357 getUpdateService().notifyRelroCreationCompleted(is64Bit, result); 358 } catch (RemoteException e) { 359 Log.e(LOGTAG, "error notifying update service", e); 360 } 361 362 if (!result) Log.e(LOGTAG, "failed to create relro file"); 363 364 // Must explicitly exit or else this process will just sit around after we return. 365 System.exit(0); 366 } 367 } 368 } 369 loadNativeLibrary()370 private static void loadNativeLibrary() { 371 if (!sAddressSpaceReserved) { 372 Log.e(LOGTAG, "can't load with relro file; address space not reserved"); 373 return; 374 } 375 376 try { 377 getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit()); 378 } catch (RemoteException e) { 379 Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e); 380 return; 381 } 382 383 try { 384 String[] args = getWebViewNativeLibraryPaths(); 385 boolean result = nativeLoadWithRelroFile(args[0] /* path32 */, 386 args[1] /* path64 */, 387 CHROMIUM_WEBVIEW_NATIVE_RELRO_32, 388 CHROMIUM_WEBVIEW_NATIVE_RELRO_64); 389 if (!result) { 390 Log.w(LOGTAG, "failed to load with relro file, proceeding without"); 391 } else if (DEBUG) { 392 Log.v(LOGTAG, "loaded with relro file"); 393 } 394 } catch (PackageManager.NameNotFoundException e) { 395 Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e); 396 } 397 } 398 getUpdateService()399 private static IWebViewUpdateService getUpdateService() { 400 return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); 401 } 402 nativeReserveAddressSpace(long addressSpaceToReserve)403 private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve); nativeCreateRelroFile(String lib32, String lib64, String relro32, String relro64)404 private static native boolean nativeCreateRelroFile(String lib32, String lib64, 405 String relro32, String relro64); nativeLoadWithRelroFile(String lib32, String lib64, String relro32, String relro64)406 private static native boolean nativeLoadWithRelroFile(String lib32, String lib64, 407 String relro32, String relro64); 408 } 409