• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.ActivityManagerInternal;
20 import android.app.Application;
21 import android.app.AppGlobals;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.os.Build;
27 import android.os.Process;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.os.StrictMode;
31 import android.os.SystemProperties;
32 import android.os.Trace;
33 import android.text.TextUtils;
34 import android.util.AndroidRuntimeException;
35 import android.util.Log;
36 import com.android.server.LocalServices;
37 import dalvik.system.VMRuntime;
38 
39 import java.io.File;
40 import java.util.Arrays;
41 
42 import com.android.internal.os.Zygote;
43 
44 /**
45  * Top level factory, used creating all the main WebView implementation classes.
46  *
47  * @hide
48  */
49 public final class WebViewFactory {
50 
51     private static final String CHROMIUM_WEBVIEW_FACTORY =
52             "com.android.webview.chromium.WebViewChromiumFactoryProvider";
53 
54     private static final String NULL_WEBVIEW_FACTORY =
55             "com.android.webview.nullwebview.NullWebViewFactoryProvider";
56 
57     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
58             "/data/misc/shared_relro/libwebviewchromium32.relro";
59     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
60             "/data/misc/shared_relro/libwebviewchromium64.relro";
61 
62     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
63             "persist.sys.webview.vmsize";
64     private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
65 
66     private static final String LOGTAG = "WebViewFactory";
67 
68     private static final boolean DEBUG = false;
69 
70     // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
71     // same provider.
72     private static WebViewFactoryProvider sProviderInstance;
73     private static final Object sProviderLock = new Object();
74     private static boolean sAddressSpaceReserved = false;
75     private static PackageInfo sPackageInfo;
76 
getWebViewPackageName()77     public static String getWebViewPackageName() {
78         return AppGlobals.getInitialApplication().getString(
79                 com.android.internal.R.string.config_webViewPackageName);
80     }
81 
getLoadedPackageInfo()82     public static PackageInfo getLoadedPackageInfo() {
83         return sPackageInfo;
84     }
85 
getProvider()86     static WebViewFactoryProvider getProvider() {
87         synchronized (sProviderLock) {
88             // For now the main purpose of this function (and the factory abstraction) is to keep
89             // us honest and minimize usage of WebView internals when binding the proxy.
90             if (sProviderInstance != null) return sProviderInstance;
91 
92             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
93             try {
94                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
95                 loadNativeLibrary();
96                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
97 
98                 Class<WebViewFactoryProvider> providerClass;
99                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getFactoryClass()");
100                 try {
101                     providerClass = getFactoryClass();
102                 } catch (ClassNotFoundException e) {
103                     Log.e(LOGTAG, "error loading provider", e);
104                     throw new AndroidRuntimeException(e);
105                 } finally {
106                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
107                 }
108 
109                 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
110                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
111                 try {
112                     sProviderInstance = providerClass.newInstance();
113                     if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
114                     return sProviderInstance;
115                 } catch (Exception e) {
116                     Log.e(LOGTAG, "error instantiating provider", e);
117                     throw new AndroidRuntimeException(e);
118                 } finally {
119                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
120                     StrictMode.setThreadPolicy(oldPolicy);
121                 }
122             } finally {
123                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
124             }
125         }
126     }
127 
getFactoryClass()128     private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
129         Application initialApplication = AppGlobals.getInitialApplication();
130         try {
131             // First fetch the package info so we can log the webview package version.
132             String packageName = getWebViewPackageName();
133             sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
134             Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName +
135                           " (code " + sPackageInfo.versionCode + ")");
136 
137             // Construct a package context to load the Java code into the current app.
138             Context webViewContext = initialApplication.createPackageContext(packageName,
139                     Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
140             initialApplication.getAssets().addAssetPath(
141                     webViewContext.getApplicationInfo().sourceDir);
142             ClassLoader clazzLoader = webViewContext.getClassLoader();
143             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
144             try {
145                 return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
146                                                                      clazzLoader);
147             } finally {
148                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
149             }
150         } catch (PackageManager.NameNotFoundException e) {
151             // If the package doesn't exist, then try loading the null WebView instead.
152             // If that succeeds, then this is a device without WebView support; if it fails then
153             // swallow the failure, complain that the real WebView is missing and rethrow the
154             // original exception.
155             try {
156                 return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
157             } catch (ClassNotFoundException e2) {
158                 // Ignore.
159             }
160             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
161             throw new AndroidRuntimeException(e);
162         }
163     }
164 
165     /**
166      * Perform any WebView loading preparations that must happen in the zygote.
167      * Currently, this means allocating address space to load the real JNI library later.
168      */
prepareWebViewInZygote()169     public static void prepareWebViewInZygote() {
170         try {
171             System.loadLibrary("webviewchromium_loader");
172             long addressSpaceToReserve =
173                     SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
174                     CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
175             sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
176 
177             if (sAddressSpaceReserved) {
178                 if (DEBUG) {
179                     Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
180                 }
181             } else {
182                 Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
183                         " bytes of address space failed");
184             }
185         } catch (Throwable t) {
186             // Log and discard errors at this stage as we must not crash the zygote.
187             Log.e(LOGTAG, "error preparing native loader", t);
188         }
189     }
190 
191     /**
192      * Perform any WebView loading preparations that must happen at boot from the system server,
193      * after the package manager has started or after an update to the webview is installed.
194      * This must be called in the system server.
195      * Currently, this means spawning the child processes which will create the relro files.
196      */
prepareWebViewInSystemServer()197     public static void prepareWebViewInSystemServer() {
198         String[] nativePaths = null;
199         try {
200             nativePaths = getWebViewNativeLibraryPaths();
201         } catch (Throwable t) {
202             // Log and discard errors at this stage as we must not crash the system server.
203             Log.e(LOGTAG, "error preparing webview native library", t);
204         }
205         prepareWebViewInSystemServer(nativePaths);
206     }
207 
prepareWebViewInSystemServer(String[] nativeLibraryPaths)208     private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
209         if (DEBUG) Log.v(LOGTAG, "creating relro files");
210 
211         // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
212         // unexpected values will be handled there to ensure that we trigger notifying any process
213         // waiting on relreo creation.
214         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
215             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
216             createRelroFile(false /* is64Bit */, nativeLibraryPaths);
217         }
218 
219         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
220             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
221             createRelroFile(true /* is64Bit */, nativeLibraryPaths);
222         }
223     }
224 
onWebViewUpdateInstalled()225     public static void onWebViewUpdateInstalled() {
226         String[] nativeLibs = null;
227         try {
228             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
229             if (nativeLibs != null) {
230                 long newVmSize = 0L;
231 
232                 for (String path : nativeLibs) {
233                     if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
234                     if (path == null) continue;
235                     File f = new File(path);
236                     if (f.exists()) {
237                         long length = f.length();
238                         if (length > newVmSize) {
239                             newVmSize = length;
240                         }
241                     }
242                 }
243 
244                 if (DEBUG) {
245                     Log.v(LOGTAG, "Based on library size, need " + newVmSize +
246                             " bytes of address space.");
247                 }
248                 // The required memory can be larger than the file on disk (due to .bss), and an
249                 // upgraded version of the library will likely be larger, so always attempt to
250                 // reserve twice as much as we think to allow for the library to grow during this
251                 // boot cycle.
252                 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
253                 Log.d(LOGTAG, "Setting new address space to " + newVmSize);
254                 SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
255                         Long.toString(newVmSize));
256             }
257         } catch (Throwable t) {
258             // Log and discard errors at this stage as we must not crash the system server.
259             Log.e(LOGTAG, "error preparing webview native library", t);
260         }
261         prepareWebViewInSystemServer(nativeLibs);
262     }
263 
getWebViewNativeLibraryPaths()264     private static String[] getWebViewNativeLibraryPaths()
265             throws PackageManager.NameNotFoundException {
266         final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";
267 
268         PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
269         ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);
270 
271         String path32;
272         String path64;
273         boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
274         if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
275             // Multi-arch case.
276             if (primaryArchIs64bit) {
277                 // Primary arch: 64-bit, secondary: 32-bit.
278                 path64 = ai.nativeLibraryDir;
279                 path32 = ai.secondaryNativeLibraryDir;
280             } else {
281                 // Primary arch: 32-bit, secondary: 64-bit.
282                 path64 = ai.secondaryNativeLibraryDir;
283                 path32 = ai.nativeLibraryDir;
284             }
285         } else if (primaryArchIs64bit) {
286             // Single-arch 64-bit.
287             path64 = ai.nativeLibraryDir;
288             path32 = "";
289         } else {
290             // Single-arch 32-bit.
291             path32 = ai.nativeLibraryDir;
292             path64 = "";
293         }
294         if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
295         if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
296         return new String[] { path32, path64 };
297     }
298 
createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths)299     private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
300         final String abi =
301                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
302 
303         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
304         Runnable crashHandler = new Runnable() {
305             @Override
306             public void run() {
307                 try {
308                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
309                     getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
310                 } catch (RemoteException e) {
311                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
312                 }
313             }
314         };
315 
316         try {
317             if (nativeLibraryPaths == null
318                     || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
319                 throw new IllegalArgumentException(
320                         "Native library paths to the WebView RelRo process must not be null!");
321             }
322             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
323                     RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
324                     Process.SHARED_RELRO_UID, crashHandler);
325             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
326         } catch (Throwable t) {
327             // Log and discard errors as we must not crash the system server.
328             Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
329             crashHandler.run();
330         }
331     }
332 
333     private static class RelroFileCreator {
334         // Called in an unprivileged child process to create the relro file.
main(String[] args)335         public static void main(String[] args) {
336             boolean result = false;
337             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
338             try{
339                 if (args.length != 2 || args[0] == null || args[1] == null) {
340                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
341                     return;
342                 }
343                 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
344                         " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
345                 if (!sAddressSpaceReserved) {
346                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
347                     return;
348                 }
349                 result = nativeCreateRelroFile(args[0] /* path32 */,
350                                                args[1] /* path64 */,
351                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
352                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
353                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
354             } finally {
355                 // We must do our best to always notify the update service, even if something fails.
356                 try {
357                     getUpdateService().notifyRelroCreationCompleted(is64Bit, result);
358                 } catch (RemoteException e) {
359                     Log.e(LOGTAG, "error notifying update service", e);
360                 }
361 
362                 if (!result) Log.e(LOGTAG, "failed to create relro file");
363 
364                 // Must explicitly exit or else this process will just sit around after we return.
365                 System.exit(0);
366             }
367         }
368     }
369 
loadNativeLibrary()370     private static void loadNativeLibrary() {
371         if (!sAddressSpaceReserved) {
372             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
373             return;
374         }
375 
376         try {
377             getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
378         } catch (RemoteException e) {
379             Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
380             return;
381         }
382 
383         try {
384             String[] args = getWebViewNativeLibraryPaths();
385             boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
386                                                      args[1] /* path64 */,
387                                                      CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
388                                                      CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
389             if (!result) {
390                 Log.w(LOGTAG, "failed to load with relro file, proceeding without");
391             } else if (DEBUG) {
392                 Log.v(LOGTAG, "loaded with relro file");
393             }
394         } catch (PackageManager.NameNotFoundException e) {
395             Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
396         }
397     }
398 
getUpdateService()399     private static IWebViewUpdateService getUpdateService() {
400         return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
401     }
402 
nativeReserveAddressSpace(long addressSpaceToReserve)403     private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
nativeCreateRelroFile(String lib32, String lib64, String relro32, String relro64)404     private static native boolean nativeCreateRelroFile(String lib32, String lib64,
405                                                         String relro32, String relro64);
nativeLoadWithRelroFile(String lib32, String lib64, String relro32, String relro64)406     private static native boolean nativeLoadWithRelroFile(String lib32, String lib64,
407                                                           String relro32, String relro64);
408 }
409