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