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