blob: cabba06bdff5f07598108b5b7ad478551cb9eaf0 [file] [log] [blame]
Gustav Senntoned98a1562017-06-06 14:25:49 +01001/*
2 * Copyright (C) 2017 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
17package android.webkit;
18
Gustav Sennton36bed132017-09-18 20:52:43 +010019import android.annotation.NonNull;
20import android.annotation.Nullable;
Gustav Senntoned98a1562017-06-06 14:25:49 +010021import android.app.ActivityManagerInternal;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageInfo;
24import android.os.Build;
25import android.os.Process;
26import android.os.RemoteException;
27import android.os.SystemProperties;
28import android.text.TextUtils;
29import android.util.Log;
30
Gustav Sennton36bed132017-09-18 20:52:43 +010031import com.android.internal.annotations.VisibleForTesting;
Gustav Senntoned98a1562017-06-06 14:25:49 +010032import com.android.server.LocalServices;
33
34import dalvik.system.VMRuntime;
35
36import java.io.File;
37import java.io.IOException;
38import java.util.Arrays;
39import java.util.zip.ZipEntry;
40import java.util.zip.ZipFile;
41
Gustav Sennton36bed132017-09-18 20:52:43 +010042/**
43 * @hide
44 */
45@VisibleForTesting
46public class WebViewLibraryLoader {
Gustav Senntoned98a1562017-06-06 14:25:49 +010047 private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
48
49 private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
50 "/data/misc/shared_relro/libwebviewchromium32.relro";
51 private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
52 "/data/misc/shared_relro/libwebviewchromium64.relro";
53 private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
54
55 private static final boolean DEBUG = false;
56
57 private static boolean sAddressSpaceReserved = false;
58
59 /**
60 * Private class for running the actual relro creation in an unprivileged child process.
61 * RelroFileCreator is a static class (without access to the outer class) to avoid accidentally
62 * using any static members from the outer class. Those members will in reality differ between
63 * the child process in which RelroFileCreator operates, and the app process in which the static
64 * members of this class are used.
65 */
66 private static class RelroFileCreator {
67 // Called in an unprivileged child process to create the relro file.
68 public static void main(String[] args) {
69 boolean result = false;
70 boolean is64Bit = VMRuntime.getRuntime().is64Bit();
71 try {
Gustav Senntonae498f22017-10-05 15:34:13 +010072 if (args.length != 1 || args[0] == null) {
Gustav Senntoned98a1562017-06-06 14:25:49 +010073 Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
74 return;
75 }
Gustav Senntonae498f22017-10-05 15:34:13 +010076 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), lib: " + args[0]);
Gustav Senntoned98a1562017-06-06 14:25:49 +010077 if (!sAddressSpaceReserved) {
78 Log.e(LOGTAG, "can't create relro file; address space not reserved");
79 return;
80 }
Gustav Senntonae498f22017-10-05 15:34:13 +010081 result = nativeCreateRelroFile(args[0] /* path */,
82 is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
83 CHROMIUM_WEBVIEW_NATIVE_RELRO_32);
Gustav Senntoned98a1562017-06-06 14:25:49 +010084 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
85 } finally {
86 // We must do our best to always notify the update service, even if something fails.
87 try {
Torne (Richard Coles)29675b22017-10-04 11:29:58 -040088 WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
Gustav Senntoned98a1562017-06-06 14:25:49 +010089 } catch (RemoteException e) {
90 Log.e(LOGTAG, "error notifying update service", e);
91 }
92
93 if (!result) Log.e(LOGTAG, "failed to create relro file");
94
95 // Must explicitly exit or else this process will just sit around after we return.
96 System.exit(0);
97 }
98 }
99 }
100
101 /**
102 * Create a single relro file by invoking an isolated process that to do the actual work.
103 */
Gustav Sennton36bed132017-09-18 20:52:43 +0100104 static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
Gustav Senntoned98a1562017-06-06 14:25:49 +0100105 final String abi =
106 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
107
108 // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
109 Runnable crashHandler = new Runnable() {
110 @Override
111 public void run() {
112 try {
113 Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
114 WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
115 } catch (RemoteException e) {
116 Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
117 }
118 }
119 };
120
121 try {
Gustav Sennton36bed132017-09-18 20:52:43 +0100122 if (nativeLib == null || nativeLib.path == null) {
Gustav Senntoned98a1562017-06-06 14:25:49 +0100123 throw new IllegalArgumentException(
124 "Native library paths to the WebView RelRo process must not be null!");
125 }
Sudheer Shankaf6690102017-10-16 10:20:32 -0700126 boolean success = LocalServices.getService(ActivityManagerInternal.class)
127 .startIsolatedProcess(
128 RelroFileCreator.class.getName(), new String[] { nativeLib.path },
129 "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
130 if (!success) throw new Exception("Failed to start the relro file creator process");
Gustav Senntoned98a1562017-06-06 14:25:49 +0100131 } catch (Throwable t) {
132 // Log and discard errors as we must not crash the system server.
133 Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
134 crashHandler.run();
135 }
136 }
137
Gustav Sennton36bed132017-09-18 20:52:43 +0100138 /**
139 * Perform preparations needed to allow loading WebView from an application. This method should
140 * be called whenever we change WebView provider.
141 * @return the number of relro processes started.
142 */
Gustav Sennton0e541ef2017-10-24 14:28:26 +0100143 static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
144 throws WebViewFactory.MissingWebViewPackageException {
Gustav Sennton36bed132017-09-18 20:52:43 +0100145 WebViewNativeLibrary nativeLib32bit =
146 getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
147 WebViewNativeLibrary nativeLib64bit =
148 getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
149 updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
150
151 return createRelros(nativeLib32bit, nativeLib64bit);
152 }
153
154 /**
155 * @return the number of relro processes started.
156 */
157 private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit,
158 @Nullable WebViewNativeLibrary nativeLib64bit) {
Gustav Sennton0e541ef2017-10-24 14:28:26 +0100159 if (DEBUG) Log.v(LOGTAG, "creating relro files");
160 int numRelros = 0;
161
Gustav Sennton0e541ef2017-10-24 14:28:26 +0100162 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
Gustav Sennton36bed132017-09-18 20:52:43 +0100163 if (nativeLib32bit == null) {
164 Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
165 } else {
166 if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
167 createRelroFile(false /* is64Bit */, nativeLib32bit);
168 numRelros++;
169 }
Gustav Sennton0e541ef2017-10-24 14:28:26 +0100170 }
171
172 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
Gustav Sennton36bed132017-09-18 20:52:43 +0100173 if (nativeLib64bit == null) {
174 Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
175 } else {
176 if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
177 createRelroFile(true /* is64Bit */, nativeLib64bit);
178 numRelros++;
179 }
Gustav Sennton0e541ef2017-10-24 14:28:26 +0100180 }
181 return numRelros;
182 }
183
Gustav Senntoned98a1562017-06-06 14:25:49 +0100184 /**
185 *
186 * @return the native WebView libraries in the new WebView APK.
187 */
Gustav Sennton36bed132017-09-18 20:52:43 +0100188 private static void updateWebViewZygoteVmSize(
189 @Nullable WebViewNativeLibrary nativeLib32bit,
190 @Nullable WebViewNativeLibrary nativeLib64bit)
Gustav Senntoned98a1562017-06-06 14:25:49 +0100191 throws WebViewFactory.MissingWebViewPackageException {
192 // Find the native libraries of the new WebView package, to change the size of the
193 // memory region in the Zygote reserved for the library.
Gustav Sennton36bed132017-09-18 20:52:43 +0100194 long newVmSize = 0L;
Gustav Senntoned98a1562017-06-06 14:25:49 +0100195
Gustav Sennton36bed132017-09-18 20:52:43 +0100196 if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
197 if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100198
Gustav Sennton36bed132017-09-18 20:52:43 +0100199 if (DEBUG) {
200 Log.v(LOGTAG, "Based on library size, need " + newVmSize
201 + " bytes of address space.");
Gustav Senntoned98a1562017-06-06 14:25:49 +0100202 }
Gustav Sennton36bed132017-09-18 20:52:43 +0100203 // The required memory can be larger than the file on disk (due to .bss), and an
204 // upgraded version of the library will likely be larger, so always attempt to
205 // reserve twice as much as we think to allow for the library to grow during this
206 // boot cycle.
207 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
208 Log.d(LOGTAG, "Setting new address space to " + newVmSize);
209 setWebViewZygoteVmSize(newVmSize);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100210 }
211
212 /**
213 * Reserve space for the native library to be loaded into.
214 */
215 static void reserveAddressSpaceInZygote() {
216 System.loadLibrary("webviewchromium_loader");
217 long addressSpaceToReserve =
218 SystemProperties.getLong(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
219 CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
220 sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
221
222 if (sAddressSpaceReserved) {
223 if (DEBUG) {
224 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
225 }
226 } else {
227 Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
228 }
229 }
230
231 /**
232 * Load WebView's native library into the current process.
Nate Fischer7051dd12017-10-26 14:51:25 -0700233 *
234 * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
235 *
Gustav Senntoned98a1562017-06-06 14:25:49 +0100236 * @param clazzLoader class loader used to find the linker namespace to load the library into.
Torne (Richard Coles)ec572f62018-02-21 15:55:24 -0500237 * @param libraryFileName the filename of the library to load.
Gustav Senntoned98a1562017-06-06 14:25:49 +0100238 */
Torne (Richard Coles)ec572f62018-02-21 15:55:24 -0500239 public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
Gustav Senntoned98a1562017-06-06 14:25:49 +0100240 if (!sAddressSpaceReserved) {
241 Log.e(LOGTAG, "can't load with relro file; address space not reserved");
242 return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
243 }
244
Gustav Senntonae498f22017-10-05 15:34:13 +0100245 String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
246 CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
247 int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100248 if (result != WebViewFactory.LIBLOAD_SUCCESS) {
249 Log.w(LOGTAG, "failed to load with relro file, proceeding without");
250 } else if (DEBUG) {
251 Log.v(LOGTAG, "loaded with relro file");
252 }
253 return result;
254 }
255
256 /**
257 * Fetch WebView's native library paths from {@param packageInfo}.
Gustav Sennton36bed132017-09-18 20:52:43 +0100258 * @hide
Gustav Senntoned98a1562017-06-06 14:25:49 +0100259 */
Gustav Sennton36bed132017-09-18 20:52:43 +0100260 @Nullable
261 @VisibleForTesting
262 public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
263 boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
Gustav Senntoned98a1562017-06-06 14:25:49 +0100264 ApplicationInfo ai = packageInfo.applicationInfo;
265 final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
266
Gustav Sennton36bed132017-09-18 20:52:43 +0100267 String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100268
Gustav Sennton36bed132017-09-18 20:52:43 +0100269 WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
270 is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100271
Gustav Sennton36bed132017-09-18 20:52:43 +0100272 if (DEBUG) {
273 Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
274 }
275 return lib;
Gustav Senntoned98a1562017-06-06 14:25:49 +0100276 }
277
Gustav Sennton36bed132017-09-18 20:52:43 +0100278 /**
279 * @return the directory of the native WebView library with bitness {@param is64bit}.
280 * @hide
281 */
282 @VisibleForTesting
283 public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
284 // Primary arch has the same bitness as the library we are looking for.
285 if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;
286
287 // Secondary arch has the same bitness as the library we are looking for.
288 if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
289 return ai.secondaryNativeLibraryDir;
290 }
291
292 return "";
293 }
294
295 /**
296 * @return an object describing a native WebView library given the directory path of that
297 * library, or null if the library couldn't be found.
298 */
299 @Nullable
300 private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
301 String nativeLibFileName, String[] abiList, String libDirectory)
302 throws WebViewFactory.MissingWebViewPackageException {
303 if (TextUtils.isEmpty(libDirectory)) return null;
304 String libPath = libDirectory + "/" + nativeLibFileName;
305 File f = new File(libPath);
306 if (f.exists()) {
307 return new WebViewNativeLibrary(libPath, f.length());
308 } else {
309 return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
310 }
311 }
312
313 /**
314 * @hide
315 */
316 @VisibleForTesting
317 public static class WebViewNativeLibrary {
318 public final String path;
319 public final long size;
320
321 WebViewNativeLibrary(String path, long size) {
322 this.path = path;
323 this.size = size;
324 }
325 }
326
327 private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
328 String[] abiList,
329 String nativeLibFileName)
Gustav Senntoned98a1562017-06-06 14:25:49 +0100330 throws WebViewFactory.MissingWebViewPackageException {
331 // Search the APK for a native library conforming to a listed ABI.
332 try (ZipFile z = new ZipFile(apkPath)) {
333 for (String abi : abiList) {
334 final String entry = "lib/" + abi + "/" + nativeLibFileName;
335 ZipEntry e = z.getEntry(entry);
336 if (e != null && e.getMethod() == ZipEntry.STORED) {
337 // Return a path formatted for dlopen() load from APK.
Gustav Sennton36bed132017-09-18 20:52:43 +0100338 return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
Gustav Senntoned98a1562017-06-06 14:25:49 +0100339 }
340 }
341 } catch (IOException e) {
342 throw new WebViewFactory.MissingWebViewPackageException(e);
343 }
Gustav Sennton36bed132017-09-18 20:52:43 +0100344 return null;
Gustav Senntoned98a1562017-06-06 14:25:49 +0100345 }
346
347 /**
348 * Sets the size of the memory area in which to store the relro section.
349 */
350 private static void setWebViewZygoteVmSize(long vmSize) {
351 SystemProperties.set(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
352 Long.toString(vmSize));
353 }
354
355 static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
Gustav Senntonae498f22017-10-05 15:34:13 +0100356 static native boolean nativeCreateRelroFile(String lib, String relro);
357 static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100358}