blob: 6f9e8ece4b13a550c821a8e5b17ade660ff07abb [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
19import android.app.ActivityManagerInternal;
20import android.content.pm.ApplicationInfo;
21import android.content.pm.PackageInfo;
22import android.os.Build;
23import android.os.Process;
24import android.os.RemoteException;
25import android.os.SystemProperties;
26import android.text.TextUtils;
27import android.util.Log;
28
29import com.android.server.LocalServices;
30
31import dalvik.system.VMRuntime;
32
33import java.io.File;
34import java.io.IOException;
35import java.util.Arrays;
36import java.util.zip.ZipEntry;
37import java.util.zip.ZipFile;
38
39class WebViewLibraryLoader {
40 private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
41
42 private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
43 "/data/misc/shared_relro/libwebviewchromium32.relro";
44 private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
45 "/data/misc/shared_relro/libwebviewchromium64.relro";
46 private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
47
48 private static final boolean DEBUG = false;
49
50 private static boolean sAddressSpaceReserved = false;
51
52 /**
53 * Private class for running the actual relro creation in an unprivileged child process.
54 * RelroFileCreator is a static class (without access to the outer class) to avoid accidentally
55 * using any static members from the outer class. Those members will in reality differ between
56 * the child process in which RelroFileCreator operates, and the app process in which the static
57 * members of this class are used.
58 */
59 private static class RelroFileCreator {
60 // Called in an unprivileged child process to create the relro file.
61 public static void main(String[] args) {
62 boolean result = false;
63 boolean is64Bit = VMRuntime.getRuntime().is64Bit();
64 try {
65 if (args.length != 2 || args[0] == null || args[1] == null) {
66 Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
67 return;
68 }
69 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), "
70 + " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
71 if (!sAddressSpaceReserved) {
72 Log.e(LOGTAG, "can't create relro file; address space not reserved");
73 return;
74 }
75 result = nativeCreateRelroFile(args[0] /* path32 */,
76 args[1] /* path64 */,
77 CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
78 CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
79 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
80 } finally {
81 // We must do our best to always notify the update service, even if something fails.
82 try {
83 WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
84 } catch (RemoteException e) {
85 Log.e(LOGTAG, "error notifying update service", e);
86 }
87
88 if (!result) Log.e(LOGTAG, "failed to create relro file");
89
90 // Must explicitly exit or else this process will just sit around after we return.
91 System.exit(0);
92 }
93 }
94 }
95
96 /**
97 * Create a single relro file by invoking an isolated process that to do the actual work.
98 */
99 static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
100 final String abi =
101 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
102
103 // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
104 Runnable crashHandler = new Runnable() {
105 @Override
106 public void run() {
107 try {
108 Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
109 WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
110 } catch (RemoteException e) {
111 Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
112 }
113 }
114 };
115
116 try {
117 if (nativeLibraryPaths == null
118 || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
119 throw new IllegalArgumentException(
120 "Native library paths to the WebView RelRo process must not be null!");
121 }
122 int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
123 RelroFileCreator.class.getName(), nativeLibraryPaths,
124 "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
125 if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
126 } catch (Throwable t) {
127 // Log and discard errors as we must not crash the system server.
128 Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
129 crashHandler.run();
130 }
131 }
132
133 /**
134 *
135 * @return the native WebView libraries in the new WebView APK.
136 */
137 static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
138 throws WebViewFactory.MissingWebViewPackageException {
139 // Find the native libraries of the new WebView package, to change the size of the
140 // memory region in the Zygote reserved for the library.
141 String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo);
142 if (nativeLibs != null) {
143 long newVmSize = 0L;
144
145 for (String path : nativeLibs) {
146 if (path == null || TextUtils.isEmpty(path)) continue;
147 if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
148 File f = new File(path);
149 if (f.exists()) {
150 newVmSize = Math.max(newVmSize, f.length());
151 continue;
152 }
153 if (path.contains("!/")) {
154 String[] split = TextUtils.split(path, "!/");
155 if (split.length == 2) {
156 try (ZipFile z = new ZipFile(split[0])) {
157 ZipEntry e = z.getEntry(split[1]);
158 if (e != null && e.getMethod() == ZipEntry.STORED) {
159 newVmSize = Math.max(newVmSize, e.getSize());
160 continue;
161 }
162 }
163 catch (IOException e) {
164 Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
165 }
166 }
167 }
168 Log.e(LOGTAG, "error sizing load for " + path);
169 }
170
171 if (DEBUG) {
172 Log.v(LOGTAG, "Based on library size, need " + newVmSize
173 + " bytes of address space.");
174 }
175 // The required memory can be larger than the file on disk (due to .bss), and an
176 // upgraded version of the library will likely be larger, so always attempt to
177 // reserve twice as much as we think to allow for the library to grow during this
178 // boot cycle.
179 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
180 Log.d(LOGTAG, "Setting new address space to " + newVmSize);
181 setWebViewZygoteVmSize(newVmSize);
182 }
183 return nativeLibs;
184 }
185
186 /**
187 * Reserve space for the native library to be loaded into.
188 */
189 static void reserveAddressSpaceInZygote() {
190 System.loadLibrary("webviewchromium_loader");
191 long addressSpaceToReserve =
192 SystemProperties.getLong(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
193 CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
194 sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
195
196 if (sAddressSpaceReserved) {
197 if (DEBUG) {
198 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
199 }
200 } else {
201 Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
202 }
203 }
204
205 /**
206 * Load WebView's native library into the current process.
207 * Note: assumes that we have waited for relro creation.
208 * @param clazzLoader class loader used to find the linker namespace to load the library into.
209 * @param packageInfo the package from which WebView is loaded.
210 */
211 static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo)
212 throws WebViewFactory.MissingWebViewPackageException {
213 if (!sAddressSpaceReserved) {
214 Log.e(LOGTAG, "can't load with relro file; address space not reserved");
215 return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
216 }
217
Gustav Senntonbeec0862017-06-21 17:51:27 +0100218 final String libraryFileName =
219 WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo);
220 int result = nativeLoadWithRelroFile(libraryFileName, CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
221 CHROMIUM_WEBVIEW_NATIVE_RELRO_64, clazzLoader);
Gustav Senntoned98a1562017-06-06 14:25:49 +0100222 if (result != WebViewFactory.LIBLOAD_SUCCESS) {
223 Log.w(LOGTAG, "failed to load with relro file, proceeding without");
224 } else if (DEBUG) {
225 Log.v(LOGTAG, "loaded with relro file");
226 }
227 return result;
228 }
229
230 /**
231 * Fetch WebView's native library paths from {@param packageInfo}.
232 */
233 static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo)
234 throws WebViewFactory.MissingWebViewPackageException {
235 ApplicationInfo ai = packageInfo.applicationInfo;
236 final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
237
238 String path32;
239 String path64;
240 boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
241 if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
242 // Multi-arch case.
243 if (primaryArchIs64bit) {
244 // Primary arch: 64-bit, secondary: 32-bit.
245 path64 = ai.nativeLibraryDir;
246 path32 = ai.secondaryNativeLibraryDir;
247 } else {
248 // Primary arch: 32-bit, secondary: 64-bit.
249 path64 = ai.secondaryNativeLibraryDir;
250 path32 = ai.nativeLibraryDir;
251 }
252 } else if (primaryArchIs64bit) {
253 // Single-arch 64-bit.
254 path64 = ai.nativeLibraryDir;
255 path32 = "";
256 } else {
257 // Single-arch 32-bit.
258 path32 = ai.nativeLibraryDir;
259 path64 = "";
260 }
261
262 // Form the full paths to the extracted native libraries.
263 // If libraries were not extracted, try load from APK paths instead.
264 if (!TextUtils.isEmpty(path32)) {
265 path32 += "/" + nativeLibFileName;
266 File f = new File(path32);
267 if (!f.exists()) {
268 path32 = getLoadFromApkPath(ai.sourceDir,
269 Build.SUPPORTED_32_BIT_ABIS,
270 nativeLibFileName);
271 }
272 }
273 if (!TextUtils.isEmpty(path64)) {
274 path64 += "/" + nativeLibFileName;
275 File f = new File(path64);
276 if (!f.exists()) {
277 path64 = getLoadFromApkPath(ai.sourceDir,
278 Build.SUPPORTED_64_BIT_ABIS,
279 nativeLibFileName);
280 }
281 }
282
283 if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
284 return new String[] { path32, path64 };
285 }
286
287 private static String getLoadFromApkPath(String apkPath,
288 String[] abiList,
289 String nativeLibFileName)
290 throws WebViewFactory.MissingWebViewPackageException {
291 // Search the APK for a native library conforming to a listed ABI.
292 try (ZipFile z = new ZipFile(apkPath)) {
293 for (String abi : abiList) {
294 final String entry = "lib/" + abi + "/" + nativeLibFileName;
295 ZipEntry e = z.getEntry(entry);
296 if (e != null && e.getMethod() == ZipEntry.STORED) {
297 // Return a path formatted for dlopen() load from APK.
298 return apkPath + "!/" + entry;
299 }
300 }
301 } catch (IOException e) {
302 throw new WebViewFactory.MissingWebViewPackageException(e);
303 }
304 return "";
305 }
306
307 /**
308 * Sets the size of the memory area in which to store the relro section.
309 */
310 private static void setWebViewZygoteVmSize(long vmSize) {
311 SystemProperties.set(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
312 Long.toString(vmSize));
313 }
314
315 static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
316 static native boolean nativeCreateRelroFile(String lib32, String lib64,
317 String relro32, String relro64);
Gustav Senntonbeec0862017-06-21 17:51:27 +0100318 static native int nativeLoadWithRelroFile(String lib, String relro32, String relro64,
Gustav Senntoned98a1562017-06-06 14:25:49 +0100319 ClassLoader clazzLoader);
320}