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.ActivityManagerInternal;
22 import android.app.AppGlobals;
23 import android.app.Application;
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.Build;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.StrictMode;
34 import android.os.SystemProperties;
35 import android.os.Trace;
36 import android.text.TextUtils;
37 import android.util.AndroidRuntimeException;
38 import android.util.ArraySet;
39 import android.util.Log;
40 
41 import com.android.server.LocalServices;
42 
43 import dalvik.system.VMRuntime;
44 
45 import java.lang.reflect.Method;
46 import java.io.File;
47 import java.io.IOException;
48 import java.util.Arrays;
49 import java.util.zip.ZipEntry;
50 import java.util.zip.ZipFile;
51 
52 /**
53  * Top level factory, used creating all the main WebView implementation classes.
54  *
55  * @hide
56  */
57 @SystemApi
58 public final class WebViewFactory {
59 
60     // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
61     /** @hide */
62     private static final String CHROMIUM_WEBVIEW_FACTORY =
63             "com.android.webview.chromium.WebViewChromiumFactoryProviderForO";
64 
65     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
66 
67     private static final String NULL_WEBVIEW_FACTORY =
68             "com.android.webview.nullwebview.NullWebViewFactoryProvider";
69 
70     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
71             "/data/misc/shared_relro/libwebviewchromium32.relro";
72     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
73             "/data/misc/shared_relro/libwebviewchromium64.relro";
74 
75     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
76             "persist.sys.webview.vmsize";
77     private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
78 
79     private static final String LOGTAG = "WebViewFactory";
80 
81     private static final boolean DEBUG = false;
82 
83     // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
84     // same provider.
85     private static WebViewFactoryProvider sProviderInstance;
86     private static final Object sProviderLock = new Object();
87     private static boolean sAddressSpaceReserved = false;
88     private static PackageInfo sPackageInfo;
89 
90     // Error codes for loadWebViewNativeLibraryFromPackage
91     public static final int LIBLOAD_SUCCESS = 0;
92     public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
93     public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2;
94 
95     // error codes for waiting for WebView preparation
96     public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3;
97     public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
98 
99     // native relro loading error codes
100     public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5;
101     public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6;
102     public static final int LIBLOAD_FAILED_JNI_CALL = 7;
103 
104     // more error codes for waiting for WebView preparation
105     public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8;
106 
107     // error for namespace lookup
108     public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
109 
getWebViewPreparationErrorReason(int error)110     private static String getWebViewPreparationErrorReason(int error) {
111         switch (error) {
112             case LIBLOAD_FAILED_WAITING_FOR_RELRO:
113                 return "Time out waiting for Relro files being created";
114             case LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES:
115                 return "No WebView installed";
116             case LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN:
117                 return "Crashed for unknown reason";
118         }
119         return "Unknown";
120     }
121 
122     /**
123      * @hide
124      */
125     public static class MissingWebViewPackageException extends AndroidRuntimeException {
MissingWebViewPackageException(String message)126         public MissingWebViewPackageException(String message) { super(message); }
MissingWebViewPackageException(Exception e)127         public MissingWebViewPackageException(Exception e) { super(e); }
128     }
129 
130     /**
131      * @hide
132      */
getWebViewLibrary(ApplicationInfo ai)133     public static String getWebViewLibrary(ApplicationInfo ai) {
134         if (ai.metaData != null)
135             return ai.metaData.getString("com.android.webview.WebViewLibrary");
136         return null;
137     }
138 
getLoadedPackageInfo()139     public static PackageInfo getLoadedPackageInfo() {
140         synchronized (sProviderLock) {
141             return sPackageInfo;
142         }
143     }
144 
145     /**
146      * @hide
147      */
getWebViewProviderClass(ClassLoader clazzLoader)148     public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
149             throws ClassNotFoundException {
150         return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
151                 true, clazzLoader);
152     }
153 
154     /**
155      * Load the native library for the given package name iff that package
156      * name is the same as the one providing the webview.
157      */
loadWebViewNativeLibraryFromPackage(String packageName, ClassLoader clazzLoader)158     public static int loadWebViewNativeLibraryFromPackage(String packageName,
159                                                           ClassLoader clazzLoader) {
160         WebViewProviderResponse response = null;
161         try {
162             response = getUpdateService().waitForAndGetProvider();
163         } catch (RemoteException e) {
164             Log.e(LOGTAG, "error waiting for relro creation", e);
165             return LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN;
166         }
167 
168 
169         if (response.status != LIBLOAD_SUCCESS
170                 && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
171             return response.status;
172         }
173         if (!response.packageInfo.packageName.equals(packageName)) {
174             return LIBLOAD_WRONG_PACKAGE_NAME;
175         }
176 
177         PackageManager packageManager = AppGlobals.getInitialApplication().getPackageManager();
178         PackageInfo packageInfo;
179         try {
180             packageInfo = packageManager.getPackageInfo(packageName,
181                     PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
182         } catch (PackageManager.NameNotFoundException e) {
183             Log.e(LOGTAG, "Couldn't find package " + packageName);
184             return LIBLOAD_WRONG_PACKAGE_NAME;
185         }
186 
187         int loadNativeRet = loadNativeLibrary(clazzLoader, packageInfo);
188         // If we failed waiting for relro we want to return that fact even if we successfully load
189         // the relro file.
190         if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
191         return loadNativeRet;
192     }
193 
getProvider()194     static WebViewFactoryProvider getProvider() {
195         synchronized (sProviderLock) {
196             // For now the main purpose of this function (and the factory abstraction) is to keep
197             // us honest and minimize usage of WebView internals when binding the proxy.
198             if (sProviderInstance != null) return sProviderInstance;
199 
200             final int uid = android.os.Process.myUid();
201             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
202                     || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
203                     || uid == android.os.Process.BLUETOOTH_UID) {
204                 throw new UnsupportedOperationException(
205                         "For security reasons, WebView is not allowed in privileged processes");
206             }
207 
208             StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
209             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
210             try {
211                 Class<WebViewFactoryProvider> providerClass = getProviderClass();
212                 Method staticFactory = null;
213                 try {
214                     staticFactory = providerClass.getMethod(
215                         CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
216                 } catch (Exception e) {
217                     if (DEBUG) {
218                         Log.w(LOGTAG, "error instantiating provider with static factory method", e);
219                     }
220                 }
221 
222                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
223                 try {
224                     sProviderInstance = (WebViewFactoryProvider)
225                             staticFactory.invoke(null, new WebViewDelegate());
226                     if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
227                     return sProviderInstance;
228                 } catch (Exception e) {
229                     Log.e(LOGTAG, "error instantiating provider", e);
230                     throw new AndroidRuntimeException(e);
231                 } finally {
232                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
233                 }
234             } finally {
235                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
236                 StrictMode.setThreadPolicy(oldPolicy);
237             }
238         }
239     }
240 
241     /**
242      * Returns true if the signatures match, false otherwise
243      */
signaturesEquals(Signature[] s1, Signature[] s2)244     private static boolean signaturesEquals(Signature[] s1, Signature[] s2) {
245         if (s1 == null) {
246             return s2 == null;
247         }
248         if (s2 == null) return false;
249 
250         ArraySet<Signature> set1 = new ArraySet<>();
251         for(Signature signature : s1) {
252             set1.add(signature);
253         }
254         ArraySet<Signature> set2 = new ArraySet<>();
255         for(Signature signature : s2) {
256             set2.add(signature);
257         }
258         return set1.equals(set2);
259     }
260 
261     // Throws MissingWebViewPackageException on failure
verifyPackageInfo(PackageInfo chosen, PackageInfo toUse)262     private static void verifyPackageInfo(PackageInfo chosen, PackageInfo toUse) {
263         if (!chosen.packageName.equals(toUse.packageName)) {
264             throw new MissingWebViewPackageException("Failed to verify WebView provider, "
265                     + "packageName mismatch, expected: "
266                     + chosen.packageName + " actual: " + toUse.packageName);
267         }
268         if (chosen.versionCode > toUse.versionCode) {
269             throw new MissingWebViewPackageException("Failed to verify WebView provider, "
270                     + "version code is lower than expected: " + chosen.versionCode
271                     + " actual: " + toUse.versionCode);
272         }
273         if (getWebViewLibrary(toUse.applicationInfo) == null) {
274             throw new MissingWebViewPackageException("Tried to load an invalid WebView provider: "
275                     + toUse.packageName);
276         }
277         if (!signaturesEquals(chosen.signatures, toUse.signatures)) {
278             throw new MissingWebViewPackageException("Failed to verify WebView provider, "
279                     + "signature mismatch");
280         }
281     }
282 
283     /**
284      * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the
285      * required values from the donor package. If the ApplicationInfo is for a full WebView,
286      * leave it alone. Throws MissingWebViewPackageException if the donor is missing.
287      */
fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)288     private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) {
289         String donorPackageName = null;
290         if (ai.metaData != null) {
291             donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
292         }
293         if (donorPackageName != null) {
294             PackageInfo donorPackage;
295             try {
296                 donorPackage = pm.getPackageInfo(
297                         donorPackageName,
298                         PackageManager.GET_SHARED_LIBRARY_FILES
299                         | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
300                         | PackageManager.MATCH_UNINSTALLED_PACKAGES
301                         | PackageManager.MATCH_FACTORY_ONLY);
302             } catch (PackageManager.NameNotFoundException e) {
303                 throw new MissingWebViewPackageException("Failed to find donor package: " +
304                                                          donorPackageName);
305             }
306             ApplicationInfo donorInfo = donorPackage.applicationInfo;
307 
308             // Replace the stub's code locations with the donor's.
309             ai.sourceDir = donorInfo.sourceDir;
310             ai.splitSourceDirs = donorInfo.splitSourceDirs;
311             ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
312             ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;
313 
314             // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code
315             // and so they are unset.
316             ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
317             ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
318         }
319     }
320 
getWebViewContextAndSetProvider()321     private static Context getWebViewContextAndSetProvider() {
322         Application initialApplication = AppGlobals.getInitialApplication();
323         try {
324             WebViewProviderResponse response = null;
325             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
326                     "WebViewUpdateService.waitForAndGetProvider()");
327             try {
328                 response = getUpdateService().waitForAndGetProvider();
329             } finally {
330                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
331             }
332             if (response.status != LIBLOAD_SUCCESS
333                     && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
334                 throw new MissingWebViewPackageException("Failed to load WebView provider: "
335                         + getWebViewPreparationErrorReason(response.status));
336             }
337             // Register to be killed before fetching package info - so that we will be
338             // killed if the package info goes out-of-date.
339             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "ActivityManager.addPackageDependency()");
340             try {
341                 ActivityManager.getService().addPackageDependency(
342                         response.packageInfo.packageName);
343             } finally {
344                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
345             }
346             // Fetch package info and verify it against the chosen package
347             PackageInfo newPackageInfo = null;
348             PackageManager pm = initialApplication.getPackageManager();
349             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()");
350             try {
351                 newPackageInfo = pm.getPackageInfo(
352                     response.packageInfo.packageName,
353                     PackageManager.GET_SHARED_LIBRARY_FILES
354                     | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
355                     // Make sure that we fetch the current provider even if its not
356                     // installed for the current user
357                     | PackageManager.MATCH_UNINSTALLED_PACKAGES
358                     // Fetch signatures for verification
359                     | PackageManager.GET_SIGNATURES
360                     // Get meta-data for meta data flag verification
361                     | PackageManager.GET_META_DATA);
362             } finally {
363                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
364             }
365 
366             // Validate the newly fetched package info, throws MissingWebViewPackageException on
367             // failure
368             verifyPackageInfo(response.packageInfo, newPackageInfo);
369 
370             ApplicationInfo ai = newPackageInfo.applicationInfo;
371             fixupStubApplicationInfo(ai, pm);
372 
373             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
374                     "initialApplication.createApplicationContext");
375             try {
376                 // Construct an app context to load the Java code into the current app.
377                 Context webViewContext = initialApplication.createApplicationContext(
378                         ai,
379                         Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
380                 sPackageInfo = newPackageInfo;
381                 return webViewContext;
382             } finally {
383                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
384             }
385         } catch (RemoteException | PackageManager.NameNotFoundException e) {
386             throw new MissingWebViewPackageException("Failed to load WebView provider: " + e);
387         }
388     }
389 
getProviderClass()390     private static Class<WebViewFactoryProvider> getProviderClass() {
391         Context webViewContext = null;
392         Application initialApplication = AppGlobals.getInitialApplication();
393 
394         try {
395             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
396                     "WebViewFactory.getWebViewContextAndSetProvider()");
397             try {
398                 webViewContext = getWebViewContextAndSetProvider();
399             } finally {
400                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
401             }
402             Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
403                     sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
404 
405             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
406             try {
407                 initialApplication.getAssets().addAssetPathAsSharedLibrary(
408                         webViewContext.getApplicationInfo().sourceDir);
409                 ClassLoader clazzLoader = webViewContext.getClassLoader();
410 
411                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
412                 loadNativeLibrary(clazzLoader, sPackageInfo);
413                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
414 
415                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
416                 try {
417                     return getWebViewProviderClass(clazzLoader);
418                 } finally {
419                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
420                 }
421             } catch (ClassNotFoundException e) {
422                 Log.e(LOGTAG, "error loading provider", e);
423                 throw new AndroidRuntimeException(e);
424             } finally {
425                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
426             }
427         } catch (MissingWebViewPackageException e) {
428             // If the package doesn't exist, then try loading the null WebView instead.
429             // If that succeeds, then this is a device without WebView support; if it fails then
430             // swallow the failure, complain that the real WebView is missing and rethrow the
431             // original exception.
432             try {
433                 return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
434             } catch (ClassNotFoundException e2) {
435                 // Ignore.
436             }
437             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
438             throw new AndroidRuntimeException(e);
439         }
440     }
441 
442     /**
443      * Perform any WebView loading preparations that must happen in the zygote.
444      * Currently, this means allocating address space to load the real JNI library later.
445      */
prepareWebViewInZygote()446     public static void prepareWebViewInZygote() {
447         try {
448             System.loadLibrary("webviewchromium_loader");
449             long addressSpaceToReserve =
450                     SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
451                     CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
452             sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
453 
454             if (sAddressSpaceReserved) {
455                 if (DEBUG) {
456                     Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
457                 }
458             } else {
459                 Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
460                         " bytes of address space failed");
461             }
462         } catch (Throwable t) {
463             // Log and discard errors at this stage as we must not crash the zygote.
464             Log.e(LOGTAG, "error preparing native loader", t);
465         }
466     }
467 
prepareWebViewInSystemServer(String[] nativeLibraryPaths)468     private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
469         if (DEBUG) Log.v(LOGTAG, "creating relro files");
470         int numRelros = 0;
471 
472         // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
473         // unexpected values will be handled there to ensure that we trigger notifying any process
474         // waiting on relro creation.
475         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
476             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
477             createRelroFile(false /* is64Bit */, nativeLibraryPaths);
478             numRelros++;
479         }
480 
481         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
482             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
483             createRelroFile(true /* is64Bit */, nativeLibraryPaths);
484             numRelros++;
485         }
486         return numRelros;
487     }
488 
489     /**
490      * @hide
491      */
onWebViewProviderChanged(PackageInfo packageInfo)492     public static int onWebViewProviderChanged(PackageInfo packageInfo) {
493         String[] nativeLibs = null;
494         String originalSourceDir = packageInfo.applicationInfo.sourceDir;
495         try {
496             fixupStubApplicationInfo(packageInfo.applicationInfo,
497                                      AppGlobals.getInitialApplication().getPackageManager());
498 
499             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths(packageInfo);
500             if (nativeLibs != null) {
501                 long newVmSize = 0L;
502 
503                 for (String path : nativeLibs) {
504                     if (path == null || TextUtils.isEmpty(path)) continue;
505                     if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
506                     File f = new File(path);
507                     if (f.exists()) {
508                         newVmSize = Math.max(newVmSize, f.length());
509                         continue;
510                     }
511                     if (path.contains("!/")) {
512                         String[] split = TextUtils.split(path, "!/");
513                         if (split.length == 2) {
514                             try (ZipFile z = new ZipFile(split[0])) {
515                                 ZipEntry e = z.getEntry(split[1]);
516                                 if (e != null && e.getMethod() == ZipEntry.STORED) {
517                                     newVmSize = Math.max(newVmSize, e.getSize());
518                                     continue;
519                                 }
520                             }
521                             catch (IOException e) {
522                                 Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
523                             }
524                         }
525                     }
526                     Log.e(LOGTAG, "error sizing load for " + path);
527                 }
528 
529                 if (DEBUG) {
530                     Log.v(LOGTAG, "Based on library size, need " + newVmSize +
531                             " bytes of address space.");
532                 }
533                 // The required memory can be larger than the file on disk (due to .bss), and an
534                 // upgraded version of the library will likely be larger, so always attempt to
535                 // reserve twice as much as we think to allow for the library to grow during this
536                 // boot cycle.
537                 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
538                 Log.d(LOGTAG, "Setting new address space to " + newVmSize);
539                 SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
540                         Long.toString(newVmSize));
541             }
542         } catch (Throwable t) {
543             // Log and discard errors at this stage as we must not crash the system server.
544             Log.e(LOGTAG, "error preparing webview native library", t);
545         }
546 
547         WebViewZygote.onWebViewProviderChanged(packageInfo, originalSourceDir);
548 
549         return prepareWebViewInSystemServer(nativeLibs);
550     }
551 
552     // throws MissingWebViewPackageException
getLoadFromApkPath(String apkPath, String[] abiList, String nativeLibFileName)553     private static String getLoadFromApkPath(String apkPath,
554                                              String[] abiList,
555                                              String nativeLibFileName) {
556         // Search the APK for a native library conforming to a listed ABI.
557         try (ZipFile z = new ZipFile(apkPath)) {
558             for (String abi : abiList) {
559                 final String entry = "lib/" + abi + "/" + nativeLibFileName;
560                 ZipEntry e = z.getEntry(entry);
561                 if (e != null && e.getMethod() == ZipEntry.STORED) {
562                     // Return a path formatted for dlopen() load from APK.
563                     return apkPath + "!/" + entry;
564                 }
565             }
566         } catch (IOException e) {
567             throw new MissingWebViewPackageException(e);
568         }
569         return "";
570     }
571 
572     // throws MissingWebViewPackageException
getWebViewNativeLibraryPaths(PackageInfo packageInfo)573     private static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo) {
574         ApplicationInfo ai = packageInfo.applicationInfo;
575         final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai);
576 
577         String path32;
578         String path64;
579         boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
580         if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
581             // Multi-arch case.
582             if (primaryArchIs64bit) {
583                 // Primary arch: 64-bit, secondary: 32-bit.
584                 path64 = ai.nativeLibraryDir;
585                 path32 = ai.secondaryNativeLibraryDir;
586             } else {
587                 // Primary arch: 32-bit, secondary: 64-bit.
588                 path64 = ai.secondaryNativeLibraryDir;
589                 path32 = ai.nativeLibraryDir;
590             }
591         } else if (primaryArchIs64bit) {
592             // Single-arch 64-bit.
593             path64 = ai.nativeLibraryDir;
594             path32 = "";
595         } else {
596             // Single-arch 32-bit.
597             path32 = ai.nativeLibraryDir;
598             path64 = "";
599         }
600 
601         // Form the full paths to the extracted native libraries.
602         // If libraries were not extracted, try load from APK paths instead.
603         if (!TextUtils.isEmpty(path32)) {
604             path32 += "/" + NATIVE_LIB_FILE_NAME;
605             File f = new File(path32);
606             if (!f.exists()) {
607                 path32 = getLoadFromApkPath(ai.sourceDir,
608                                             Build.SUPPORTED_32_BIT_ABIS,
609                                             NATIVE_LIB_FILE_NAME);
610             }
611         }
612         if (!TextUtils.isEmpty(path64)) {
613             path64 += "/" + NATIVE_LIB_FILE_NAME;
614             File f = new File(path64);
615             if (!f.exists()) {
616                 path64 = getLoadFromApkPath(ai.sourceDir,
617                                             Build.SUPPORTED_64_BIT_ABIS,
618                                             NATIVE_LIB_FILE_NAME);
619             }
620         }
621 
622         if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
623         return new String[] { path32, path64 };
624     }
625 
createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths)626     private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
627         final String abi =
628                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
629 
630         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
631         Runnable crashHandler = new Runnable() {
632             @Override
633             public void run() {
634                 try {
635                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
636                     getUpdateService().notifyRelroCreationCompleted();
637                 } catch (RemoteException e) {
638                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
639                 }
640             }
641         };
642 
643         try {
644             if (nativeLibraryPaths == null
645                     || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
646                 throw new IllegalArgumentException(
647                         "Native library paths to the WebView RelRo process must not be null!");
648             }
649             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
650                     RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
651                     Process.SHARED_RELRO_UID, crashHandler);
652             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
653         } catch (Throwable t) {
654             // Log and discard errors as we must not crash the system server.
655             Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
656             crashHandler.run();
657         }
658     }
659 
660     private static class RelroFileCreator {
661         // Called in an unprivileged child process to create the relro file.
main(String[] args)662         public static void main(String[] args) {
663             boolean result = false;
664             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
665             try{
666                 if (args.length != 2 || args[0] == null || args[1] == null) {
667                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
668                     return;
669                 }
670                 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
671                         " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
672                 if (!sAddressSpaceReserved) {
673                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
674                     return;
675                 }
676                 result = nativeCreateRelroFile(args[0] /* path32 */,
677                                                args[1] /* path64 */,
678                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
679                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
680                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
681             } finally {
682                 // We must do our best to always notify the update service, even if something fails.
683                 try {
684                     getUpdateService().notifyRelroCreationCompleted();
685                 } catch (RemoteException e) {
686                     Log.e(LOGTAG, "error notifying update service", e);
687                 }
688 
689                 if (!result) Log.e(LOGTAG, "failed to create relro file");
690 
691                 // Must explicitly exit or else this process will just sit around after we return.
692                 System.exit(0);
693             }
694         }
695     }
696 
697     // Assumes that we have waited for relro creation
loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo)698     private static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo) {
699         if (!sAddressSpaceReserved) {
700             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
701             return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
702         }
703 
704         String[] args = getWebViewNativeLibraryPaths(packageInfo);
705         int result = nativeLoadWithRelroFile(args[0] /* path32 */,
706                                              args[1] /* path64 */,
707                                              CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
708                                              CHROMIUM_WEBVIEW_NATIVE_RELRO_64,
709                                              clazzLoader);
710         if (result != LIBLOAD_SUCCESS) {
711             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
712         } else if (DEBUG) {
713             Log.v(LOGTAG, "loaded with relro file");
714         }
715         return result;
716     }
717 
718     private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
719 
720     /** @hide */
getUpdateService()721     public static IWebViewUpdateService getUpdateService() {
722         return IWebViewUpdateService.Stub.asInterface(
723                 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
724     }
725 
nativeReserveAddressSpace(long addressSpaceToReserve)726     private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
nativeCreateRelroFile(String lib32, String lib64, String relro32, String relro64)727     private static native boolean nativeCreateRelroFile(String lib32, String lib64,
728                                                         String relro32, String relro64);
nativeLoadWithRelroFile(String lib32, String lib64, String relro32, String relro64, ClassLoader clazzLoader)729     private static native int nativeLoadWithRelroFile(String lib32, String lib64,
730                                                       String relro32, String relro64,
731                                                       ClassLoader clazzLoader);
732 }
733