Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94da7781 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Simplify WebView RELRO creation and handle more cases."

parents da2766f8 5abeed5e
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -6785,7 +6785,6 @@ package android.webkit {
    method public static android.content.pm.PackageInfo getLoadedPackageInfo();
    method public static int loadWebViewNativeLibraryFromPackage(java.lang.String, java.lang.ClassLoader);
    method public static void prepareWebViewInZygote();
    field public static final java.lang.String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY = "persist.sys.webview.vmsize";
    field public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2; // 0x2
    field public static final int LIBLOAD_FAILED_JNI_CALL = 7; // 0x7
    field public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
+0 −3
Original line number Diff line number Diff line
@@ -51,9 +51,6 @@ public final class WebViewFactory {

    private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";

    public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
            "persist.sys.webview.vmsize";

    private static final String LOGTAG = "WebViewFactory";

    private static final boolean DEBUG = false;
+43 −175
Original line number Diff line number Diff line
@@ -17,15 +17,14 @@
package android.webkit;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.content.pm.ApplicationInfo;
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.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
@@ -33,11 +32,7 @@ import com.android.server.LocalServices;

import dalvik.system.VMRuntime;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @hide
@@ -50,7 +45,6 @@ public class WebViewLibraryLoader {
            "/data/misc/shared_relro/libwebviewchromium32.relro";
    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
            "/data/misc/shared_relro/libwebviewchromium64.relro";
    private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;

    private static final boolean DEBUG = false;

@@ -69,18 +63,26 @@ public class WebViewLibraryLoader {
            boolean result = false;
            boolean is64Bit = VMRuntime.getRuntime().is64Bit();
            try {
                if (args.length != 1 || args[0] == null) {
                if (args.length != 2 || args[0] == null || args[1] == null) {
                    Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
                    return;
                }
                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), lib: " + args[0]);
                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;
                }
                result = nativeCreateRelroFile(args[0] /* path */,
                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);
                                                         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.
@@ -101,7 +103,8 @@ public class WebViewLibraryLoader {
    /**
     * Create a single relro file by invoking an isolated process that to do the actual work.
     */
    static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
    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];

@@ -119,13 +122,10 @@ public class WebViewLibraryLoader {
        };

        try {
            if (nativeLib == null || nativeLib.path == null) {
                throw new IllegalArgumentException(
                        "Native library paths to the WebView RelRo process must not be null!");
            }
            boolean success = LocalServices.getService(ActivityManagerInternal.class)
                    .startIsolatedProcess(
                            RelroFileCreator.class.getName(), new String[] { nativeLib.path },
                            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) {
@@ -140,83 +140,50 @@ public class WebViewLibraryLoader {
     * be called whenever we change WebView provider.
     * @return the number of relro processes started.
     */
    static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
            throws WebViewFactory.MissingWebViewPackageException {
        WebViewNativeLibrary nativeLib32bit =
                getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
        WebViewNativeLibrary nativeLib64bit =
                getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
        updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);

        return createRelros(nativeLib32bit, nativeLib64bit);
    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(@Nullable WebViewNativeLibrary nativeLib32bit,
            @Nullable WebViewNativeLibrary nativeLib64bit) {
    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 (nativeLib32bit == null) {
                Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
            } else {
            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
                createRelroFile(false /* is64Bit */, nativeLib32bit);
            createRelroFile(false /* is64Bit */, packageName, libraryFileName);
            numRelros++;
        }
        }

        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
            if (nativeLib64bit == null) {
                Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
            } else {
            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
                createRelroFile(true /* is64Bit */, nativeLib64bit);
            createRelroFile(true /* is64Bit */, packageName, libraryFileName);
            numRelros++;
        }
        }
        return numRelros;
    }

    /**
     *
     * @return the native WebView libraries in the new WebView APK.
     */
    private static void updateWebViewZygoteVmSize(
            @Nullable WebViewNativeLibrary nativeLib32bit,
            @Nullable WebViewNativeLibrary nativeLib64bit)
            throws WebViewFactory.MissingWebViewPackageException {
        // Find the native libraries of the new WebView package, to change the size of the
        // memory region in the Zygote reserved for the library.
        long newVmSize = 0L;

        if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
        if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);

        if (DEBUG) {
            Log.v(LOGTAG, "Based on library size, need " + newVmSize
                    + " bytes of address space.");
        }
        // The required memory can be larger than the file on disk (due to .bss), and an
        // upgraded version of the library will likely be larger, so always attempt to
        // reserve twice as much as we think to allow for the library to grow during this
        // boot cycle.
        newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
        Log.d(LOGTAG, "Setting new address space to " + newVmSize);
        setWebViewZygoteVmSize(newVmSize);
    }

    /**
     * Reserve space for the native library to be loaded into.
     */
    static void reserveAddressSpaceInZygote() {
        System.loadLibrary("webviewchromium_loader");
        long addressSpaceToReserve =
                SystemProperties.getLong(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
                CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
        boolean is64Bit = VMRuntime.getRuntime().is64Bit();
        // On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
        // On 32-bit it's fairly scarce and we should keep it to a realistic number that
        // permits some future growth but doesn't hog space: we use 100MB which is more than 2x
        // the current requirement.
        long addressSpaceToReserve = is64Bit ? 1 * 1024 * 1024 * 1024 : 100 * 1024 * 1024;
        sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);

        if (sAddressSpaceReserved) {
@@ -253,106 +220,7 @@ public class WebViewLibraryLoader {
        return result;
    }

    /**
     * Fetch WebView's native library paths from {@param packageInfo}.
     * @hide
     */
    @Nullable
    @VisibleForTesting
    public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
            boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
        ApplicationInfo ai = packageInfo.applicationInfo;
        final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);

        String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);

        WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
                is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);

        if (DEBUG) {
            Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
        }
        return lib;
    }

    /**
     * @return the directory of the native WebView library with bitness {@param is64bit}.
     * @hide
     */
    @VisibleForTesting
    public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
        // Primary arch has the same bitness as the library we are looking for.
        if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;

        // Secondary arch has the same bitness as the library we are looking for.
        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
            return ai.secondaryNativeLibraryDir;
        }

        return "";
    }

    /**
     * @return an object describing a native WebView library given the directory path of that
     * library, or null if the library couldn't be found.
     */
    @Nullable
    private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
            String nativeLibFileName, String[] abiList, String libDirectory)
            throws WebViewFactory.MissingWebViewPackageException {
        if (TextUtils.isEmpty(libDirectory)) return null;
        String libPath = libDirectory + "/" + nativeLibFileName;
        File f = new File(libPath);
        if (f.exists()) {
            return new WebViewNativeLibrary(libPath, f.length());
        } else {
            return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
        }
    }

    /**
     * @hide
     */
    @VisibleForTesting
    public static class WebViewNativeLibrary {
        public final String path;
        public final long size;

        WebViewNativeLibrary(String path, long size) {
            this.path = path;
            this.size = size;
        }
    }

    private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
                                                           String[] abiList,
                                                           String nativeLibFileName)
            throws WebViewFactory.MissingWebViewPackageException {
        // Search the APK for a native library conforming to a listed ABI.
        try (ZipFile z = new ZipFile(apkPath)) {
            for (String abi : abiList) {
                final String entry = "lib/" + abi + "/" + nativeLibFileName;
                ZipEntry e = z.getEntry(entry);
                if (e != null && e.getMethod() == ZipEntry.STORED) {
                    // Return a path formatted for dlopen() load from APK.
                    return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
                }
            }
        } catch (IOException e) {
            throw new WebViewFactory.MissingWebViewPackageException(e);
        }
        return null;
    }

    /**
     * Sets the size of the memory area in which to store the relro section.
     */
    private static void setWebViewZygoteVmSize(long vmSize) {
        SystemProperties.set(WebViewFactory.CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
                Long.toString(vmSize));
    }

    static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
    static native boolean nativeCreateRelroFile(String lib, String relro);
    static native boolean nativeCreateRelroFile(String lib, String relro, ClassLoader clazzLoader);
    static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
}

core/tests/webkit/Android.mk

deleted100644 → 0
+0 −45
Original line number Diff line number Diff line
# 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.

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests

LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)


# Include all test java files.
LOCAL_SRC_FILES := \
	$(call all-java-files-under, unit_tests_src)

LOCAL_JAVA_LIBRARIES := android.test.runner

LOCAL_STATIC_JAVA_LIBRARIES := \
	android-support-test

LOCAL_PACKAGE_NAME := WebViewLoadingTests
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform

LOCAL_COMPATIBILITY_SUITE := device-tests

LOCAL_REQUIRED_MODULES := \
	WebViewLoadingOnDiskTestApk \
	WebViewLoadingFromApkTestApk

include $(BUILD_PACKAGE)

include $(call all-makefiles-under,$(LOCAL_PATH))
+0 −30
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.webkit.tests"
          android:sharedUserId="android.uid.system">

    <application>
        <uses-library android:name="android.test.runner" />
    </application>

    <instrumentation
            android:name="android.support.test.runner.AndroidJUnitRunner"
            android:targetPackage="com.android.webkit.tests"
            android:label="Frameworks WebView Loader Tests" />

</manifest>
Loading