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         boolean is64Bit = VMRuntime.getRuntime().is64Bit();
182         // On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
183         // On 32-bit it's fairly scarce and we should keep it to a realistic number that
184         // permits some future growth but doesn't hog space: we use 130MB which is roughly
185         // what was calculated on older OS versions in practice.
186         long addressSpaceToReserve = is64Bit ? 1 * 1024 * 1024 * 1024 : 130 * 1024 * 1024;
187         sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
188 
189         if (sAddressSpaceReserved) {
190             if (DEBUG) {
191                 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
192             }
193         } else {
194             Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
195         }
196     }
197 
198     /**
199      * Load WebView's native library into the current process.
200      *
201      * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
202      *
203      * @param clazzLoader class loader used to find the linker namespace to load the library into.
204      * @param libraryFileName the filename of the library to load.
205      */
loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName)206     public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
207         if (!sAddressSpaceReserved) {
208             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
209             return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
210         }
211 
212         String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
213                                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
214         int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
215         if (result != WebViewFactory.LIBLOAD_SUCCESS) {
216             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
217         } else if (DEBUG) {
218             Log.v(LOGTAG, "loaded with relro file");
219         }
220         return result;
221     }
222 
nativeReserveAddressSpace(long addressSpaceToReserve)223     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader)224     static native boolean nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader);
nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader)225     static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
226 }
227