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.ActivityManagerInternal;
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.os.Build;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.StrictMode;
32 import android.os.SystemProperties;
33 import android.os.Trace;
34 import android.text.TextUtils;
35 import android.util.AndroidRuntimeException;
36 import android.util.Log;
37 
38 import com.android.server.LocalServices;
39 
40 import dalvik.system.VMRuntime;
41 
42 import java.io.File;
43 import java.io.IOException;
44 import java.util.Arrays;
45 import java.util.zip.ZipEntry;
46 import java.util.zip.ZipFile;
47 
48 /**
49  * Top level factory, used creating all the main WebView implementation classes.
50  *
51  * @hide
52  */
53 @SystemApi
54 public final class WebViewFactory {
55 
56     private static final String CHROMIUM_WEBVIEW_FACTORY =
57             "com.android.webview.chromium.WebViewChromiumFactoryProvider";
58 
59     private static final String NULL_WEBVIEW_FACTORY =
60             "com.android.webview.nullwebview.NullWebViewFactoryProvider";
61 
62     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
63             "/data/misc/shared_relro/libwebviewchromium32.relro";
64     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
65             "/data/misc/shared_relro/libwebviewchromium64.relro";
66 
67     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
68             "persist.sys.webview.vmsize";
69     private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
70 
71     private static final String LOGTAG = "WebViewFactory";
72 
73     private static final boolean DEBUG = false;
74 
75     // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
76     // same provider.
77     private static WebViewFactoryProvider sProviderInstance;
78     private static final Object sProviderLock = new Object();
79     private static boolean sAddressSpaceReserved = false;
80     private static PackageInfo sPackageInfo;
81 
82     // Error codes for loadWebViewNativeLibraryFromPackage
83     public static final int LIBLOAD_SUCCESS = 0;
84     public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
85     public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2;
86     public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3;
87     public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
88 
89     // native relro loading error codes
90     public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5;
91     public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6;
92     public static final int LIBLOAD_FAILED_JNI_CALL = 7;
93 
94     private static class MissingWebViewPackageException extends AndroidRuntimeException {
MissingWebViewPackageException(String message)95         public MissingWebViewPackageException(String message) { super(message); }
MissingWebViewPackageException(Exception e)96         public MissingWebViewPackageException(Exception e) { super(e); }
97     }
98 
getWebViewPackageName()99     public static String getWebViewPackageName() {
100         return AppGlobals.getInitialApplication().getString(
101                 com.android.internal.R.string.config_webViewPackageName);
102     }
103 
fetchPackageInfo()104     private static PackageInfo fetchPackageInfo() {
105         PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
106         try {
107             return pm.getPackageInfo(getWebViewPackageName(), PackageManager.GET_META_DATA);
108         } catch (PackageManager.NameNotFoundException e) {
109             throw new MissingWebViewPackageException(e);
110         }
111     }
112 
113     // throws MissingWebViewPackageException
getWebViewApplicationInfo()114     private static ApplicationInfo getWebViewApplicationInfo() {
115         if (sPackageInfo == null) {
116             return fetchPackageInfo().applicationInfo;
117         } else {
118             return sPackageInfo.applicationInfo;
119         }
120     }
121 
getWebViewLibrary(ApplicationInfo ai)122     private static String getWebViewLibrary(ApplicationInfo ai) {
123         if (ai.metaData != null)
124             return ai.metaData.getString("com.android.webview.WebViewLibrary");
125         return null;
126     }
127 
getLoadedPackageInfo()128     public static PackageInfo getLoadedPackageInfo() {
129         return sPackageInfo;
130     }
131 
132     /**
133      * Load the native library for the given package name iff that package
134      * name is the same as the one providing the webview.
135      */
loadWebViewNativeLibraryFromPackage(String packageName)136     public static int loadWebViewNativeLibraryFromPackage(String packageName) {
137         sPackageInfo = fetchPackageInfo();
138         if (packageName != null && packageName.equals(sPackageInfo.packageName)) {
139             return loadNativeLibrary();
140         }
141         return LIBLOAD_WRONG_PACKAGE_NAME;
142     }
143 
getProvider()144     static WebViewFactoryProvider getProvider() {
145         synchronized (sProviderLock) {
146             // For now the main purpose of this function (and the factory abstraction) is to keep
147             // us honest and minimize usage of WebView internals when binding the proxy.
148             if (sProviderInstance != null) return sProviderInstance;
149 
150             final int uid = android.os.Process.myUid();
151             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
152                 throw new UnsupportedOperationException(
153                         "For security reasons, WebView is not allowed in privileged processes");
154             }
155 
156             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
157             try {
158                 Class<WebViewFactoryProvider> providerClass = getProviderClass();
159 
160                 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
161                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
162                 try {
163                     sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
164                             .newInstance(new WebViewDelegate());
165                     if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
166                     return sProviderInstance;
167                 } catch (Exception e) {
168                     Log.e(LOGTAG, "error instantiating provider", e);
169                     throw new AndroidRuntimeException(e);
170                 } finally {
171                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
172                     StrictMode.setThreadPolicy(oldPolicy);
173                 }
174             } finally {
175                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
176             }
177         }
178     }
179 
getProviderClass()180     private static Class<WebViewFactoryProvider> getProviderClass() {
181         try {
182             // First fetch the package info so we can log the webview package version.
183             sPackageInfo = fetchPackageInfo();
184             Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
185                 sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
186 
187             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
188             loadNativeLibrary();
189             Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
190 
191             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
192             try {
193                 return getChromiumProviderClass();
194             } catch (ClassNotFoundException e) {
195                 Log.e(LOGTAG, "error loading provider", e);
196                 throw new AndroidRuntimeException(e);
197             } finally {
198                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
199             }
200         } catch (MissingWebViewPackageException e) {
201             // If the package doesn't exist, then try loading the null WebView instead.
202             // If that succeeds, then this is a device without WebView support; if it fails then
203             // swallow the failure, complain that the real WebView is missing and rethrow the
204             // original exception.
205             try {
206                 return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
207             } catch (ClassNotFoundException e2) {
208                 // Ignore.
209             }
210             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
211             throw new AndroidRuntimeException(e);
212         }
213     }
214 
215     // throws MissingWebViewPackageException
getChromiumProviderClass()216     private static Class<WebViewFactoryProvider> getChromiumProviderClass()
217             throws ClassNotFoundException {
218         Application initialApplication = AppGlobals.getInitialApplication();
219         try {
220             // Construct a package context to load the Java code into the current app.
221             Context webViewContext = initialApplication.createPackageContext(
222                     sPackageInfo.packageName,
223                     Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
224             initialApplication.getAssets().addAssetPath(
225                     webViewContext.getApplicationInfo().sourceDir);
226             ClassLoader clazzLoader = webViewContext.getClassLoader();
227             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
228             try {
229                 return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
230                                                                      clazzLoader);
231             } finally {
232                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
233             }
234         } catch (PackageManager.NameNotFoundException e) {
235             throw new MissingWebViewPackageException(e);
236         }
237     }
238 
239     /**
240      * Perform any WebView loading preparations that must happen in the zygote.
241      * Currently, this means allocating address space to load the real JNI library later.
242      */
prepareWebViewInZygote()243     public static void prepareWebViewInZygote() {
244         try {
245             System.loadLibrary("webviewchromium_loader");
246             long addressSpaceToReserve =
247                     SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
248                     CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
249             sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
250 
251             if (sAddressSpaceReserved) {
252                 if (DEBUG) {
253                     Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
254                 }
255             } else {
256                 Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
257                         " bytes of address space failed");
258             }
259         } catch (Throwable t) {
260             // Log and discard errors at this stage as we must not crash the zygote.
261             Log.e(LOGTAG, "error preparing native loader", t);
262         }
263     }
264 
265     /**
266      * Perform any WebView loading preparations that must happen at boot from the system server,
267      * after the package manager has started or after an update to the webview is installed.
268      * This must be called in the system server.
269      * Currently, this means spawning the child processes which will create the relro files.
270      */
prepareWebViewInSystemServer()271     public static void prepareWebViewInSystemServer() {
272         String[] nativePaths = null;
273         try {
274             nativePaths = getWebViewNativeLibraryPaths();
275         } catch (Throwable t) {
276             // Log and discard errors at this stage as we must not crash the system server.
277             Log.e(LOGTAG, "error preparing webview native library", t);
278         }
279         prepareWebViewInSystemServer(nativePaths);
280     }
281 
prepareWebViewInSystemServer(String[] nativeLibraryPaths)282     private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
283         if (DEBUG) Log.v(LOGTAG, "creating relro files");
284 
285         // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
286         // unexpected values will be handled there to ensure that we trigger notifying any process
287         // waiting on relreo creation.
288         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
289             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
290             createRelroFile(false /* is64Bit */, nativeLibraryPaths);
291         }
292 
293         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
294             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
295             createRelroFile(true /* is64Bit */, nativeLibraryPaths);
296         }
297     }
298 
onWebViewUpdateInstalled()299     public static void onWebViewUpdateInstalled() {
300         String[] nativeLibs = null;
301         try {
302             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
303             if (nativeLibs != null) {
304                 long newVmSize = 0L;
305 
306                 for (String path : nativeLibs) {
307                     if (path == null || TextUtils.isEmpty(path)) continue;
308                     if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
309                     File f = new File(path);
310                     if (f.exists()) {
311                         newVmSize = Math.max(newVmSize, f.length());
312                         continue;
313                     }
314                     if (path.contains("!/")) {
315                         String[] split = TextUtils.split(path, "!/");
316                         if (split.length == 2) {
317                             try (ZipFile z = new ZipFile(split[0])) {
318                                 ZipEntry e = z.getEntry(split[1]);
319                                 if (e != null && e.getMethod() == ZipEntry.STORED) {
320                                     newVmSize = Math.max(newVmSize, e.getSize());
321                                     continue;
322                                 }
323                             }
324                             catch (IOException e) {
325                                 Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
326                             }
327                         }
328                     }
329                     Log.e(LOGTAG, "error sizing load for " + path);
330                 }
331 
332                 if (DEBUG) {
333                     Log.v(LOGTAG, "Based on library size, need " + newVmSize +
334                             " bytes of address space.");
335                 }
336                 // The required memory can be larger than the file on disk (due to .bss), and an
337                 // upgraded version of the library will likely be larger, so always attempt to
338                 // reserve twice as much as we think to allow for the library to grow during this
339                 // boot cycle.
340                 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
341                 Log.d(LOGTAG, "Setting new address space to " + newVmSize);
342                 SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
343                         Long.toString(newVmSize));
344             }
345         } catch (Throwable t) {
346             // Log and discard errors at this stage as we must not crash the system server.
347             Log.e(LOGTAG, "error preparing webview native library", t);
348         }
349         prepareWebViewInSystemServer(nativeLibs);
350     }
351 
352     // throws MissingWebViewPackageException
getLoadFromApkPath(String apkPath, String[] abiList, String nativeLibFileName)353     private static String getLoadFromApkPath(String apkPath,
354                                              String[] abiList,
355                                              String nativeLibFileName) {
356         // Search the APK for a native library conforming to a listed ABI.
357         try (ZipFile z = new ZipFile(apkPath)) {
358             for (String abi : abiList) {
359                 final String entry = "lib/" + abi + "/" + nativeLibFileName;
360                 ZipEntry e = z.getEntry(entry);
361                 if (e != null && e.getMethod() == ZipEntry.STORED) {
362                     // Return a path formatted for dlopen() load from APK.
363                     return apkPath + "!/" + entry;
364                 }
365             }
366         } catch (IOException e) {
367             throw new MissingWebViewPackageException(e);
368         }
369         return "";
370     }
371 
372     // throws MissingWebViewPackageException
getWebViewNativeLibraryPaths()373     private static String[] getWebViewNativeLibraryPaths() {
374         ApplicationInfo ai = getWebViewApplicationInfo();
375         final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai);
376 
377         String path32;
378         String path64;
379         boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
380         if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
381             // Multi-arch case.
382             if (primaryArchIs64bit) {
383                 // Primary arch: 64-bit, secondary: 32-bit.
384                 path64 = ai.nativeLibraryDir;
385                 path32 = ai.secondaryNativeLibraryDir;
386             } else {
387                 // Primary arch: 32-bit, secondary: 64-bit.
388                 path64 = ai.secondaryNativeLibraryDir;
389                 path32 = ai.nativeLibraryDir;
390             }
391         } else if (primaryArchIs64bit) {
392             // Single-arch 64-bit.
393             path64 = ai.nativeLibraryDir;
394             path32 = "";
395         } else {
396             // Single-arch 32-bit.
397             path32 = ai.nativeLibraryDir;
398             path64 = "";
399         }
400 
401         // Form the full paths to the extracted native libraries.
402         // If libraries were not extracted, try load from APK paths instead.
403         if (!TextUtils.isEmpty(path32)) {
404             path32 += "/" + NATIVE_LIB_FILE_NAME;
405             File f = new File(path32);
406             if (!f.exists()) {
407                 path32 = getLoadFromApkPath(ai.sourceDir,
408                                             Build.SUPPORTED_32_BIT_ABIS,
409                                             NATIVE_LIB_FILE_NAME);
410             }
411         }
412         if (!TextUtils.isEmpty(path64)) {
413             path64 += "/" + NATIVE_LIB_FILE_NAME;
414             File f = new File(path64);
415             if (!f.exists()) {
416                 path64 = getLoadFromApkPath(ai.sourceDir,
417                                             Build.SUPPORTED_64_BIT_ABIS,
418                                             NATIVE_LIB_FILE_NAME);
419             }
420         }
421 
422         if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
423         return new String[] { path32, path64 };
424     }
425 
createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths)426     private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
427         final String abi =
428                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
429 
430         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
431         Runnable crashHandler = new Runnable() {
432             @Override
433             public void run() {
434                 try {
435                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
436                     getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
437                 } catch (RemoteException e) {
438                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
439                 }
440             }
441         };
442 
443         try {
444             if (nativeLibraryPaths == null
445                     || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
446                 throw new IllegalArgumentException(
447                         "Native library paths to the WebView RelRo process must not be null!");
448             }
449             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
450                     RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
451                     Process.SHARED_RELRO_UID, crashHandler);
452             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
453         } catch (Throwable t) {
454             // Log and discard errors as we must not crash the system server.
455             Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
456             crashHandler.run();
457         }
458     }
459 
460     private static class RelroFileCreator {
461         // Called in an unprivileged child process to create the relro file.
main(String[] args)462         public static void main(String[] args) {
463             boolean result = false;
464             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
465             try{
466                 if (args.length != 2 || args[0] == null || args[1] == null) {
467                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
468                     return;
469                 }
470                 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
471                         " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
472                 if (!sAddressSpaceReserved) {
473                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
474                     return;
475                 }
476                 result = nativeCreateRelroFile(args[0] /* path32 */,
477                                                args[1] /* path64 */,
478                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
479                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
480                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
481             } finally {
482                 // We must do our best to always notify the update service, even if something fails.
483                 try {
484                     getUpdateService().notifyRelroCreationCompleted(is64Bit, result);
485                 } catch (RemoteException e) {
486                     Log.e(LOGTAG, "error notifying update service", e);
487                 }
488 
489                 if (!result) Log.e(LOGTAG, "failed to create relro file");
490 
491                 // Must explicitly exit or else this process will just sit around after we return.
492                 System.exit(0);
493             }
494         }
495     }
496 
loadNativeLibrary()497     private static int loadNativeLibrary() {
498         if (!sAddressSpaceReserved) {
499             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
500             return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
501         }
502 
503         try {
504             getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
505         } catch (RemoteException e) {
506             Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
507             return LIBLOAD_FAILED_WAITING_FOR_RELRO;
508         }
509 
510         try {
511             String[] args = getWebViewNativeLibraryPaths();
512             int result = nativeLoadWithRelroFile(args[0] /* path32 */,
513                                                      args[1] /* path64 */,
514                                                      CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
515                                                      CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
516             if (result != LIBLOAD_SUCCESS) {
517                 Log.w(LOGTAG, "failed to load with relro file, proceeding without");
518             } else if (DEBUG) {
519                 Log.v(LOGTAG, "loaded with relro file");
520             }
521             return result;
522         } catch (MissingWebViewPackageException e) {
523             Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
524             return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
525         }
526     }
527 
getUpdateService()528     private static IWebViewUpdateService getUpdateService() {
529         return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
530     }
531 
nativeReserveAddressSpace(long addressSpaceToReserve)532     private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
nativeCreateRelroFile(String lib32, String lib64, String relro32, String relro64)533     private static native boolean nativeCreateRelroFile(String lib32, String lib64,
534                                                         String relro32, String relro64);
nativeLoadWithRelroFile(String lib32, String lib64, String relro32, String relro64)535     private static native int nativeLoadWithRelroFile(String lib32, String lib64,
536                                                           String relro32, String relro64);
537 }
538