Home | History | Annotate | Download | only in webkit
      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 
     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 {
    103         public MissingWebViewPackageException(String message) { super(message); }
    104         public MissingWebViewPackageException(Exception e) { super(e); }
    105     }
    106 
    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      */
    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      */
    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      */
    150     static String getDataDirectorySuffix() {
    151         synchronized (sProviderLock) {
    152             return sDataDirectorySuffix;
    153         }
    154     }
    155 
    156     /**
    157      * @hide
    158      */
    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 
    165     public static PackageInfo getLoadedPackageInfo() {
    166         synchronized (sProviderLock) {
    167             return sPackageInfo;
    168         }
    169     }
    170 
    171     /**
    172      * @hide
    173      */
    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      */
    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 
    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      */
    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
    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      */
    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 
    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 
    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      */
    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      */
    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 */
    512     public static IWebViewUpdateService getUpdateService() {
    513         if (isWebViewSupported()) {
    514             return getUpdateServiceUnchecked();
    515         } else {
    516             return null;
    517         }
    518     }
    519 
    520     /** @hide */
    521     static IWebViewUpdateService getUpdateServiceUnchecked() {
    522         return IWebViewUpdateService.Stub.asInterface(
    523                 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
    524     }
    525 }
    526