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