diff --git a/Android.mk b/Android.mk index 9cda341ec364e..7a8907e02bdf7 100644 --- a/Android.mk +++ b/Android.mk @@ -241,6 +241,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/IWindowManager.aidl \ core/java/android/view/IWindowSession.aidl \ core/java/android/view/IWindowSessionCallback.aidl \ + core/java/android/webkit/IWebViewUpdateService.aidl \ core/java/android/speech/IRecognitionListener.aidl \ core/java/android/speech/IRecognitionService.aidl \ core/java/android/speech/tts/ITextToSpeechCallback.aidl \ diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 8caea250d0c81..afac2397760ce 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -130,6 +130,12 @@ public class Process { */ public static final int PACKAGE_INFO_GID = 1032; + /** + * Defines the UID/GID for the shared RELRO file updater process. + * @hide + */ + public static final int SHARED_RELRO_UID = 1037; + /** * Defines the start of a range of UIDs (and GIDs), going from this * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl new file mode 100644 index 0000000000000..a77459b09251d --- /dev/null +++ b/core/java/android/webkit/IWebViewUpdateService.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 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; + +/** + * Private service to wait for the updatable WebView to be ready for use. + * @hide + */ +interface IWebViewUpdateService { + + /** + * Used by the relro file creator to notify the service that it's done. + */ + void notifyRelroCreationCompleted(boolean is64Bit, boolean success); + + /** + * Used by WebViewFactory to block loading of WebView code until + * preparations are complete. + */ + void waitForRelroCreationCompleted(boolean is64Bit); + +} diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 4d55c6a8c4ef3..13be4539d1892 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -16,9 +16,18 @@ package android.webkit; +import android.os.Build; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.StrictMode; import android.util.AndroidRuntimeException; import android.util.Log; +import dalvik.system.VMRuntime; + +import java.io.File; + +import com.android.internal.os.Zygote; /** * Top level factory, used creating all the main WebView implementation classes. @@ -33,6 +42,17 @@ public final class WebViewFactory { private static final String NULL_WEBVIEW_FACTORY = "com.android.webview.nullwebview.NullWebViewFactoryProvider"; + // TODO(torne): we need to use a system property instead of hardcoding the library paths to + // enable it to be changed when a webview update apk is installed. + private static final String CHROMIUM_WEBVIEW_NATIVE_LIB_32 = + "/system/lib/libwebviewchromium.so"; + private static final String CHROMIUM_WEBVIEW_NATIVE_LIB_64 = + "/system/lib64/libwebviewchromium.so"; + 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 String LOGTAG = "WebViewFactory"; private static final boolean DEBUG = false; @@ -41,6 +61,7 @@ public final class WebViewFactory { // same provider. private static WebViewFactoryProvider sProviderInstance; private static final Object sProviderLock = new Object(); + private static boolean sAddressSpaceReserved = false; static WebViewFactoryProvider getProvider() { synchronized (sProviderLock) { @@ -48,6 +69,8 @@ public final class WebViewFactory { // us honest and minimize usage of WebView internals when binding the proxy. if (sProviderInstance != null) return sProviderInstance; + loadNativeLibrary(); + Class providerClass; try { providerClass = getFactoryClass(); @@ -78,4 +101,121 @@ public final class WebViewFactory { return (Class) Class.forName(NULL_WEBVIEW_FACTORY); } } + + /** + * Perform any WebView loading preparations that must happen in the zygote. + * Currently, this means allocating address space to load the real JNI library later. + */ + public static void prepareWebViewInZygote() { + try { + System.loadLibrary("webviewchromium_loader"); + sAddressSpaceReserved = nativeReserveAddressSpace(CHROMIUM_WEBVIEW_NATIVE_LIB_32, + CHROMIUM_WEBVIEW_NATIVE_LIB_64); + if (sAddressSpaceReserved) { + if (DEBUG) Log.v(LOGTAG, "address space reserved"); + } else { + Log.e(LOGTAG, "reserving address space failed"); + } + } catch (Throwable e) { + // Log and discard errors at this stage as we must not crash the zygote. + Log.e(LOGTAG, "error preparing native loader", e); + } + } + + /** + * Perform any WebView loading preparations that must happen at boot from the system server, + * after the package manager has started. + * This must be called in the system server. + * Currently, this means spawning the child processes which will create the relro files. + */ + public static void prepareWebViewInSystemServer() { + if (DEBUG) Log.v(LOGTAG, "creating relro files"); + if (new File(CHROMIUM_WEBVIEW_NATIVE_LIB_64).exists()) { + createRelroFile(Build.SUPPORTED_64_BIT_ABIS[0]); + } + if (new File(CHROMIUM_WEBVIEW_NATIVE_LIB_32).exists()) { + createRelroFile(Build.SUPPORTED_32_BIT_ABIS[0]); + } + } + + private static void createRelroFile(String abi) { + try { + Process.start("android.webkit.WebViewFactory$RelroFileCreator", + "WebViewLoader-" + abi, + Process.SHARED_RELRO_UID, + Process.SHARED_RELRO_UID, + null, + 0, // TODO(torne): do we need to set debug flags? + Zygote.MOUNT_EXTERNAL_NONE, + Build.VERSION.SDK_INT, + null, + abi, + null); + } catch (Throwable e) { + // Log and discard errors as we must not crash the system server. + Log.e(LOGTAG, "error starting relro file creator for abi " + abi, e); + } + } + + private static class RelroFileCreator { + // Called in an unprivileged child process to create the relro file. + public static void main(String[] args) { + if (!sAddressSpaceReserved) { + Log.e(LOGTAG, "can't create relro file; address space not reserved"); + return; + } + boolean result = nativeCreateRelroFile(CHROMIUM_WEBVIEW_NATIVE_LIB_32, + CHROMIUM_WEBVIEW_NATIVE_LIB_64, + CHROMIUM_WEBVIEW_NATIVE_RELRO_32, + CHROMIUM_WEBVIEW_NATIVE_RELRO_64); + if (!result) { + Log.e(LOGTAG, "failed to create relro file"); + } else if (DEBUG) { + Log.v(LOGTAG, "created relro file"); + } + try { + getUpdateService().notifyRelroCreationCompleted(VMRuntime.getRuntime().is64Bit(), + result); + } catch (RemoteException e) { + Log.e(LOGTAG, "error notifying update service", e); + } + + // Must explicitly exit or else this process will just sit around after we return. + System.exit(0); + } + } + + private static void loadNativeLibrary() { + if (!sAddressSpaceReserved) { + Log.e(LOGTAG, "can't load with relro file; address space not reserved"); + return; + } + + try { + getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit()); + } catch (RemoteException e) { + Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e); + return; + } + + boolean result = nativeLoadWithRelroFile(CHROMIUM_WEBVIEW_NATIVE_LIB_32, + CHROMIUM_WEBVIEW_NATIVE_LIB_64, + CHROMIUM_WEBVIEW_NATIVE_RELRO_32, + CHROMIUM_WEBVIEW_NATIVE_RELRO_64); + if (!result) { + Log.w(LOGTAG, "failed to load with relro file, proceeding without"); + } else if (DEBUG) { + Log.v(LOGTAG, "loaded with relro file"); + } + } + + private static IWebViewUpdateService getUpdateService() { + return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); + } + + private static native boolean nativeReserveAddressSpace(String lib32, String lib64); + private static native boolean nativeCreateRelroFile(String lib32, String lib64, + String relro32, String relro64); + private static native boolean nativeLoadWithRelroFile(String lib32, String lib64, + String relro32, String relro64); } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 5ce658b74e040..eea420133f4e4 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -34,6 +34,7 @@ import android.system.Os; import android.system.OsConstants; import android.util.EventLog; import android.util.Log; +import android.webkit.WebViewFactory; import dalvik.system.VMRuntime; @@ -250,6 +251,9 @@ public class ZygoteInit { preloadClasses(); preloadResources(); preloadOpenGL(); + // Ask the WebViewFactory to do any initialization that must run in the zygote process, + // for memory sharing purposes. + WebViewFactory.prepareWebViewInZygote(); Log.d(TAG, "end preload"); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java new file mode 100644 index 0000000000000..e8ae97c29cae4 --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 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 com.android.server.webkit; + +import android.os.Binder; +import android.os.Process; +import android.util.Log; +import android.webkit.IWebViewUpdateService; + +/** + * Private service to wait for the updatable WebView to be ready for use. + * @hide + */ +public class WebViewUpdateService extends IWebViewUpdateService.Stub { + + private static final String TAG = "WebViewUpdateService"; + + private boolean mRelroReady32Bit = false; + private boolean mRelroReady64Bit = false; + + public WebViewUpdateService() { + } + + /** + * The shared relro process calls this to notify us that it's done trying to create a relro + * file. + */ + public void notifyRelroCreationCompleted(boolean is64Bit, boolean success) { + // Verify that the caller is the shared relro process. + if (Binder.getCallingUid() != Process.SHARED_RELRO_UID) { + return; + } + + synchronized (this) { + if (is64Bit) { + mRelroReady64Bit = true; + } else { + mRelroReady32Bit = true; + } + this.notifyAll(); + } + } + + /** + * WebViewFactory calls this to block WebView loading until the relro file is created. + */ + public void waitForRelroCreationCompleted(boolean is64Bit) { + synchronized (this) { + if (is64Bit) { + while (!mRelroReady64Bit) { + try { + this.wait(); + } catch (InterruptedException e) {} + } + } else { + while (!mRelroReady32Bit) { + try { + this.wait(); + } catch (InterruptedException e) {} + } + } + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 8d388273e80a1..a6030cfc7dcab 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -49,6 +49,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.view.WindowManager; +import android.webkit.WebViewFactory; import com.android.internal.R; import com.android.internal.os.BinderInternal; @@ -91,6 +92,7 @@ import com.android.server.tv.TvInputManagerService; import com.android.server.twilight.TwilightService; import com.android.server.usb.UsbService; import com.android.server.wallpaper.WallpaperManagerService; +import com.android.server.webkit.WebViewUpdateService; import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; @@ -408,6 +410,12 @@ public final class SystemServer { Slog.i(TAG, "Reading configuration..."); SystemConfig.getInstance(); + Slog.i(TAG, "WebView Update Service"); + ServiceManager.addService("webviewupdate", new WebViewUpdateService()); + + Slog.i(TAG, "WebViewFactory preparation"); + WebViewFactory.prepareWebViewInSystemServer(); + Slog.i(TAG, "Scheduling Policy"); ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());