From a0023604ddaeef34049f1a245be7c42a66a7d0e8 Mon Sep 17 00:00:00 2001 From: Alan Stokes Date: Mon, 16 Oct 2017 12:31:44 +0100 Subject: [PATCH] Log SHA256 of secondary dex files during reconcile. Bug: 63927552 Test: Exercised manually. Added unit test for DexManager. Change-Id: Ic8e9ea4da8eb5c22fbe088a59a406e36bc2eb6fa --- .../core/java/com/android/server/pm/Installer.java | 10 ++ .../android/server/pm/PackageManagerService.java | 6 +- .../java/com/android/server/pm/dex/DexLogger.java | 116 +++++++++++++++++++++ .../java/com/android/server/pm/dex/DexManager.java | 23 +++- .../com/android/server/pm/dex/DexManagerTests.java | 47 +++++++-- 5 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 services/core/java/com/android/server/pm/dex/DexLogger.java diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 210eb1385035..6a06be2fcaa9 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -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 { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 053029268c50..bbe59eb84904 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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( diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java new file mode 100644 index 000000000000..c7bbf1cbc5c6 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/DexLogger.java @@ -0,0 +1,116 @@ +/* + * 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 otherPackages = dexUseInfo.getLoadingPackages(); + Set 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); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 62747547f320..0e2730cbd944 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -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()); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 4db9a30a11ca..36d0c8bec09e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -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 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 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 getSecondaryDexPathsFromProtectedDirs() { List 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; } -- 2.11.0