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

Commit 46bddf8e authored by Calin Juravle's avatar Calin Juravle
Browse files

Setup dex load reporter for system server

System server may load code at runtime from outside its original
classpath. In order to ensure this code is optimized (verified) we need to
report it to PackageManager which can optimize it during idle-maintenance
windows.

This CL sets up the reporting infrastructure for system server.

Test: atest DexManagerTest
Bug: 148774920
Change-Id: Ibd2ee38857b97bde426ee0a01c60543f1acb8671
parent 072d9fe0
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -9679,6 +9679,20 @@ public class PackageManagerService extends IPackageManager.Stub
    @Override
    public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap,
            String loaderIsa) {
        if (PLATFORM_PACKAGE_NAME.equals(loadingPackageName)
                && Binder.getCallingUid() != Process.SYSTEM_UID) {
            Slog.w(TAG, "Non System Server process reporting dex loads as system server. uid="
                    + Binder.getCallingUid());
            // Do not record dex loads from processes pretending to be system server.
            // Only the system server should be assigned the package "android", so reject calls
            // that don't satisfy the constraint.
            //
            // notifyDexLoad is a PM API callable from the app process. So in theory, apps could
            // craft calls to this API and pretend to be system server. Doing so poses no particular
            // danger for dex load reporting or later dexopt, however it is a sensible check to do
            // in order to verify the expectations.
            return;
        }
        int userId = UserHandle.getCallingUserId();
        ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
        if (ai == null) {
+47 −9
Original line number Diff line number Diff line
@@ -17,13 +17,17 @@
package com.android.server.pm.dex;

import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;

import static java.util.function.Function.identity;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackagePartitions;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -67,13 +71,12 @@ import java.util.zip.ZipEntry;
 */
public class DexManager {
    private static final String TAG = "DexManager";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
    private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
            "pm.dexopt.priv-apps-oob-list";

    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final Context mContext;

    // Maps package name to code locations.
@@ -178,12 +181,14 @@ public class DexManager {
                boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
                        searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;

                if (primaryOrSplit && !isUsedByOtherApps) {
                if (primaryOrSplit && !isUsedByOtherApps
                        && !PLATFORM_PACKAGE_NAME.equals(searchResult.mOwningPackageName)) {
                    // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
                    // do not record it. This case does not bring any new usable information
                    // and can be safely skipped.
                    // Note this is just an optimization that makes things easier to read in the
                    // package-dex-use file since we don't need to pollute it with redundant info.
                    // However, we always record system server packages.
                    continue;
                }

@@ -216,6 +221,23 @@ public class DexManager {
        }
    }

    /**
     * Check if the dexPath belongs to system server.
     * System server can load code from different location, so we cast a wide-net here, and
     * assume that if the paths is on any of the registered system partitions then it can be loaded
     * by system server.
     */
    private boolean isSystemServerDexPathSupportedForOdex(String dexPath) {
        ArrayList<PackagePartitions.SystemPartition> partitions =
                PackagePartitions.getOrderedPartitions(identity());
        for (int i = 0; i < partitions.size(); i++) {
            if (partitions.get(i).containsPath(dexPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Read the dex usage from disk and populate the code cache locations.
     * @param existingPackages a map containing information about what packages
@@ -607,12 +629,6 @@ public class DexManager {
     */
    private DexSearchResult getDexPackage(
            ApplicationInfo loadingAppInfo, String dexPath, int userId) {
        // Ignore framework code.
        // TODO(calin): is there a better way to detect it?
        if (dexPath.startsWith("/system/framework/")) {
            return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
        }

        // First, check if the package which loads the dex file actually owns it.
        // Most of the time this will be true and we can return early.
        PackageCodeLocations loadingPackageCodeLocations =
@@ -635,6 +651,28 @@ public class DexManager {
            }
        }

        // We could not find the owning package amongst regular apps.
        // If the loading package is system server, see if the dex file resides
        // on any of the potentially system server owning location and if so,
        // assuming system server ownership.
        //
        // Note: We don't have any way to detect which code paths are actually
        // owned by system server. We can only assume that such paths are on
        // system partitions.
        if (PLATFORM_PACKAGE_NAME.equals(loadingAppInfo.packageName)) {
            if (isSystemServerDexPathSupportedForOdex(dexPath)) {
                // We record system server dex files as secondary dex files.
                // The reason is that we only record the class loader context for secondary dex
                // files and we expect that all primary apks are loaded with an empty class loader.
                // System server dex files may be loaded in non-empty class loader so we need to
                // keep track of their context.
                return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY);
            } else {
                Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: "
                        + dexPath);
            }
        }

        if (DEBUG) {
            // TODO(calin): Consider checking for /data/data symlink.
            // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.pm.dex;

import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;

import android.content.pm.IPackageManager;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;

import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;

import java.util.Map;

/**
 * Reports dex file use to the package manager on behalf of system server.
 */
public class SystemServerDexLoadReporter implements BaseDexClassLoader.Reporter {
    private static final String TAG = "SystemServerDexLoadReporter";

    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final IPackageManager mPackageManager;

    public SystemServerDexLoadReporter(IPackageManager pm) {
        mPackageManager = pm;
    }

    @Override
    public void report(Map<String, String> classLoaderContextMap) {
        if (DEBUG) {
            Slog.i(TAG, "Reporting "  + classLoaderContextMap);
        }
        if (classLoaderContextMap.isEmpty()) {
            Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap");
            return;
        }

        try {
            mPackageManager.notifyDexLoad(
                    PLATFORM_PACKAGE_NAME,
                    classLoaderContextMap,
                    VMRuntime.getRuntime().vmInstructionSet());
        } catch (RemoteException ignored) {
            // We're in system server, it can't happen.
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -136,6 +136,7 @@ import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.dex.SystemServerDexLoadReporter;
import com.android.server.policy.PermissionPolicyService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.policy.role.LegacyRoleResolutionPolicy;
@@ -171,6 +172,7 @@ import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowManagerGlobalLock;
import com.android.server.wm.WindowManagerService;

import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;

import com.google.android.startop.iorap.IorapForwardingService;
@@ -839,6 +841,11 @@ public final class SystemServer {
            Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
        }

        // Now that the package manager has started, register the dex load reporter to capture any
        // dex files loaded by system server.
        // These dex files will be optimized by the BackgroundDexOptService.
        BaseDexClassLoader.setReporter(new SystemServerDexLoadReporter(mPackageManagerService));

        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
        t.traceEnd();
+25 −0
Original line number Diff line number Diff line
@@ -85,6 +85,9 @@ public class DexManagerTests {
    private TestData mBarUser0UnsupportedClassLoader;
    private TestData mBarUser0DelegateLastClassLoader;

    private TestData mSystemServerJar;
    private TestData mSystemServerJarInvalid;

    private int mUser0;
    private int mUser1;

@@ -108,6 +111,9 @@ public class DexManagerTests {
        mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
                DELEGATE_LAST_CLASS_LOADER_NAME);

        mSystemServerJar = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME);
        mSystemServerJarInvalid = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME);

        mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null,
                mInstaller, mInstallLock);

@@ -587,6 +593,25 @@ public class DexManagerTests {
        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
    }


    @Test
    public void testNotifySystemServerUse() {
        List<String> dexFiles = new ArrayList<String>();
        dexFiles.add("/system/framework/foo");
        notifyDexLoad(mSystemServerJar, dexFiles, mUser0);
        PackageUseInfo pui = getPackageUseInfo(mSystemServerJar);
        assertIsUsedByOtherApps(mSystemServerJar, pui, false);
    }

    @Test
    public void testNotifySystemServerInvalidUse() {
        List<String> dexFiles = new ArrayList<String>();
        dexFiles.add("/data/foo");
        notifyDexLoad(mSystemServerJarInvalid, dexFiles, mUser0);
        assertNoUseInfo(mSystemServerJarInvalid);
        assertNoDclInfo(mSystemServerJarInvalid);
    }

    private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
            List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
            String[] expectedContexts) {