Log SHA256 of secondary dex files during reconcile.

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

Change-Id: Ic8e9ea4da8eb5c22fbe088a59a406e36bc2eb6fa
This commit is contained in:
Alan Stokes
2017-10-16 12:31:44 +01:00
parent b92f1eed1b
commit a0023604dd
5 changed files with 191 additions and 11 deletions

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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<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);
}
}
}
}

View File

@@ -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());

View File

@@ -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;
}