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

Commit a0023604 authored by Alan Stokes's avatar Alan Stokes
Browse files

Log SHA256 of secondary dex files during reconcile.

Bug: 63927552
Test: Exercised manually. Added unit test for DexManager.

Change-Id: Ic8e9ea4da8eb5c22fbe088a59a406e36bc2eb6fa
parent b92f1eed
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -486,6 +486,16 @@ public class Installer extends SystemService {
        }
    }

    public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
            @Nullable String volumeUuid, int flags) throws InstallerException {
        if (!checkBeforeRemote()) return new byte[0];
        try {
            return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
        } catch (Exception e) {
            throw InstallerException.from(e);
        }
    }

    public void invalidateMounts() throws InstallerException {
        if (!checkBeforeRemote()) return;
        try {
+5 −1
Original line number Diff line number Diff line
@@ -293,6 +293,7 @@ import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.DexLogger;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
@@ -2380,7 +2381,10 @@ public class PackageManagerService extends IPackageManager.Stub
        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                "*dexopt*");
        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
        DexManager.Listener dexManagerListener = DexLogger.getListener(this,
                installer, mInstallLock);
        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
                dexManagerListener);
        mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
        mOnPermissionChangeListeners = new OnPermissionChangeListeners(
+116 −0
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
 */

package com.android.server.pm.dex;

import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.os.RemoteException;

import android.util.ArraySet;
import android.util.ByteStringUtils;
import android.util.EventLog;
import android.util.PackageUtils;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;

import java.io.File;
import java.util.Set;

import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;

/**
 * This class is responsible for logging data about secondary dex files.
 * The data logged includes hashes of the name and content of each file.
 */
public class DexLogger implements DexManager.Listener {
    private static final String TAG = "DexLogger";

    // Event log tag & subtag used for SafetyNet logging of dynamic
    // code loading (DCL) - see b/63927552.
    private static final int SNET_TAG = 0x534e4554;
    private static final String DCL_SUBTAG = "dcl";

    private final IPackageManager mPackageManager;
    private final Object mInstallLock;
    @GuardedBy("mInstallLock")
    private final Installer mInstaller;

    public static DexManager.Listener getListener(IPackageManager pms,
            Installer installer, Object installLock) {
        return new DexLogger(pms, installer, installLock);
    }

    private DexLogger(IPackageManager pms, Installer installer, Object installLock) {
      mPackageManager = pms;
      mInstaller = installer;
      mInstallLock = installLock;
    }

    /**
     * Compute and log hashes of the name and content of a secondary dex file.
     */
    @Override
    public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
            String dexPath, int storageFlags) {
        int ownerUid = appInfo.uid;

        byte[] hash = null;
        synchronized(mInstallLock) {
            try {
                hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
                        ownerUid, appInfo.volumeUuid, storageFlags);
            } catch (InstallerException e) {
                Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
                        " : " + e.getMessage());
            }
        }
        if (hash == null) {
            return;
        }

        String dexFileName = new File(dexPath).getName();
        String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
        // Valid SHA256 will be 256 bits, 32 bytes.
        if (hash.length == 32) {
            message = message + ' ' + ByteStringUtils.toHexString(hash);
        }

        EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, ownerUid, message);

        if (dexUseInfo.isUsedByOtherApps()) {
            Set<String> otherPackages = dexUseInfo.getLoadingPackages();
            Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
            for (String otherPackageName : otherPackages) {
                try {
                    int otherUid = mPackageManager.getPackageUid(
                        otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
                    if (otherUid != -1 && otherUid != ownerUid) {
                        otherUids.add(otherUid);
                    }
                } catch (RemoteException ignore) {
                    // Can't happen, we're local.
                }
            }
            for (int otherUid : otherUids) {
                EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, otherUid, message);
            }
        }
    }
}
+19 −4
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ public class DexManager {
    private final Object mInstallLock;
    @GuardedBy("mInstallLock")
    private final Installer mInstaller;
    private final Listener mListener;

    // Possible outcomes of a dex search.
    private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
@@ -96,14 +97,24 @@ public class DexManager {
     */
    private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();

    public interface Listener {
        /**
         * Invoked just before the secondary dex file {@code dexPath} for the specified application
         * is reconciled.
         */
        void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
                String dexPath, int storageFlags);
    }

    public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
            Installer installer, Object installLock) {
            Installer installer, Object installLock, Listener listener) {
      mPackageCodeLocationsCache = new HashMap<>();
      mPackageDexUsage = new PackageDexUsage();
      mPackageManager = pms;
      mPackageDexOptimizer = pdo;
      mInstaller = installer;
      mInstallLock = installLock;
      mListener = listener;
    }

    /**
@@ -389,7 +400,7 @@ public class DexManager {
                : mPackageDexOptimizer;
        String packageName = options.getPackageName();
        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
        if (useInfo.getDexUseInfoMap().isEmpty()) {
            if (DEBUG) {
                Slog.d(TAG, "No secondary dex use for package:" + packageName);
            }
@@ -433,7 +444,7 @@ public class DexManager {
     */
    public void reconcileSecondaryDexFiles(String packageName) {
        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
        if (useInfo.getDexUseInfoMap().isEmpty()) {
            if (DEBUG) {
                Slog.d(TAG, "No secondary dex use for package:" + packageName);
            }
@@ -481,12 +492,16 @@ public class DexManager {
                continue;
            }

            if (mListener != null) {
                mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
            }

            boolean dexStillExists = true;
            synchronized(mInstallLock) {
                try {
                    String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
                    dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
                            info.uid, isas, info.volumeUuid, flags);
                } catch (InstallerException e) {
                    Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
                            " : " + e.getMessage());
+41 −6
Original line number Diff line number Diff line
@@ -17,12 +17,15 @@
package com.android.server.pm.dex;

import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.UserHandle;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.server.pm.Installer;

import dalvik.system.DelegateLastClassLoader;
import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;
@@ -36,8 +39,13 @@ import java.util.List;
import java.util.Map;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -45,6 +53,12 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
@@ -56,6 +70,12 @@ public class DexManagerTests {
    private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
            DelegateLastClassLoader.class.getName();

    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
    @Mock Installer mInstaller;
    @Mock IPackageManager mPM;
    private final Object mInstallLock = new Object();
    @Mock DexManager.Listener mListener;

    private DexManager mDexManager;

    private TestData mFooUser0;
@@ -90,7 +110,8 @@ public class DexManagerTests {
        mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
                DELEGATE_LAST_CLASS_LOADER_NAME);

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

        // Foo and Bar are available to user0.
        // Only Bar is available to user1;
@@ -440,6 +461,20 @@ public class DexManagerTests {

    }

    @Test
    public void testReconcileSecondaryDexFiles_invokesListener() throws Exception {
        List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
        notifyDexLoad(mFooUser0, fooSecondaries, mUser0);

        when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0))
                .thenReturn(mFooUser0.mPackageInfo);

        mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName());

        verify(mListener, times(fooSecondaries.size()))
                .onReconcileSecondaryDexFile(any(ApplicationInfo.class),
                        any(DexUseInfo.class), anyString(), anyInt());
    }

    private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
            List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
@@ -492,12 +527,12 @@ public class DexManagerTests {
    }

    private PackageUseInfo getPackageUseInfo(TestData testData) {
        assertTrue(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
        return mDexManager.getPackageUseInfoOrDefault(testData.mPackageInfo.packageName);
        assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
        return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
    }

    private void assertNoUseInfo(TestData testData) {
        assertFalse(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
        assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
    }

    private static PackageInfo getMockPackageInfo(String packageName, int userId) {
@@ -555,8 +590,8 @@ public class DexManagerTests {

        List<String> getSecondaryDexPathsFromProtectedDirs() {
            List<String> paths = new ArrayList<>();
            paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary6.dex");
            paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary7.dex");
            paths.add(mPackageInfo.applicationInfo.deviceProtectedDataDir + "/secondary6.dex");
            paths.add(mPackageInfo.applicationInfo.credentialProtectedDataDir + "/secondary7.dex");
            return paths;
        }