WebViewUpdateServiceImpl.java revision 95f7e8e06dd0003a813f7a4b3c718c68d0fa4a01
1/* 2 * Copyright (C) 2016 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 */ 16package com.android.server.webkit; 17 18import android.content.Context; 19import android.content.pm.ApplicationInfo; 20import android.content.pm.PackageInfo; 21import android.content.pm.PackageManager.NameNotFoundException; 22import android.content.pm.Signature; 23import android.util.Base64; 24import android.util.Slog; 25import android.webkit.WebViewFactory; 26import android.webkit.WebViewProviderInfo; 27import android.webkit.WebViewProviderResponse; 28 29import java.util.ArrayList; 30import java.util.Arrays; 31import java.util.List; 32 33/** 34 * Implementation of the WebViewUpdateService. 35 * This class doesn't depend on the android system like the actual Service does and can be used 36 * directly by tests (as long as they implement a SystemInterface). 37 * @hide 38 */ 39public class WebViewUpdateServiceImpl { 40 private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName(); 41 42 private SystemInterface mSystemInterface; 43 private WebViewUpdater mWebViewUpdater; 44 private Context mContext; 45 46 public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) { 47 mContext = context; 48 mSystemInterface = systemInterface; 49 mWebViewUpdater = new WebViewUpdater(mContext, mSystemInterface); 50 } 51 52 void packageStateChanged(String packageName, int changedState) { 53 updateFallbackStateOnPackageChange(packageName, changedState); 54 mWebViewUpdater.packageStateChanged(packageName, changedState); 55 } 56 57 void prepareWebViewInSystemServer() { 58 updateFallbackStateOnBoot(); 59 mWebViewUpdater.prepareWebViewInSystemServer(); 60 } 61 62 private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) { 63 for (WebViewProviderInfo provider : providers) { 64 if (provider.availableByDefault && !provider.isFallback) { 65 try { 66 PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider); 67 if (isEnabledPackage(packageInfo) 68 && mWebViewUpdater.isValidProvider(provider, packageInfo)) { 69 return true; 70 } 71 } catch (NameNotFoundException e) { 72 // A non-existent provider is neither valid nor enabled 73 } 74 } 75 } 76 return false; 77 } 78 79 /** 80 * Called when a new user has been added to update the state of its fallback package. 81 */ 82 void handleNewUser(int userId) { 83 if (!mSystemInterface.isFallbackLogicEnabled()) return; 84 85 WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); 86 WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); 87 if (fallbackProvider == null) return; 88 89 mSystemInterface.enablePackageForUser(fallbackProvider.packageName, 90 !existsValidNonFallbackProvider(webviewProviders), userId); 91 } 92 93 void notifyRelroCreationCompleted() { 94 mWebViewUpdater.notifyRelroCreationCompleted(); 95 } 96 97 WebViewProviderResponse waitForAndGetProvider() { 98 return mWebViewUpdater.waitForAndGetProvider(); 99 } 100 101 String changeProviderAndSetting(String newProvider) { 102 return mWebViewUpdater.changeProviderAndSetting(newProvider); 103 } 104 105 WebViewProviderInfo[] getValidWebViewPackages() { 106 return mWebViewUpdater.getValidWebViewPackages(); 107 } 108 109 WebViewProviderInfo[] getWebViewPackages() { 110 return mSystemInterface.getWebViewPackages(); 111 } 112 113 String getCurrentWebViewPackageName() { 114 return mWebViewUpdater.getCurrentWebViewPackageName(); 115 } 116 117 void enableFallbackLogic(boolean enable) { 118 mSystemInterface.enableFallbackLogic(enable); 119 } 120 121 private void updateFallbackStateOnBoot() { 122 if (!mSystemInterface.isFallbackLogicEnabled()) return; 123 124 WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); 125 updateFallbackState(webviewProviders, true); 126 } 127 128 /** 129 * Handle the enabled-state of our fallback package, i.e. if there exists some non-fallback 130 * package that is valid (and available by default) then disable the fallback package, 131 * otherwise, enable the fallback package. 132 */ 133 private void updateFallbackStateOnPackageChange(String changedPackage, int changedState) { 134 if (!mSystemInterface.isFallbackLogicEnabled()) return; 135 136 WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); 137 138 // A package was changed / updated / downgraded, early out if it is not one of the 139 // webview packages that are available by default. 140 boolean changedPackageAvailableByDefault = false; 141 for (WebViewProviderInfo provider : webviewProviders) { 142 if (provider.packageName.equals(changedPackage)) { 143 if (provider.availableByDefault) { 144 changedPackageAvailableByDefault = true; 145 } 146 break; 147 } 148 } 149 if (!changedPackageAvailableByDefault) return; 150 updateFallbackState(webviewProviders, false); 151 } 152 153 private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) { 154 // If there exists a valid and enabled non-fallback package - disable the fallback 155 // package, otherwise, enable it. 156 WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); 157 if (fallbackProvider == null) return; 158 boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders); 159 160 boolean isFallbackEnabled = false; 161 try { 162 isFallbackEnabled = isEnabledPackage( 163 mSystemInterface.getPackageInfoForProvider(fallbackProvider)); 164 } catch (NameNotFoundException e) { 165 // No fallback package installed -> early out. 166 return; 167 } 168 169 if (existsValidNonFallbackProvider 170 // During an OTA the primary user's WebView state might differ from other users', so 171 // ignore the state of that user during boot. 172 && (isFallbackEnabled || isBoot)) { 173 mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext, 174 fallbackProvider.packageName); 175 } else if (!existsValidNonFallbackProvider 176 // During an OTA the primary user's WebView state might differ from other users', so 177 // ignore the state of that user during boot. 178 && (!isFallbackEnabled || isBoot)) { 179 // Enable the fallback package for all users. 180 mSystemInterface.enablePackageForAllUsers(mContext, 181 fallbackProvider.packageName, true); 182 } 183 } 184 185 /** 186 * Returns the only fallback provider in the set of given packages, or null if there is none. 187 */ 188 private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) { 189 for (WebViewProviderInfo provider : webviewPackages) { 190 if (provider.isFallback) { 191 return provider; 192 } 193 } 194 return null; 195 } 196 197 boolean isFallbackPackage(String packageName) { 198 if (packageName == null || !mSystemInterface.isFallbackLogicEnabled()) return false; 199 200 WebViewProviderInfo[] webviewPackages = mSystemInterface.getWebViewPackages(); 201 WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewPackages); 202 return (fallbackProvider != null 203 && packageName.equals(fallbackProvider.packageName)); 204 } 205 206 /** 207 * Class that decides what WebView implementation to use and prepares that implementation for 208 * use. 209 */ 210 private static class WebViewUpdater { 211 private Context mContext; 212 private SystemInterface mSystemInterface; 213 private int mMinimumVersionCode = -1; 214 215 public WebViewUpdater(Context context, SystemInterface systemInterface) { 216 mContext = context; 217 mSystemInterface = systemInterface; 218 } 219 220 private static final int WAIT_TIMEOUT_MS = 4500; // KEY_DISPATCHING_TIMEOUT is 5000. 221 222 // Keeps track of the number of running relro creations 223 private int mNumRelroCreationsStarted = 0; 224 private int mNumRelroCreationsFinished = 0; 225 // Implies that we need to rerun relro creation because we are using an out-of-date package 226 private boolean mWebViewPackageDirty = false; 227 private boolean mAnyWebViewInstalled = false; 228 229 private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE; 230 231 // The WebView package currently in use (or the one we are preparing). 232 private PackageInfo mCurrentWebViewPackage = null; 233 234 private Object mLock = new Object(); 235 236 public void packageStateChanged(String packageName, int changedState) { 237 for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) { 238 String webviewPackage = provider.packageName; 239 240 if (webviewPackage.equals(packageName)) { 241 boolean updateWebView = false; 242 boolean removedOrChangedOldPackage = false; 243 String oldProviderName = null; 244 PackageInfo newPackage = null; 245 synchronized(mLock) { 246 try { 247 newPackage = findPreferredWebViewPackage(); 248 if (mCurrentWebViewPackage != null) { 249 oldProviderName = mCurrentWebViewPackage.packageName; 250 if (changedState == WebViewUpdateService.PACKAGE_CHANGED 251 && newPackage.packageName.equals(oldProviderName)) { 252 // If we don't change package name we should only rerun the 253 // preparation phase if the current package has been replaced 254 // (not if it has been enabled/disabled). 255 return; 256 } 257 } 258 // Only trigger update actions if the updated package is the one 259 // that will be used, or the one that was in use before the 260 // update, or if we haven't seen a valid WebView package before. 261 updateWebView = 262 provider.packageName.equals(newPackage.packageName) 263 || provider.packageName.equals(oldProviderName) 264 || mCurrentWebViewPackage == null; 265 // We removed the old package if we received an intent to remove 266 // or replace the old package. 267 removedOrChangedOldPackage = 268 provider.packageName.equals(oldProviderName); 269 if (updateWebView) { 270 onWebViewProviderChanged(newPackage); 271 } 272 } catch (WebViewFactory.MissingWebViewPackageException e) { 273 Slog.e(TAG, "Could not find valid WebView package to create " + 274 "relro with " + e); 275 } 276 } 277 if(updateWebView && !removedOrChangedOldPackage 278 && oldProviderName != null) { 279 // If the provider change is the result of adding or replacing a 280 // package that was not the previous provider then we must kill 281 // packages dependent on the old package ourselves. The framework 282 // only kills dependents of packages that are being removed. 283 mSystemInterface.killPackageDependents(oldProviderName); 284 } 285 return; 286 } 287 } 288 } 289 290 public void prepareWebViewInSystemServer() { 291 try { 292 synchronized(mLock) { 293 mCurrentWebViewPackage = findPreferredWebViewPackage(); 294 // Don't persist the user-chosen setting across boots if the package being 295 // chosen is not used (could be disabled or uninstalled) so that the user won't 296 // be surprised by the device switching to using a certain webview package, 297 // that was uninstalled/disabled a long time ago, if it is installed/enabled 298 // again. 299 mSystemInterface.updateUserSetting(mContext, 300 mCurrentWebViewPackage.packageName); 301 onWebViewProviderChanged(mCurrentWebViewPackage); 302 } 303 } catch (Throwable t) { 304 // Log and discard errors at this stage as we must not crash the system server. 305 Slog.e(TAG, "error preparing webview provider from system server", t); 306 } 307 } 308 309 /** 310 * Change WebView provider and provider setting and kill packages using the old provider. 311 * Return the new provider (in case we are in the middle of creating relro files, or 312 * replacing that provider it will not be in use directly, but will be used when the relros 313 * or the replacement are done). 314 */ 315 public String changeProviderAndSetting(String newProviderName) { 316 PackageInfo oldPackage = null; 317 PackageInfo newPackage = null; 318 boolean providerChanged = false; 319 synchronized(mLock) { 320 oldPackage = mCurrentWebViewPackage; 321 mSystemInterface.updateUserSetting(mContext, newProviderName); 322 323 try { 324 newPackage = findPreferredWebViewPackage(); 325 providerChanged = (oldPackage == null) 326 || !newPackage.packageName.equals(oldPackage.packageName); 327 } catch (WebViewFactory.MissingWebViewPackageException e) { 328 Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " + 329 "package " + e); 330 // If we don't perform the user change but don't have an installed WebView 331 // package, we will have changed the setting and it will be used when a package 332 // is available. 333 return ""; 334 } 335 // Perform the provider change if we chose a new provider 336 if (providerChanged) { 337 onWebViewProviderChanged(newPackage); 338 } 339 } 340 // Kill apps using the old provider only if we changed provider 341 if (providerChanged && oldPackage != null) { 342 mSystemInterface.killPackageDependents(oldPackage.packageName); 343 } 344 // Return the new provider, this is not necessarily the one we were asked to switch to 345 // But the persistent setting will now be pointing to the provider we were asked to 346 // switch to anyway 347 return newPackage.packageName; 348 } 349 350 /** 351 * This is called when we change WebView provider, either when the current provider is 352 * updated or a new provider is chosen / takes precedence. 353 */ 354 private void onWebViewProviderChanged(PackageInfo newPackage) { 355 synchronized(mLock) { 356 mAnyWebViewInstalled = true; 357 if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { 358 mCurrentWebViewPackage = newPackage; 359 360 // The relro creations might 'finish' (not start at all) before 361 // WebViewFactory.onWebViewProviderChanged which means we might not know the 362 // number of started creations before they finish. 363 mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN; 364 mNumRelroCreationsFinished = 0; 365 mNumRelroCreationsStarted = 366 mSystemInterface.onWebViewProviderChanged(newPackage); 367 // If the relro creations finish before we know the number of started creations 368 // we will have to do any cleanup/notifying here. 369 checkIfRelrosDoneLocked(); 370 } else { 371 mWebViewPackageDirty = true; 372 } 373 } 374 } 375 376 private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() { 377 WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); 378 List<ProviderAndPackageInfo> providers = new ArrayList<>(); 379 for(int n = 0; n < allProviders.length; n++) { 380 try { 381 PackageInfo packageInfo = 382 mSystemInterface.getPackageInfoForProvider(allProviders[n]); 383 if (isValidProvider(allProviders[n], packageInfo)) { 384 providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo)); 385 } 386 } catch (NameNotFoundException e) { 387 // Don't add non-existent packages 388 } 389 } 390 return providers.toArray(new ProviderAndPackageInfo[providers.size()]); 391 } 392 393 /** 394 * Fetch only the currently valid WebView packages. 395 **/ 396 public WebViewProviderInfo[] getValidWebViewPackages() { 397 ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); 398 WebViewProviderInfo[] providers = 399 new WebViewProviderInfo[providersAndPackageInfos.length]; 400 for(int n = 0; n < providersAndPackageInfos.length; n++) { 401 providers[n] = providersAndPackageInfos[n].provider; 402 } 403 return providers; 404 } 405 406 407 private class ProviderAndPackageInfo { 408 public final WebViewProviderInfo provider; 409 public final PackageInfo packageInfo; 410 411 public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) { 412 this.provider = provider; 413 this.packageInfo = packageInfo; 414 } 415 } 416 417 /** 418 * Returns either the package info of the WebView provider determined in the following way: 419 * If the user has chosen a provider then use that if it is valid, 420 * otherwise use the first package in the webview priority list that is valid. 421 * 422 */ 423 private PackageInfo findPreferredWebViewPackage() { 424 ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); 425 426 String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext); 427 428 // If the user has chosen provider, use that 429 for (ProviderAndPackageInfo providerAndPackage : providers) { 430 if (providerAndPackage.provider.packageName.equals(userChosenProvider) 431 && isEnabledPackage(providerAndPackage.packageInfo)) { 432 return providerAndPackage.packageInfo; 433 } 434 } 435 436 // User did not choose, or the choice failed; use the most stable provider that is 437 // enabled and available by default (not through user choice). 438 for (ProviderAndPackageInfo providerAndPackage : providers) { 439 if (providerAndPackage.provider.availableByDefault 440 && isEnabledPackage(providerAndPackage.packageInfo)) { 441 return providerAndPackage.packageInfo; 442 } 443 } 444 445 // Could not find any enabled package either, use the most stable and default-available 446 // provider. 447 for (ProviderAndPackageInfo providerAndPackage : providers) { 448 if (providerAndPackage.provider.availableByDefault) { 449 return providerAndPackage.packageInfo; 450 } 451 } 452 453 mAnyWebViewInstalled = false; 454 throw new WebViewFactory.MissingWebViewPackageException( 455 "Could not find a loadable WebView package"); 456 } 457 458 public void notifyRelroCreationCompleted() { 459 synchronized (mLock) { 460 mNumRelroCreationsFinished++; 461 checkIfRelrosDoneLocked(); 462 } 463 } 464 465 public WebViewProviderResponse waitForAndGetProvider() { 466 PackageInfo webViewPackage = null; 467 final long NS_PER_MS = 1000000; 468 final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS; 469 boolean webViewReady = false; 470 int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS; 471 synchronized (mLock) { 472 webViewReady = webViewIsReadyLocked(); 473 while (!webViewReady) { 474 final long timeNowMs = System.nanoTime() / NS_PER_MS; 475 if (timeNowMs >= timeoutTimeMs) break; 476 try { 477 mLock.wait(timeoutTimeMs - timeNowMs); 478 } catch (InterruptedException e) {} 479 webViewReady = webViewIsReadyLocked(); 480 } 481 // Make sure we return the provider that was used to create the relro file 482 webViewPackage = mCurrentWebViewPackage; 483 if (webViewReady) { 484 } else if (!mAnyWebViewInstalled) { 485 webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES; 486 } else { 487 // Either the current relro creation isn't done yet, or the new relro creatioin 488 // hasn't kicked off yet (the last relro creation used an out-of-date WebView). 489 webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO; 490 } 491 } 492 if (!webViewReady) Slog.w(TAG, "creating relro file timed out"); 493 return new WebViewProviderResponse(webViewPackage, webViewStatus); 494 } 495 496 public String getCurrentWebViewPackageName() { 497 synchronized(mLock) { 498 if (mCurrentWebViewPackage == null) 499 return null; 500 return mCurrentWebViewPackage.packageName; 501 } 502 } 503 504 /** 505 * Returns whether WebView is ready and is not going to go through its preparation phase 506 * again directly. 507 */ 508 private boolean webViewIsReadyLocked() { 509 return !mWebViewPackageDirty 510 && (mNumRelroCreationsStarted == mNumRelroCreationsFinished) 511 // The current package might be replaced though we haven't received an intent 512 // declaring this yet, the following flag makes anyone loading WebView to wait in 513 // this case. 514 && mAnyWebViewInstalled; 515 } 516 517 private void checkIfRelrosDoneLocked() { 518 if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { 519 if (mWebViewPackageDirty) { 520 mWebViewPackageDirty = false; 521 // If we have changed provider since we started the relro creation we need to 522 // redo the whole process using the new package instead. 523 try { 524 PackageInfo newPackage = findPreferredWebViewPackage(); 525 onWebViewProviderChanged(newPackage); 526 } catch (WebViewFactory.MissingWebViewPackageException e) { 527 // If we can't find any valid WebView package we are now in a state where 528 // mAnyWebViewInstalled is false, so loading WebView will be blocked and we 529 // should simply wait until we receive an intent declaring a new package was 530 // installed. 531 } 532 } else { 533 mLock.notifyAll(); 534 } 535 } 536 } 537 538 /** 539 * Returns whether this provider is valid for use as a WebView provider. 540 */ 541 public boolean isValidProvider(WebViewProviderInfo configInfo, 542 PackageInfo packageInfo) { 543 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 544 && packageInfo.versionCode < getMinimumVersionCode() 545 && !mSystemInterface.systemIsDebuggable()) { 546 // Non-system package webview providers may be downgraded arbitrarily low, prevent 547 // that by enforcing minimum version code. This check is only enforced for user 548 // builds. 549 return false; 550 } 551 if (providerHasValidSignature(configInfo, packageInfo, mSystemInterface) && 552 WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) { 553 return true; 554 } 555 return false; 556 } 557 558 /** 559 * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode 560 * of all available-by-default and non-fallback WebView provider packages. If there is no 561 * such WebView provider package on the system, then return -1, which means all positive 562 * versionCode WebView packages are accepted. 563 */ 564 private int getMinimumVersionCode() { 565 if (mMinimumVersionCode > 0) { 566 return mMinimumVersionCode; 567 } 568 569 for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) { 570 if (provider.availableByDefault && !provider.isFallback) { 571 try { 572 int versionCode = 573 mSystemInterface.getFactoryPackageVersion(provider.packageName); 574 if (mMinimumVersionCode < 0 || versionCode < mMinimumVersionCode) { 575 mMinimumVersionCode = versionCode; 576 } 577 } catch (NameNotFoundException e) { 578 // Safe to ignore. 579 } 580 } 581 } 582 583 return mMinimumVersionCode; 584 } 585 } 586 587 private static boolean providerHasValidSignature(WebViewProviderInfo provider, 588 PackageInfo packageInfo, SystemInterface systemInterface) { 589 if (systemInterface.systemIsDebuggable()) { 590 return true; 591 } 592 Signature[] packageSignatures; 593 // If no signature is declared, instead check whether the package is included in the 594 // system. 595 if (provider.signatures == null || provider.signatures.length == 0) { 596 return packageInfo.applicationInfo.isSystemApp(); 597 } 598 packageSignatures = packageInfo.signatures; 599 if (packageSignatures.length != 1) 600 return false; 601 602 final byte[] packageSignature = packageSignatures[0].toByteArray(); 603 // Return whether the package signature matches any of the valid signatures 604 for (String signature : provider.signatures) { 605 final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT); 606 if (Arrays.equals(packageSignature, validSignature)) 607 return true; 608 } 609 return false; 610 } 611 612 /** 613 * Returns whether the given package is enabled. 614 * This state can be changed by the user from Settings->Apps 615 */ 616 private static boolean isEnabledPackage(PackageInfo packageInfo) { 617 return packageInfo.applicationInfo.enabled; 618 } 619 620} 621