Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2012 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.SystemApi;
     20 import android.app.ActivityManagerInternal;
     21 import android.app.AppGlobals;
     22 import android.app.Application;
     23 import android.content.Context;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.PackageInfo;
     26 import android.content.pm.PackageManager;
     27 import android.os.Build;
     28 import android.os.Process;
     29 import android.os.RemoteException;
     30 import android.os.ServiceManager;
     31 import android.os.StrictMode;
     32 import android.os.SystemProperties;
     33 import android.os.Trace;
     34 import android.text.TextUtils;
     35 import android.util.AndroidRuntimeException;
     36 import android.util.Log;
     37 
     38 import com.android.server.LocalServices;
     39 
     40 import dalvik.system.VMRuntime;
     41 
     42 import java.io.File;
     43 import java.util.Arrays;
     44 
     45 /**
     46  * Top level factory, used creating all the main WebView implementation classes.
     47  *
     48  * @hide
     49  */
     50 @SystemApi
     51 public final class WebViewFactory {
     52 
     53     private static final String CHROMIUM_WEBVIEW_FACTORY =
     54             "com.android.webview.chromium.WebViewChromiumFactoryProvider";
     55 
     56     private static final String NULL_WEBVIEW_FACTORY =
     57             "com.android.webview.nullwebview.NullWebViewFactoryProvider";
     58 
     59     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
     60             "/data/misc/shared_relro/libwebviewchromium32.relro";
     61     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
     62             "/data/misc/shared_relro/libwebviewchromium64.relro";
     63 
     64     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
     65             "persist.sys.webview.vmsize";
     66     private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
     67 
     68     private static final String LOGTAG = "WebViewFactory";
     69 
     70     private static final boolean DEBUG = false;
     71 
     72     // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
     73     // same provider.
     74     private static WebViewFactoryProvider sProviderInstance;
     75     private static final Object sProviderLock = new Object();
     76     private static boolean sAddressSpaceReserved = false;
     77     private static PackageInfo sPackageInfo;
     78 
     79     public static String getWebViewPackageName() {
     80         return AppGlobals.getInitialApplication().getString(
     81                 com.android.internal.R.string.config_webViewPackageName);
     82     }
     83 
     84     public static PackageInfo getLoadedPackageInfo() {
     85         return sPackageInfo;
     86     }
     87 
     88     static WebViewFactoryProvider getProvider() {
     89         synchronized (sProviderLock) {
     90             // For now the main purpose of this function (and the factory abstraction) is to keep
     91             // us honest and minimize usage of WebView internals when binding the proxy.
     92             if (sProviderInstance != null) return sProviderInstance;
     93 
     94             final int uid = android.os.Process.myUid();
     95             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
     96                 throw new UnsupportedOperationException(
     97                         "For security reasons, WebView is not allowed in privileged processes");
     98             }
     99 
    100             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
    101             try {
    102                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
    103                 loadNativeLibrary();
    104                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
    105 
    106                 Class<WebViewFactoryProvider> providerClass;
    107                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getFactoryClass()");
    108                 try {
    109                     providerClass = getFactoryClass();
    110                 } catch (ClassNotFoundException e) {
    111                     Log.e(LOGTAG, "error loading provider", e);
    112                     throw new AndroidRuntimeException(e);
    113                 } finally {
    114                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
    115                 }
    116 
    117                 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
    118                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
    119                 try {
    120                     try {
    121                         sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
    122                                 .newInstance(new WebViewDelegate());
    123                     } catch (Exception e) {
    124                         sProviderInstance = providerClass.newInstance();
    125                     }
    126                     if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
    127                     return sProviderInstance;
    128                 } catch (Exception e) {
    129                     Log.e(LOGTAG, "error instantiating provider", e);
    130                     throw new AndroidRuntimeException(e);
    131                 } finally {
    132                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
    133                     StrictMode.setThreadPolicy(oldPolicy);
    134                 }
    135             } finally {
    136                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
    137             }
    138         }
    139     }
    140 
    141     private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
    142         Application initialApplication = AppGlobals.getInitialApplication();
    143         try {
    144             // First fetch the package info so we can log the webview package version.
    145             String packageName = getWebViewPackageName();
    146             sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
    147             Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName +
    148                           " (code " + sPackageInfo.versionCode + ")");
    149 
    150             // Construct a package context to load the Java code into the current app.
    151             Context webViewContext = initialApplication.createPackageContext(packageName,
    152                     Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
    153             initialApplication.getAssets().addAssetPath(
    154                     webViewContext.getApplicationInfo().sourceDir);
    155             ClassLoader clazzLoader = webViewContext.getClassLoader();
    156             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
    157             try {
    158                 return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
    159                                                                      clazzLoader);
    160             } finally {
    161                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
    162             }
    163         } catch (PackageManager.NameNotFoundException e) {
    164             // If the package doesn't exist, then try loading the null WebView instead.
    165             // If that succeeds, then this is a device without WebView support; if it fails then
    166             // swallow the failure, complain that the real WebView is missing and rethrow the
    167             // original exception.
    168             try {
    169                 return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
    170             } catch (ClassNotFoundException e2) {
    171                 // Ignore.
    172             }
    173             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
    174             throw new AndroidRuntimeException(e);
    175         }
    176     }
    177 
    178     /**
    179      * Perform any WebView loading preparations that must happen in the zygote.
    180      * Currently, this means allocating address space to load the real JNI library later.
    181      */
    182     public static void prepareWebViewInZygote() {
    183         try {
    184             System.loadLibrary("webviewchromium_loader");
    185             long addressSpaceToReserve =
    186                     SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
    187                     CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
    188             sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
    189 
    190             if (sAddressSpaceReserved) {
    191                 if (DEBUG) {
    192                     Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
    193                 }
    194             } else {
    195                 Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
    196                         " bytes of address space failed");
    197             }
    198         } catch (Throwable t) {
    199             // Log and discard errors at this stage as we must not crash the zygote.
    200             Log.e(LOGTAG, "error preparing native loader", t);
    201         }
    202     }
    203 
    204     /**
    205      * Perform any WebView loading preparations that must happen at boot from the system server,
    206      * after the package manager has started or after an update to the webview is installed.
    207      * This must be called in the system server.
    208      * Currently, this means spawning the child processes which will create the relro files.
    209      */
    210     public static void prepareWebViewInSystemServer() {
    211         String[] nativePaths = null;
    212         try {
    213             nativePaths = getWebViewNativeLibraryPaths();
    214         } catch (Throwable t) {
    215             // Log and discard errors at this stage as we must not crash the system server.
    216             Log.e(LOGTAG, "error preparing webview native library", t);
    217         }
    218         prepareWebViewInSystemServer(nativePaths);
    219     }
    220 
    221     private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
    222         if (DEBUG) Log.v(LOGTAG, "creating relro files");
    223 
    224         // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
    225         // unexpected values will be handled there to ensure that we trigger notifying any process
    226         // waiting on relreo creation.
    227         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
    228             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
    229             createRelroFile(false /* is64Bit */, nativeLibraryPaths);
    230         }
    231 
    232         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
    233             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
    234             createRelroFile(true /* is64Bit */, nativeLibraryPaths);
    235         }
    236     }
    237 
    238     public static void onWebViewUpdateInstalled() {
    239         String[] nativeLibs = null;
    240         try {
    241             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
    242             if (nativeLibs != null) {
    243                 long newVmSize = 0L;
    244 
    245                 for (String path : nativeLibs) {
    246                     if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
    247                     if (path == null) continue;
    248                     File f = new File(path);
    249                     if (f.exists()) {
    250                         long length = f.length();
    251                         if (length > newVmSize) {
    252                             newVmSize = length;
    253                         }
    254                     }
    255                 }
    256 
    257                 if (DEBUG) {
    258                     Log.v(LOGTAG, "Based on library size, need " + newVmSize +
    259                             " bytes of address space.");
    260                 }
    261                 // The required memory can be larger than the file on disk (due to .bss), and an
    262                 // upgraded version of the library will likely be larger, so always attempt to
    263                 // reserve twice as much as we think to allow for the library to grow during this
    264                 // boot cycle.
    265                 newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
    266                 Log.d(LOGTAG, "Setting new address space to " + newVmSize);
    267                 SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
    268                         Long.toString(newVmSize));
    269             }
    270         } catch (Throwable t) {
    271             // Log and discard errors at this stage as we must not crash the system server.
    272             Log.e(LOGTAG, "error preparing webview native library", t);
    273         }
    274         prepareWebViewInSystemServer(nativeLibs);
    275     }
    276 
    277     private static String[] getWebViewNativeLibraryPaths()
    278             throws PackageManager.NameNotFoundException {
    279         final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";
    280 
    281         PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
    282         ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);
    283 
    284         String path32;
    285         String path64;
    286         boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
    287         if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
    288             // Multi-arch case.
    289             if (primaryArchIs64bit) {
    290                 // Primary arch: 64-bit, secondary: 32-bit.
    291                 path64 = ai.nativeLibraryDir;
    292                 path32 = ai.secondaryNativeLibraryDir;
    293             } else {
    294                 // Primary arch: 32-bit, secondary: 64-bit.
    295                 path64 = ai.secondaryNativeLibraryDir;
    296                 path32 = ai.nativeLibraryDir;
    297             }
    298         } else if (primaryArchIs64bit) {
    299             // Single-arch 64-bit.
    300             path64 = ai.nativeLibraryDir;
    301             path32 = "";
    302         } else {
    303             // Single-arch 32-bit.
    304             path32 = ai.nativeLibraryDir;
    305             path64 = "";
    306         }
    307         if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
    308         if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
    309         return new String[] { path32, path64 };
    310     }
    311 
    312     private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
    313         final String abi =
    314                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
    315 
    316         // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
    317         Runnable crashHandler = new Runnable() {
    318             @Override
    319             public void run() {
    320                 try {
    321                     Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
    322                     getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
    323                 } catch (RemoteException e) {
    324                     Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
    325                 }
    326             }
    327         };
    328 
    329         try {
    330             if (nativeLibraryPaths == null
    331                     || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
    332                 throw new IllegalArgumentException(
    333                         "Native library paths to the WebView RelRo process must not be null!");
    334             }
    335             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
    336                     RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
    337                     Process.SHARED_RELRO_UID, crashHandler);
    338             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
    339         } catch (Throwable t) {
    340             // Log and discard errors as we must not crash the system server.
    341             Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
    342             crashHandler.run();
    343         }
    344     }
    345 
    346     private static class RelroFileCreator {
    347         // Called in an unprivileged child process to create the relro file.
    348         public static void main(String[] args) {
    349             boolean result = false;
    350             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
    351             try{
    352                 if (args.length != 2 || args[0] == null || args[1] == null) {
    353                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
    354                     return;
    355                 }
    356                 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
    357                         " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
    358                 if (!sAddressSpaceReserved) {
    359                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
    360                     return;
    361                 }
    362                 result = nativeCreateRelroFile(args[0] /* path32 */,
    363                                                args[1] /* path64 */,
    364                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
    365                                                CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
    366                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
    367             } finally {
    368                 // We must do our best to always notify the update service, even if something fails.
    369                 try {
    370                     getUpdateService().notifyRelroCreationCompleted(is64Bit, result);
    371                 } catch (RemoteException e) {
    372                     Log.e(LOGTAG, "error notifying update service", e);
    373                 }
    374 
    375                 if (!result) Log.e(LOGTAG, "failed to create relro file");
    376 
    377                 // Must explicitly exit or else this process will just sit around after we return.
    378                 System.exit(0);
    379             }
    380         }
    381     }
    382 
    383     private static void loadNativeLibrary() {
    384         if (!sAddressSpaceReserved) {
    385             Log.e(LOGTAG, "can't load with relro file; address space not reserved");
    386             return;
    387         }
    388 
    389         try {
    390             getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
    391         } catch (RemoteException e) {
    392             Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
    393             return;
    394         }
    395 
    396         try {
    397             String[] args = getWebViewNativeLibraryPaths();
    398             boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
    399                                                      args[1] /* path64 */,
    400                                                      CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
    401                                                      CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
    402             if (!result) {
    403                 Log.w(LOGTAG, "failed to load with relro file, proceeding without");
    404             } else if (DEBUG) {
    405                 Log.v(LOGTAG, "loaded with relro file");
    406             }
    407         } catch (PackageManager.NameNotFoundException e) {
    408             Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
    409         }
    410     }
    411 
    412     private static IWebViewUpdateService getUpdateService() {
    413         return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
    414     }
    415 
    416     private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
    417     private static native boolean nativeCreateRelroFile(String lib32, String lib64,
    418                                                         String relro32, String relro64);
    419     private static native boolean nativeLoadWithRelroFile(String lib32, String lib64,
    420                                                           String relro32, String relro64);
    421 }
    422