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.annotation.SystemApi; 20 import android.app.ActivityManager; 21 import android.app.AppGlobals; 22 import android.app.Application; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.Signature; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.Trace; 31 import android.util.AndroidRuntimeException; 32 import android.util.ArraySet; 33 import android.util.Log; 34 35 import java.io.File; 36 import java.lang.reflect.Method; 37 38 /** 39 * Top level factory, used creating all the main WebView implementation classes. 40 * 41 * @hide 42 */ 43 @SystemApi 44 public final class WebViewFactory { 45 46 // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote. 47 /** @hide */ 48 private static final String CHROMIUM_WEBVIEW_FACTORY = 49 "com.android.webview.chromium.WebViewChromiumFactoryProviderForP"; 50 51 private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; 52 53 public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY = 54 "persist.sys.webview.vmsize"; 55 56 private static final String LOGTAG = "WebViewFactory"; 57 58 private static final boolean DEBUG = false; 59 60 // Cache the factory both for efficiency, and ensure any one process gets all webviews from the 61 // same provider. 62 private static WebViewFactoryProvider sProviderInstance; 63 private static final Object sProviderLock = new Object(); 64 private static PackageInfo sPackageInfo; 65 private static Boolean sWebViewSupported; 66 private static boolean sWebViewDisabled; 67 private static String sDataDirectorySuffix; // stored here so it can be set without loading WV 68 69 // Error codes for loadWebViewNativeLibraryFromPackage 70 public static final int LIBLOAD_SUCCESS = 0; 71 public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1; 72 public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2; 73 74 // error codes for waiting for WebView preparation 75 public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3; 76 public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; 77 78 // native relro loading error codes 79 public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5; 80 public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6; 81 public static final int LIBLOAD_FAILED_JNI_CALL = 7; 82 83 // more error codes for waiting for WebView preparation 84 public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8; 85 86 // error for namespace lookup 87 public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10; 88 89 getWebViewPreparationErrorReason(int error)90 private static String getWebViewPreparationErrorReason(int error) { 91 switch (error) { 92 case LIBLOAD_FAILED_WAITING_FOR_RELRO: 93 return "Time out waiting for Relro files being created"; 94 case LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES: 95 return "No WebView installed"; 96 case LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN: 97 return "Crashed for unknown reason"; 98 } 99 return "Unknown"; 100 } 101 102 static class MissingWebViewPackageException extends Exception { MissingWebViewPackageException(String message)103 public MissingWebViewPackageException(String message) { super(message); } MissingWebViewPackageException(Exception e)104 public MissingWebViewPackageException(Exception e) { super(e); } 105 } 106 isWebViewSupported()107 private static boolean isWebViewSupported() { 108 // No lock; this is a benign race as Boolean's state is final and the PackageManager call 109 // will always return the same value. 110 if (sWebViewSupported == null) { 111 sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager() 112 .hasSystemFeature(PackageManager.FEATURE_WEBVIEW); 113 } 114 return sWebViewSupported; 115 } 116 117 /** 118 * @hide 119 */ disableWebView()120 static void disableWebView() { 121 synchronized (sProviderLock) { 122 if (sProviderInstance != null) { 123 throw new IllegalStateException( 124 "Can't disable WebView: WebView already initialized"); 125 } 126 sWebViewDisabled = true; 127 } 128 } 129 130 /** 131 * @hide 132 */ setDataDirectorySuffix(String suffix)133 static void setDataDirectorySuffix(String suffix) { 134 synchronized (sProviderLock) { 135 if (sProviderInstance != null) { 136 throw new IllegalStateException( 137 "Can't set data directory suffix: WebView already initialized"); 138 } 139 if (suffix.indexOf(File.separatorChar) >= 0) { 140 throw new IllegalArgumentException("Suffix " + suffix 141 + " contains a path separator"); 142 } 143 sDataDirectorySuffix = suffix; 144 } 145 } 146 147 /** 148 * @hide 149 */ getDataDirectorySuffix()150 static String getDataDirectorySuffix() { 151 synchronized (sProviderLock) { 152 return sDataDirectorySuffix; 153 } 154 } 155 156 /** 157 * @hide 158 */ getWebViewLibrary(ApplicationInfo ai)159 public static String getWebViewLibrary(ApplicationInfo ai) { 160 if (ai.metaData != null) 161 return ai.metaData.getString("com.android.webview.WebViewLibrary"); 162 return null; 163 } 164 getLoadedPackageInfo()165 public static PackageInfo getLoadedPackageInfo() { 166 synchronized (sProviderLock) { 167 return sPackageInfo; 168 } 169 } 170 171 /** 172 * @hide 173 */ getWebViewProviderClass(ClassLoader clazzLoader)174 public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader) 175 throws ClassNotFoundException { 176 return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, 177 true, clazzLoader); 178 } 179 180 /** 181 * Load the native library for the given package name if that package 182 * name is the same as the one providing the webview. 183 */ loadWebViewNativeLibraryFromPackage(String packageName, ClassLoader clazzLoader)184 public static int loadWebViewNativeLibraryFromPackage(String packageName, 185 ClassLoader clazzLoader) { 186 if (!isWebViewSupported()) { 187 return LIBLOAD_WRONG_PACKAGE_NAME; 188 } 189 190 WebViewProviderResponse response = null; 191 try { 192 response = getUpdateService().waitForAndGetProvider(); 193 } catch (RemoteException e) { 194 Log.e(LOGTAG, "error waiting for relro creation", e); 195 return LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN; 196 } 197 198 199 if (response.status != LIBLOAD_SUCCESS 200 && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) { 201 return response.status; 202 } 203 if (!response.packageInfo.packageName.equals(packageName)) { 204 return LIBLOAD_WRONG_PACKAGE_NAME; 205 } 206 207 PackageManager packageManager = AppGlobals.getInitialApplication().getPackageManager(); 208 String libraryFileName; 209 try { 210 PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 211 PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING); 212 libraryFileName = getWebViewLibrary(packageInfo.applicationInfo); 213 } catch (PackageManager.NameNotFoundException e) { 214 Log.e(LOGTAG, "Couldn't find package " + packageName); 215 return LIBLOAD_WRONG_PACKAGE_NAME; 216 } 217 218 int loadNativeRet = WebViewLibraryLoader.loadNativeLibrary(clazzLoader, libraryFileName); 219 // If we failed waiting for relro we want to return that fact even if we successfully 220 // load the relro file. 221 if (loadNativeRet == LIBLOAD_SUCCESS) return response.status; 222 return loadNativeRet; 223 } 224 getProvider()225 static WebViewFactoryProvider getProvider() { 226 synchronized (sProviderLock) { 227 // For now the main purpose of this function (and the factory abstraction) is to keep 228 // us honest and minimize usage of WebView internals when binding the proxy. 229 if (sProviderInstance != null) return sProviderInstance; 230 231 final int uid = android.os.Process.myUid(); 232 if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID 233 || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID 234 || uid == android.os.Process.BLUETOOTH_UID) { 235 throw new UnsupportedOperationException( 236 "For security reasons, WebView is not allowed in privileged processes"); 237 } 238 239 if (!isWebViewSupported()) { 240 // Device doesn't support WebView; don't try to load it, just throw. 241 throw new UnsupportedOperationException(); 242 } 243 244 if (sWebViewDisabled) { 245 throw new IllegalStateException( 246 "WebView.disableWebView() was called: WebView is disabled"); 247 } 248 249 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); 250 try { 251 Class<WebViewFactoryProvider> providerClass = getProviderClass(); 252 Method staticFactory = null; 253 try { 254 staticFactory = providerClass.getMethod( 255 CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class); 256 } catch (Exception e) { 257 if (DEBUG) { 258 Log.w(LOGTAG, "error instantiating provider with static factory method", e); 259 } 260 } 261 262 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation"); 263 try { 264 sProviderInstance = (WebViewFactoryProvider) 265 staticFactory.invoke(null, new WebViewDelegate()); 266 if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance); 267 return sProviderInstance; 268 } catch (Exception e) { 269 Log.e(LOGTAG, "error instantiating provider", e); 270 throw new AndroidRuntimeException(e); 271 } finally { 272 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 273 } 274 } finally { 275 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 276 } 277 } 278 } 279 280 /** 281 * Returns {@code true} if the signatures match, {@code false} otherwise 282 */ signaturesEquals(Signature[] s1, Signature[] s2)283 private static boolean signaturesEquals(Signature[] s1, Signature[] s2) { 284 if (s1 == null) { 285 return s2 == null; 286 } 287 if (s2 == null) return false; 288 289 ArraySet<Signature> set1 = new ArraySet<>(); 290 for(Signature signature : s1) { 291 set1.add(signature); 292 } 293 ArraySet<Signature> set2 = new ArraySet<>(); 294 for(Signature signature : s2) { 295 set2.add(signature); 296 } 297 return set1.equals(set2); 298 } 299 300 // Throws MissingWebViewPackageException on failure verifyPackageInfo(PackageInfo chosen, PackageInfo toUse)301 private static void verifyPackageInfo(PackageInfo chosen, PackageInfo toUse) 302 throws MissingWebViewPackageException { 303 if (!chosen.packageName.equals(toUse.packageName)) { 304 throw new MissingWebViewPackageException("Failed to verify WebView provider, " 305 + "packageName mismatch, expected: " 306 + chosen.packageName + " actual: " + toUse.packageName); 307 } 308 if (chosen.getLongVersionCode() > toUse.getLongVersionCode()) { 309 throw new MissingWebViewPackageException("Failed to verify WebView provider, " 310 + "version code is lower than expected: " + chosen.getLongVersionCode() 311 + " actual: " + toUse.getLongVersionCode()); 312 } 313 if (getWebViewLibrary(toUse.applicationInfo) == null) { 314 throw new MissingWebViewPackageException("Tried to load an invalid WebView provider: " 315 + toUse.packageName); 316 } 317 if (!signaturesEquals(chosen.signatures, toUse.signatures)) { 318 throw new MissingWebViewPackageException("Failed to verify WebView provider, " 319 + "signature mismatch"); 320 } 321 } 322 323 /** 324 * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the 325 * required values from the donor package. If the ApplicationInfo is for a full WebView, 326 * leave it alone. Throws MissingWebViewPackageException if the donor is missing. 327 */ fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)328 private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) 329 throws MissingWebViewPackageException { 330 String donorPackageName = null; 331 if (ai.metaData != null) { 332 donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage"); 333 } 334 if (donorPackageName != null) { 335 PackageInfo donorPackage; 336 try { 337 donorPackage = pm.getPackageInfo( 338 donorPackageName, 339 PackageManager.GET_SHARED_LIBRARY_FILES 340 | PackageManager.MATCH_DEBUG_TRIAGED_MISSING 341 | PackageManager.MATCH_UNINSTALLED_PACKAGES 342 | PackageManager.MATCH_FACTORY_ONLY); 343 } catch (PackageManager.NameNotFoundException e) { 344 throw new MissingWebViewPackageException("Failed to find donor package: " + 345 donorPackageName); 346 } 347 ApplicationInfo donorInfo = donorPackage.applicationInfo; 348 349 // Replace the stub's code locations with the donor's. 350 ai.sourceDir = donorInfo.sourceDir; 351 ai.splitSourceDirs = donorInfo.splitSourceDirs; 352 ai.nativeLibraryDir = donorInfo.nativeLibraryDir; 353 ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir; 354 355 // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code 356 // and so they are unset. 357 ai.primaryCpuAbi = donorInfo.primaryCpuAbi; 358 ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi; 359 } 360 } 361 getWebViewContextAndSetProvider()362 private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException { 363 Application initialApplication = AppGlobals.getInitialApplication(); 364 try { 365 WebViewProviderResponse response = null; 366 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, 367 "WebViewUpdateService.waitForAndGetProvider()"); 368 try { 369 response = getUpdateService().waitForAndGetProvider(); 370 } finally { 371 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 372 } 373 if (response.status != LIBLOAD_SUCCESS 374 && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) { 375 throw new MissingWebViewPackageException("Failed to load WebView provider: " 376 + getWebViewPreparationErrorReason(response.status)); 377 } 378 // Register to be killed before fetching package info - so that we will be 379 // killed if the package info goes out-of-date. 380 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "ActivityManager.addPackageDependency()"); 381 try { 382 ActivityManager.getService().addPackageDependency( 383 response.packageInfo.packageName); 384 } finally { 385 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 386 } 387 // Fetch package info and verify it against the chosen package 388 PackageInfo newPackageInfo = null; 389 PackageManager pm = initialApplication.getPackageManager(); 390 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()"); 391 try { 392 newPackageInfo = pm.getPackageInfo( 393 response.packageInfo.packageName, 394 PackageManager.GET_SHARED_LIBRARY_FILES 395 | PackageManager.MATCH_DEBUG_TRIAGED_MISSING 396 // Make sure that we fetch the current provider even if its not 397 // installed for the current user 398 | PackageManager.MATCH_UNINSTALLED_PACKAGES 399 // Fetch signatures for verification 400 | PackageManager.GET_SIGNATURES 401 // Get meta-data for meta data flag verification 402 | PackageManager.GET_META_DATA); 403 } finally { 404 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 405 } 406 407 // Validate the newly fetched package info, throws MissingWebViewPackageException on 408 // failure 409 verifyPackageInfo(response.packageInfo, newPackageInfo); 410 411 ApplicationInfo ai = newPackageInfo.applicationInfo; 412 fixupStubApplicationInfo(ai, pm); 413 414 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, 415 "initialApplication.createApplicationContext"); 416 try { 417 // Construct an app context to load the Java code into the current app. 418 Context webViewContext = initialApplication.createApplicationContext( 419 ai, 420 Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); 421 sPackageInfo = newPackageInfo; 422 return webViewContext; 423 } finally { 424 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 425 } 426 } catch (RemoteException | PackageManager.NameNotFoundException e) { 427 throw new MissingWebViewPackageException("Failed to load WebView provider: " + e); 428 } 429 } 430 getProviderClass()431 private static Class<WebViewFactoryProvider> getProviderClass() { 432 Context webViewContext = null; 433 Application initialApplication = AppGlobals.getInitialApplication(); 434 435 try { 436 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, 437 "WebViewFactory.getWebViewContextAndSetProvider()"); 438 try { 439 webViewContext = getWebViewContextAndSetProvider(); 440 } finally { 441 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 442 } 443 Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " + 444 sPackageInfo.versionName + " (code " + sPackageInfo.getLongVersionCode() + ")"); 445 446 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()"); 447 try { 448 initialApplication.getAssets().addAssetPathAsSharedLibrary( 449 webViewContext.getApplicationInfo().sourceDir); 450 ClassLoader clazzLoader = webViewContext.getClassLoader(); 451 452 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); 453 WebViewLibraryLoader.loadNativeLibrary(clazzLoader, 454 getWebViewLibrary(sPackageInfo.applicationInfo)); 455 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 456 457 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()"); 458 try { 459 return getWebViewProviderClass(clazzLoader); 460 } finally { 461 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 462 } 463 } catch (ClassNotFoundException e) { 464 Log.e(LOGTAG, "error loading provider", e); 465 throw new AndroidRuntimeException(e); 466 } finally { 467 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 468 } 469 } catch (MissingWebViewPackageException e) { 470 Log.e(LOGTAG, "Chromium WebView package does not exist", e); 471 throw new AndroidRuntimeException(e); 472 } 473 } 474 475 /** 476 * Perform any WebView loading preparations that must happen in the zygote. 477 * Currently, this means allocating address space to load the real JNI library later. 478 */ prepareWebViewInZygote()479 public static void prepareWebViewInZygote() { 480 try { 481 WebViewLibraryLoader.reserveAddressSpaceInZygote(); 482 } catch (Throwable t) { 483 // Log and discard errors at this stage as we must not crash the zygote. 484 Log.e(LOGTAG, "error preparing native loader", t); 485 } 486 } 487 488 /** 489 * @hide 490 */ onWebViewProviderChanged(PackageInfo packageInfo)491 public static int onWebViewProviderChanged(PackageInfo packageInfo) { 492 int startedRelroProcesses = 0; 493 ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo); 494 try { 495 fixupStubApplicationInfo(packageInfo.applicationInfo, 496 AppGlobals.getInitialApplication().getPackageManager()); 497 498 startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo); 499 } catch (Throwable t) { 500 // Log and discard errors at this stage as we must not crash the system server. 501 Log.e(LOGTAG, "error preparing webview native library", t); 502 } 503 504 WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo); 505 506 return startedRelroProcesses; 507 } 508 509 private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate"; 510 511 /** @hide */ getUpdateService()512 public static IWebViewUpdateService getUpdateService() { 513 if (isWebViewSupported()) { 514 return getUpdateServiceUnchecked(); 515 } else { 516 return null; 517 } 518 } 519 520 /** @hide */ getUpdateServiceUnchecked()521 static IWebViewUpdateService getUpdateServiceUnchecked() { 522 return IWebViewUpdateService.Stub.asInterface( 523 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME)); 524 } 525 } 526