1 /*
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 
17 package android.webkit;
18 
19 import android.annotation.NonNull;
20 import android.app.ActivityManagerInternal;
21 import android.app.ActivityThread;
22 import android.app.LoadedApk;
23 import android.content.Context;
24 import android.content.pm.PackageInfo;
25 import android.os.Build;
26 import android.os.Process;
27 import android.util.Log;
28 import android.util.Slog;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.server.LocalServices;
32 
33 import dalvik.system.VMRuntime;
34 
35 import java.util.Arrays;
36 
37 /**
38  * @hide
39  */
40 @VisibleForTesting
41 public class WebViewLibraryLoader {
42     private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
43 
44     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
45             "/data/misc/shared_relro/libwebviewchromium32.relro";
46     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
47             "/data/misc/shared_relro/libwebviewchromium64.relro";
48 
49     private static final boolean DEBUG = false;
50 
51     private static boolean sAddressSpaceReserved = false;
52 
53     /**
54      * Private class for running the actual relro creation in an unprivileged child process.
55      * RelroFileCreator is a static class (without access to the outer class) to avoid accidentally
56      * using any static members from the outer class. Those members will in reality differ between
57      * the child process in which RelroFileCreator operates, and the app process in which the static
58      * members of this class are used.
59      */
60     private static class RelroFileCreator {
61         // Called in an unprivileged child process to create the relro file.
main(String[] args)62         public static void main(String[] args) {
63             boolean result = false;
64             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
65             try {
66                 if (args.length != 2 || args[0] == null || args[1] == null) {
67                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
68                     return;
69                 }
70                 String packageName = args[0];
71                 String libraryFileName = args[1];
72                 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), package: "
73                         + packageName + " library: " + libraryFileName);
74                 if (!sAddressSpaceReserved) {
75                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
76                     return;
77                 }
78                 LoadedApk apk = ActivityThread.currentActivityThread().getPackageInfo(
79                         packageName,
80                         null,
81                         Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
82                 result = nativeCreateRelroFile(libraryFileName,
83                                                is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
84                                                          CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
85                                                apk.getClassLoader());
86                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
87             } finally {
88                 // We must do our best to always notify the update service, even if something fails.
89                 try {
90                     if (Flags.updateServiceIpcWrapper()) {
91                         WebViewUpdateManager.getInstance().notifyRelroCreationCompleted();
92                     } else {
93                         WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
94                     }
95                 } catch (Exception e) {
96                     Log.e(LOGTAG, "error notifying update service", e);
97                 }
98 
99                 if (!result) Log.e(LOGTAG, "failed to create relro file");
100 
101                 // Must explicitly exit or else this process will just sit around after we return.
102                 System.exit(0);
103             }
104         }
105     }
106 
107     /**
108      * Create a single relro file by invoking an isolated process that to do the actual work.
109      */
createRelroFile(final boolean is64Bit, @NonNull String packageName, @NonNull String libraryFileName)110     static void createRelroFile(final boolean is64Bit, @NonNull String packageName,
111             @NonNull String libraryFileName) {
112         final String abi =
113                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
114 
115         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
116         Runnable crashHandler = new Runnable() {
117             @Override
118             public void run() {
119                 try {
120                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
121                     if (Flags.updateServiceIpcWrapper()) {
122                         WebViewUpdateManager.getInstance().notifyRelroCreationCompleted();
123                     } else {
124                         WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
125                     }
126                 } catch (Exception e) {
127                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
128                 }
129             }
130         };
131 
132         try {
133             boolean success = LocalServices.getService(ActivityManagerInternal.class)
134                     .startIsolatedProcess(
135                             RelroFileCreator.class.getName(),
136                             new String[] { packageName, libraryFileName },
137                             "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
138             if (!success) throw new Exception("Failed to start the relro file creator process");
139         } catch (Throwable t) {
140             // Log and discard errors as we must not crash the system server.
141             Slog.wtf(LOGTAG, "error starting relro file creator for abi " + abi, t);
142             crashHandler.run();
143         }
144     }
145 
146     /**
147      * Perform preparations needed to allow loading WebView from an application. This method should
148      * be called whenever we change WebView provider.
149      * @return the number of relro processes started.
150      */
prepareNativeLibraries(@onNull PackageInfo webViewPackageInfo)151     static int prepareNativeLibraries(@NonNull PackageInfo webViewPackageInfo) {
152         // TODO(torne): new way of calculating VM size
153         // updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
154         String libraryFileName = WebViewFactory.getWebViewLibrary(
155                 webViewPackageInfo.applicationInfo);
156         if (libraryFileName == null) {
157             // Can't do anything with no filename, don't spawn any processes.
158             return 0;
159         }
160         return createRelros(webViewPackageInfo.packageName, libraryFileName);
161     }
162 
163     /**
164      * @return the number of relro processes started.
165      */
createRelros(@onNull String packageName, @NonNull String libraryFileName)166     private static int createRelros(@NonNull String packageName, @NonNull String libraryFileName) {
167         if (DEBUG) Log.v(LOGTAG, "creating relro files");
168         int numRelros = 0;
169 
170         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
171             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
172             createRelroFile(false /* is64Bit */, packageName, libraryFileName);
173             numRelros++;
174         }
175 
176         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
177             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
178             createRelroFile(true /* is64Bit */, packageName, libraryFileName);
179             numRelros++;
180         }
181         return numRelros;
182     }
183 
184     /**
185      * Reserve space for the native library to be loaded into.
186      */
reserveAddressSpaceInZygote()187     static void reserveAddressSpaceInZygote() {
188         System.loadLibrary("webviewchromium_loader");
189         long addressSpaceToReserve;
190         if (VMRuntime.getRuntime().is64Bit()) {
191             // On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
192             addressSpaceToReserve = 1 * 1024 * 1024 * 1024;
193         } else if (VMRuntime.getRuntime().vmInstructionSet().equals("arm")) {
194             // On 32-bit the address space is fairly scarce, hence we should keep it to a realistic
195             // number that permits some future growth but doesn't hog space. For ARM we use 130MB
196             // which is roughly what was calculated on older OS versions. The size has been
197             // growing gradually, but a few efforts have offset it back to the size close to the
198             // original.
199             addressSpaceToReserve = 130 * 1024 * 1024;
200         } else {
201             // The number below was obtained for a binary used for x86 emulators, allowing some
202             // natural growth.
203             addressSpaceToReserve = 190 * 1024 * 1024;
204         }
205 
206         sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
207 
208         if (sAddressSpaceReserved) {
209             if (DEBUG) {
210                 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
211             }
212         } else {
213             Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
214         }
215     }
216 
217     /**
218      * Load WebView's native library into the current process.
219      *
220      * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
221      *
222      * @param clazzLoader class loader used to find the linker namespace to load the library into.
223      * @param libraryFileName the filename of the library to load.
224      */
loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName)225     public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
226         if (!sAddressSpaceReserved) {
227             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
228             return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
229         }
230 
231         String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
232                                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
233         int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
234         if (result != WebViewFactory.LIBLOAD_SUCCESS) {
235             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
236         } else if (DEBUG) {
237             Log.v(LOGTAG, "loaded with relro file");
238         }
239         return result;
240     }
241 
nativeReserveAddressSpace(long addressSpaceToReserve)242     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader)243     static native boolean nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader);
nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader)244     static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
245 }
246