blob: 91412d7e8631fcafdac2ec117d6c5e4144d3d607 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit;
import android.annotation.NonNull;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import dalvik.system.VMRuntime;
import java.util.Arrays;
/**
* @hide
*/
@VisibleForTesting
public class WebViewLibraryLoader {
private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
"/data/misc/shared_relro/libwebviewchromium32.relro";
private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
"/data/misc/shared_relro/libwebviewchromium64.relro";
private static final boolean DEBUG = false;
private static boolean sAddressSpaceReserved = false;
/**
* Private class for running the actual relro creation in an unprivileged child process.
* RelroFileCreator is a static class (without access to the outer class) to avoid accidentally
* using any static members from the outer class. Those members will in reality differ between
* the child process in which RelroFileCreator operates, and the app process in which the static
* members of this class are used.
*/
private static class RelroFileCreator {
// Called in an unprivileged child process to create the relro file.
public static void main(String[] args) {
boolean result = false;
boolean is64Bit = VMRuntime.getRuntime().is64Bit();
try {
if (args.length != 2 || args[0] == null || args[1] == null) {
Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
return;
}
String packageName = args[0];
String libraryFileName = args[1];
Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), package: "
+ packageName + " library: " + libraryFileName);
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't create relro file; address space not reserved");
return;
}
LoadedApk apk = ActivityThread.currentActivityThread().getPackageInfo(
packageName,
null,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
result = nativeCreateRelroFile(libraryFileName,
is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
apk.getClassLoader());
if (result && DEBUG) Log.v(LOGTAG, "created relro file");
} finally {
// We must do our best to always notify the update service, even if something fails.
try {
WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
} catch (RemoteException e) {
Log.e(LOGTAG, "error notifying update service", e);
}
if (!result) Log.e(LOGTAG, "failed to create relro file");
// Must explicitly exit or else this process will just sit around after we return.
System.exit(0);
}
}
}
/**
* Create a single relro file by invoking an isolated process that to do the actual work.
*/
static void createRelroFile(final boolean is64Bit, @NonNull String packageName,
@NonNull String libraryFileName) {
final String abi =
is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
// crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
Runnable crashHandler = new Runnable() {
@Override
public void run() {
try {
Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
} catch (RemoteException e) {
Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
}
}
};
try {
boolean success = LocalServices.getService(ActivityManagerInternal.class)
.startIsolatedProcess(
RelroFileCreator.class.getName(),
new String[] { packageName, libraryFileName },
"WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
if (!success) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
// Log and discard errors as we must not crash the system server.
Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
crashHandler.run();
}
}
/**
* Perform preparations needed to allow loading WebView from an application. This method should
* be called whenever we change WebView provider.
* @return the number of relro processes started.
*/
static int prepareNativeLibraries(@NonNull PackageInfo webViewPackageInfo) {
// TODO(torne): new way of calculating VM size
// updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
String libraryFileName = WebViewFactory.getWebViewLibrary(
webViewPackageInfo.applicationInfo);
if (libraryFileName == null) {
// Can't do anything with no filename, don't spawn any processes.
return 0;
}
return createRelros(webViewPackageInfo.packageName, libraryFileName);
}
/**
* @return the number of relro processes started.
*/
private static int createRelros(@NonNull String packageName, @NonNull String libraryFileName) {
if (DEBUG) Log.v(LOGTAG, "creating relro files");
int numRelros = 0;
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
createRelroFile(false /* is64Bit */, packageName, libraryFileName);
numRelros++;
}
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
createRelroFile(true /* is64Bit */, packageName, libraryFileName);
numRelros++;
}
return numRelros;
}
/**
* Reserve space for the native library to be loaded into.
*/
static void reserveAddressSpaceInZygote() {
System.loadLibrary("webviewchromium_loader");
long addressSpaceToReserve;
if (VMRuntime.getRuntime().is64Bit()) {
// On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
addressSpaceToReserve = 1 * 1024 * 1024 * 1024;
} else if (VMRuntime.getRuntime().vmInstructionSet().equals("arm")) {
// On 32-bit the address space is fairly scarce, hence we should keep it to a realistic
// number that permits some future growth but doesn't hog space. For ARM we use 130MB
// which is roughly what was calculated on older OS versions. The size has been
// growing gradually, but a few efforts have offset it back to the size close to the
// original.
addressSpaceToReserve = 130 * 1024 * 1024;
} else {
// The number below was obtained for a binary used for x86 emulators, allowing some
// natural growth.
addressSpaceToReserve = 190 * 1024 * 1024;
}
sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
if (sAddressSpaceReserved) {
if (DEBUG) {
Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
}
} else {
Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
}
}
/**
* Load WebView's native library into the current process.
*
* <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
*
* @param clazzLoader class loader used to find the linker namespace to load the library into.
* @param libraryFileName the filename of the library to load.
*/
public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't load with relro file; address space not reserved");
return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
}
String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
if (result != WebViewFactory.LIBLOAD_SUCCESS) {
Log.w(LOGTAG, "failed to load with relro file, proceeding without");
} else if (DEBUG) {
Log.v(LOGTAG, "loaded with relro file");
}
return result;
}
static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
static native boolean nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader);
static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
}