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