Home | History | Annotate | Download | only in webkit
      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.
     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      */
    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      */
    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      */
    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      */
    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      */
    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 
    223     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
    224     static native boolean nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader);
    225     static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
    226 }
    227