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.os.RemoteException;
28 import android.util.Log;
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                     WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
91                 } catch (RemoteException e) {
92                     Log.e(LOGTAG, "error notifying update service", e);
93                 }
94 
95                 if (!result) Log.e(LOGTAG, "failed to create relro file");
96 
97                 // Must explicitly exit or else this process will just sit around after we return.
98                 System.exit(0);
99             }
100         }
101     }
102 
103     /**
104      * Create a single relro file by invoking an isolated process that to do the actual work.
105      */
createRelroFile(final boolean is64Bit, @NonNull String packageName, @NonNull String libraryFileName)106     static void createRelroFile(final boolean is64Bit, @NonNull String packageName,
107             @NonNull String libraryFileName) {
108         final String abi =
109                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
110 
111         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
112         Runnable crashHandler = new Runnable() {
113             @Override
114             public void run() {
115                 try {
116                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
117                     WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
118                 } catch (RemoteException e) {
119                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
120                 }
121             }
122         };
123 
124         try {
125             boolean success = LocalServices.getService(ActivityManagerInternal.class)
126                     .startIsolatedProcess(
127                             RelroFileCreator.class.getName(),
128                             new String[] { packageName, libraryFileName },
129                             "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
130             if (!success) throw new Exception("Failed to start the relro file creator process");
131         } 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 
138     /**
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      */
prepareNativeLibraries(@onNull PackageInfo webViewPackageInfo)143     static int prepareNativeLibraries(@NonNull PackageInfo webViewPackageInfo) {
144         // TODO(torne): new way of calculating VM size
145         // updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
146         String libraryFileName = WebViewFactory.getWebViewLibrary(
147                 webViewPackageInfo.applicationInfo);
148         if (libraryFileName == null) {
149             // Can't do anything with no filename, don't spawn any processes.
150             return 0;
151         }
152         return createRelros(webViewPackageInfo.packageName, libraryFileName);
153     }
154 
155     /**
156      * @return the number of relro processes started.
157      */
createRelros(@onNull String packageName, @NonNull String libraryFileName)158     private static int createRelros(@NonNull String packageName, @NonNull String libraryFileName) {
159         if (DEBUG) Log.v(LOGTAG, "creating relro files");
160         int numRelros = 0;
161 
162         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
163             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
164             createRelroFile(false /* is64Bit */, packageName, libraryFileName);
165             numRelros++;
166         }
167 
168         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
169             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
170             createRelroFile(true /* is64Bit */, packageName, libraryFileName);
171             numRelros++;
172         }
173         return numRelros;
174     }
175 
176     /**
177      * Reserve space for the native library to be loaded into.
178      */
reserveAddressSpaceInZygote()179     static void reserveAddressSpaceInZygote() {
180         System.loadLibrary("webviewchromium_loader");
181         long addressSpaceToReserve;
182         if (VMRuntime.getRuntime().is64Bit()) {
183             // On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
184             addressSpaceToReserve = 1 * 1024 * 1024 * 1024;
185         } else if (VMRuntime.getRuntime().vmInstructionSet().equals("arm")) {
186             // On 32-bit the address space is fairly scarce, hence we should keep it to a realistic
187             // number that permits some future growth but doesn't hog space. For ARM we use 130MB
188             // which is roughly what was calculated on older OS versions. The size has been
189             // growing gradually, but a few efforts have offset it back to the size close to the
190             // original.
191             addressSpaceToReserve = 130 * 1024 * 1024;
192         } else {
193             // The number below was obtained for a binary used for x86 emulators, allowing some
194             // natural growth.
195             addressSpaceToReserve = 190 * 1024 * 1024;
196         }
197 
198         sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
199 
200         if (sAddressSpaceReserved) {
201             if (DEBUG) {
202                 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
203             }
204         } else {
205             Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
206         }
207     }
208 
209     /**
210      * Load WebView's native library into the current process.
211      *
212      * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
213      *
214      * @param clazzLoader class loader used to find the linker namespace to load the library into.
215      * @param libraryFileName the filename of the library to load.
216      */
loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName)217     public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
218         if (!sAddressSpaceReserved) {
219             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
220             return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
221         }
222 
223         String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
224                                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
225         int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
226         if (result != WebViewFactory.LIBLOAD_SUCCESS) {
227             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
228         } else if (DEBUG) {
229             Log.v(LOGTAG, "loaded with relro file");
230         }
231         return result;
232     }
233 
nativeReserveAddressSpace(long addressSpaceToReserve)234     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader)235     static native boolean nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader);
nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader)236     static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
237 }
238