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 
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 
getProvider()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      */
signaturesEquals(Signature[] s1, Signature[] s2)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
verifyPackageInfo(PackageInfo chosen, PackageInfo toUse)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      */
fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)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 
getWebViewContextAndSetProvider()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 
getProviderClass()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      */
prepareWebViewInZygote()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      */
onWebViewProviderChanged(PackageInfo packageInfo)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 */
getUpdateService()512     public static IWebViewUpdateService getUpdateService() {
513         if (isWebViewSupported()) {
514             return getUpdateServiceUnchecked();
515         } else {
516             return null;
517         }
518     }
519 
520     /** @hide */
getUpdateServiceUnchecked()521     static IWebViewUpdateService getUpdateServiceUnchecked() {
522         return IWebViewUpdateService.Stub.asInterface(
523                 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
524     }
525 }
526