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.annotation.Nullable;
     21 import android.app.ActivityManagerInternal;
     22 import android.content.pm.ApplicationInfo;
     23 import android.content.pm.PackageInfo;
     24 import android.os.Build;
     25 import android.os.Process;
     26 import android.os.RemoteException;
     27 import android.os.SystemProperties;
     28 import android.text.TextUtils;
     29 import android.util.Log;
     30 
     31 import com.android.internal.annotations.VisibleForTesting;
     32 import com.android.server.LocalServices;
     33 
     34 import dalvik.system.VMRuntime;
     35 
     36 import java.io.File;
     37 import java.io.IOException;
     38 import java.util.Arrays;
     39 import java.util.zip.ZipEntry;
     40 import java.util.zip.ZipFile;
     41 
     42 /**
     43  * @hide
     44  */
     45 @VisibleForTesting
     46 public class WebViewLibraryLoader {
     47     private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
     48 
     49     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
     50             "/data/misc/shared_relro/libwebviewchromium32.relro";
     51     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
     52             "/data/misc/shared_relro/libwebviewchromium64.relro";
     53     private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
     54 
     55     private static final boolean DEBUG = false;
     56 
     57     private static boolean sAddressSpaceReserved = false;
     58 
     59     /**
     60      * Private class for running the actual relro creation in an unprivileged child process.
     61      * RelroFileCreator is a static class (without access to the outer class) to avoid accidentally
     62      * using any static members from the outer class. Those members will in reality differ between
     63      * the child process in which RelroFileCreator operates, and the app process in which the static
     64      * members of this class are used.
     65      */
     66     private static class RelroFileCreator {
     67         // Called in an unprivileged child process to create the relro file.
     68         public static void main(String[] args) {
     69             boolean result = false;
     70             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
     71             try {
     72                 if (args.length != 1 || args[0] == null) {
     73                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
     74                     return;
     75                 }
     76                 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), lib: " + args[0]);
     77                 if (!sAddressSpaceReserved) {
     78                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
     79                     return;
     80                 }
     81                 result = nativeCreateRelroFile(args[0] /* path */,
     82                                                is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
     83                                                          CHROMIUM_WEBVIEW_NATIVE_RELRO_32);
     84                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
     85             } finally {
     86                 // We must do our best to always notify the update service, even if something fails.
     87                 try {
     88                     WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
     89                 } catch (RemoteException e) {
     90                     Log.e(LOGTAG, "error notifying update service", e);
     91                 }
     92 
     93                 if (!result) Log.e(LOGTAG, "failed to create relro file");
     94 
     95                 // Must explicitly exit or else this process will just sit around after we return.
     96                 System.exit(0);
     97             }
     98         }
     99     }
    100 
    101     /**
    102      * Create a single relro file by invoking an isolated process that to do the actual work.
    103      */
    104     static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
    105         final String abi =
    106                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
    107 
    108         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
    109         Runnable crashHandler = new Runnable() {
    110             @Override
    111             public void run() {
    112                 try {
    113                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
    114                     WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
    115                 } catch (RemoteException e) {
    116                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
    117                 }
    118             }
    119         };
    120 
    121         try {
    122             if (nativeLib == null || nativeLib.path == null) {
    123                 throw new IllegalArgumentException(
    124                         "Native library paths to the WebView RelRo process must not be null!");
    125             }
    126             boolean success = LocalServices.getService(ActivityManagerInternal.class)
    127                     .startIsolatedProcess(
    128                             RelroFileCreator.class.getName(), new String[] { nativeLib.path },
    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(PackageInfo webviewPackageInfo)
    144             throws WebViewFactory.MissingWebViewPackageException {
    145         WebViewNativeLibrary nativeLib32bit =
    146                 getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
    147         WebViewNativeLibrary nativeLib64bit =
    148                 getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
    149         updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
    150 
    151         return createRelros(nativeLib32bit, nativeLib64bit);
    152     }
    153 
    154     /**
    155      * @return the number of relro processes started.
    156      */
    157     private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit,
    158             @Nullable WebViewNativeLibrary nativeLib64bit) {
    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 (nativeLib32bit == null) {
    164                 Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
    165             } else {
    166                 if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
    167                 createRelroFile(false /* is64Bit */, nativeLib32bit);
    168                 numRelros++;
    169             }
    170         }
    171 
    172         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
    173             if (nativeLib64bit == null) {
    174                 Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
    175             } else {
    176                 if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
    177                 createRelroFile(true /* is64Bit */, nativeLib64bit);
    178                 numRelros++;
    179             }
    180         }
    181         return numRelros;
    182     }
    183 
    184     /**
    185      *
    186      * @return the native WebView libraries in the new WebView APK.
    187      */
    188     private static void updateWebViewZygoteVmSize(
    189             @Nullable WebViewNativeLibrary nativeLib32bit,
    190             @Nullable WebViewNativeLibrary nativeLib64bit)
    191             throws WebViewFactory.MissingWebViewPackageException {
    192         // Find the native libraries of the new WebView package, to change the size of the
    193         // memory region in the Zygote reserved for the library.
    194         long newVmSize = 0L;
    195 
    196         if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
    197         if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);
    198 
    199         if (DEBUG) {
    200             Log.v(LOGTAG, "Based on library size, need " + newVmSize
    201                     + " bytes of address space.");
    202         }
    203         // The required memory can be larger than the file on disk (due to .bss), and an
    204         // upgraded version of the library will likely be larger, so always attempt to
    205         // reserve twice as much as we think to allow for the library to grow during this
    206         // boot cycle.
    207         newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
    208         Log.d(LOGTAG, "Setting new address space to " + newVmSize);
    209         setWebViewZygoteVmSize(newVmSize);
    210     }
    211 
    212     /**
    213      * Reserve space for the native library to be loaded into.
    214      */
    215     static void reserveAddressSpaceInZygote() {
    216         System.loadLibrary("webviewchromium_loader");
    217         long addressSpaceToReserve =
    218                 SystemProperties.getLong(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
    219                 CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
    220         sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
    221 
    222         if (sAddressSpaceReserved) {
    223             if (DEBUG) {
    224                 Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
    225             }
    226         } else {
    227             Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
    228         }
    229     }
    230 
    231     /**
    232      * Load WebView's native library into the current process.
    233      *
    234      * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
    235      *
    236      * @param clazzLoader class loader used to find the linker namespace to load the library into.
    237      * @param libraryFileName the filename of the library to load.
    238      */
    239     public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
    240         if (!sAddressSpaceReserved) {
    241             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
    242             return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
    243         }
    244 
    245         String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
    246                                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
    247         int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
    248         if (result != WebViewFactory.LIBLOAD_SUCCESS) {
    249             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
    250         } else if (DEBUG) {
    251             Log.v(LOGTAG, "loaded with relro file");
    252         }
    253         return result;
    254     }
    255 
    256     /**
    257      * Fetch WebView's native library paths from {@param packageInfo}.
    258      * @hide
    259      */
    260     @Nullable
    261     @VisibleForTesting
    262     public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
    263             boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
    264         ApplicationInfo ai = packageInfo.applicationInfo;
    265         final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
    266 
    267         String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);
    268 
    269         WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
    270                 is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
    271 
    272         if (DEBUG) {
    273             Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
    274         }
    275         return lib;
    276     }
    277 
    278     /**
    279      * @return the directory of the native WebView library with bitness {@param is64bit}.
    280      * @hide
    281      */
    282     @VisibleForTesting
    283     public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
    284         // Primary arch has the same bitness as the library we are looking for.
    285         if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;
    286 
    287         // Secondary arch has the same bitness as the library we are looking for.
    288         if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
    289             return ai.secondaryNativeLibraryDir;
    290         }
    291 
    292         return "";
    293     }
    294 
    295     /**
    296      * @return an object describing a native WebView library given the directory path of that
    297      * library, or null if the library couldn't be found.
    298      */
    299     @Nullable
    300     private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
    301             String nativeLibFileName, String[] abiList, String libDirectory)
    302             throws WebViewFactory.MissingWebViewPackageException {
    303         if (TextUtils.isEmpty(libDirectory)) return null;
    304         String libPath = libDirectory + "/" + nativeLibFileName;
    305         File f = new File(libPath);
    306         if (f.exists()) {
    307             return new WebViewNativeLibrary(libPath, f.length());
    308         } else {
    309             return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
    310         }
    311     }
    312 
    313     /**
    314      * @hide
    315      */
    316     @VisibleForTesting
    317     public static class WebViewNativeLibrary {
    318         public final String path;
    319         public final long size;
    320 
    321         WebViewNativeLibrary(String path, long size) {
    322             this.path = path;
    323             this.size = size;
    324         }
    325     }
    326 
    327     private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
    328                                                            String[] abiList,
    329                                                            String nativeLibFileName)
    330             throws WebViewFactory.MissingWebViewPackageException {
    331         // Search the APK for a native library conforming to a listed ABI.
    332         try (ZipFile z = new ZipFile(apkPath)) {
    333             for (String abi : abiList) {
    334                 final String entry = "lib/" + abi + "/" + nativeLibFileName;
    335                 ZipEntry e = z.getEntry(entry);
    336                 if (e != null && e.getMethod() == ZipEntry.STORED) {
    337                     // Return a path formatted for dlopen() load from APK.
    338                     return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
    339                 }
    340             }
    341         } catch (IOException e) {
    342             throw new WebViewFactory.MissingWebViewPackageException(e);
    343         }
    344         return null;
    345     }
    346 
    347     /**
    348      * Sets the size of the memory area in which to store the relro section.
    349      */
    350     private static void setWebViewZygoteVmSize(long vmSize) {
    351         SystemProperties.set(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
    352                 Long.toString(vmSize));
    353     }
    354 
    355     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
    356     static native boolean nativeCreateRelroFile(String lib, String relro);
    357     static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
    358 }
    359