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