Merge "Check for downgrade and signature of outer .apex certificate" into sc-dev

This commit is contained in:
Nikita Ioffe
2021-06-07 16:56:21 +00:00
committed by Android (Google) Code Review
4 changed files with 223 additions and 25 deletions

View File

@@ -32,6 +32,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.parsing.PackageInfoWithoutStateUtils;
import android.content.pm.parsing.ParsingPackageUtils;
import android.os.Binder;
@@ -45,6 +46,7 @@ import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -52,6 +54,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.utils.TimingsTraceAndSlog;
import com.google.android.collect.Lists;
@@ -390,9 +393,10 @@ public abstract class ApexManager {
throws RemoteException;
/**
* Performs a non-staged install of an APEX package with given {@code packagePath}.
* Performs a non-staged install of the given {@code apexFile}.
*/
abstract void installPackage(String packagePath) throws PackageManagerException;
abstract void installPackage(File apexFile, PackageParser2 packageParser)
throws PackageManagerException;
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -979,12 +983,80 @@ public abstract class ApexManager {
waitForApexService().reserveSpaceForCompressedApex(infoList);
}
@Override
void installPackage(String packagePath) throws PackageManagerException {
private SigningDetails getSigningDetails(PackageInfo pkg) throws PackageManagerException {
int minSignatureScheme =
ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
pkg.applicationInfo.targetSdkVersion);
try {
// TODO(b/187864524): do pre-install verification.
waitForApexService().installAndActivatePackage(packagePath);
// TODO(b/187864524): update mAllPackagesCache.
return ApkSignatureVerifier.verify(pkg.applicationInfo.sourceDir,
minSignatureScheme);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
}
private void checkApexSignature(PackageInfo existingApexPkg, PackageInfo newApexPkg)
throws PackageManagerException {
final SigningDetails existingSigningDetails = getSigningDetails(existingApexPkg);
final SigningDetails newSigningDetails = getSigningDetails(newApexPkg);
if (!newSigningDetails.checkCapability(existingSigningDetails,
SigningDetails.CertCapabilities.INSTALLED_DATA)) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
"APK container signature of " + newApexPkg.applicationInfo.sourceDir
+ " is not compatible with currently installed on device");
}
}
private void checkDowngrade(PackageInfo existingApexPkg, PackageInfo newApexPkg)
throws PackageManagerException {
final long currentVersionCode = existingApexPkg.applicationInfo.longVersionCode;
final long newVersionCode = newApexPkg.applicationInfo.longVersionCode;
if (currentVersionCode > newVersionCode) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE,
"Downgrade of APEX package " + newApexPkg.packageName
+ " is not allowed");
}
}
@Override
void installPackage(File apexFile, PackageParser2 packageParser)
throws PackageManagerException {
try {
final int flags = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES
| PackageManager.GET_SIGNATURES;
final ParsedPackage parsedPackage = packageParser.parsePackage(
apexFile, flags, /* useCaches= */ false);
final PackageInfo newApexPkg = PackageInfoWithoutStateUtils.generate(parsedPackage,
/* apexInfo= */ null, flags);
if (newApexPkg == null) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
"Failed to generate package info for " + apexFile.getAbsolutePath());
}
final PackageInfo existingApexPkg = getPackageInfo(newApexPkg.packageName,
MATCH_ACTIVE_PACKAGE);
if (existingApexPkg == null) {
Slog.w(TAG, "Attempting to install new APEX package " + newApexPkg.packageName);
throw new PackageManagerException(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED,
"It is forbidden to install new APEX packages");
}
checkApexSignature(existingApexPkg, newApexPkg);
checkDowngrade(existingApexPkg, newApexPkg);
ApexInfo apexInfo = waitForApexService().installAndActivatePackage(
apexFile.getAbsolutePath());
final ParsedPackage parsedPackage2 = packageParser.parsePackage(
new File(apexInfo.modulePath), flags, /* useCaches= */ false);
final PackageInfo finalApexPkg = PackageInfoWithoutStateUtils.generate(
parsedPackage, apexInfo, flags);
// Installation was successful, time to update mAllPackagesCache
synchronized (mLock) {
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
if (mAllPackagesCache.get(i).equals(existingApexPkg)) {
mAllPackagesCache.set(i, finalApexPkg);
break;
}
}
}
} catch (RemoteException e) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"apexservice not available");
@@ -1262,7 +1334,7 @@ public abstract class ApexManager {
}
@Override
void installPackage(String packagePath) {
void installPackage(File apexFile, PackageParser2 packageParser) {
throw new UnsupportedOperationException("APEX updates are not supported");
}

View File

@@ -17118,7 +17118,7 @@ public class PackageManagerService extends IPackageManager.Stub
try {
// Should directory scanning logic be moved to ApexManager for better test coverage?
final File dir = request.args.origin.resolvedFile;
final String[] apexes = dir.list();
final File[] apexes = dir.listFiles();
if (apexes == null) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
dir.getAbsolutePath() + " is not a directory");
@@ -17128,7 +17128,9 @@ public class PackageManagerService extends IPackageManager.Stub
"Expected exactly one .apex file under " + dir.getAbsolutePath()
+ " got: " + apexes.length);
}
mApexManager.installPackage(dir.getAbsolutePath() + "/" + apexes[0]);
try (PackageParser2 packageParser = mInjector.getScanningPackageParser()) {
mApexManager.installPackage(apexes[0], packageParser);
}
} catch (PackageManagerException e) {
request.installResult.setError("APEX installation failed", e);
}

View File

@@ -118,6 +118,11 @@ android_test {
":PackageParserTestApp3",
":PackageParserTestApp4",
":apex.test",
":test.rebootless_apex_v1",
":test.rebootless_apex_v2",
":com.android.apex.cts.shim.v1_prebuilt",
":com.android.apex.cts.shim.v2_different_certificate_prebuilt",
":com.android.apex.cts.shim.v2_unsigned_apk_container_prebuilt",
],
resource_zips: [":FrameworksServicesTests_apks_as_resources"],
}

View File

@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -29,6 +30,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.expectThrows;
import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
@@ -84,7 +86,7 @@ public class ApexManagerTest {
@Test
public void testGetPackageInfo_setFlagsMatchActivePackage() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, false));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
final PackageInfo activePkgPi = mApexManager.getPackageInfo(TEST_APEX_PKG,
@@ -101,7 +103,7 @@ public class ApexManagerTest {
@Test
public void testGetPackageInfo_setFlagsMatchFactoryPackage() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
PackageInfo factoryPkgPi = mApexManager.getPackageInfo(TEST_APEX_PKG,
@@ -118,7 +120,7 @@ public class ApexManagerTest {
@Test
public void testGetPackageInfo_setFlagsNone() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -127,7 +129,7 @@ public class ApexManagerTest {
@Test
public void testGetActivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -136,7 +138,7 @@ public class ApexManagerTest {
@Test
public void testGetActivePackages_noneActivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -145,7 +147,7 @@ public class ApexManagerTest {
@Test
public void testGetFactoryPackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -154,7 +156,7 @@ public class ApexManagerTest {
@Test
public void testGetFactoryPackages_noneFactoryPackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, false));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -163,7 +165,7 @@ public class ApexManagerTest {
@Test
public void testGetInactivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -172,7 +174,7 @@ public class ApexManagerTest {
@Test
public void testGetInactivePackages_noneInactivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, false));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -181,7 +183,7 @@ public class ApexManagerTest {
@Test
public void testIsApexPackage() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -276,11 +278,11 @@ public class ApexManagerTest {
@Test
public void testReportErrorWithApkInApex() throws RemoteException {
when(mApexService.getActivePackages()).thenReturn(createApexInfo(true, true));
when(mApexService.getActivePackages()).thenReturn(createApexInfoForTestPkg(true, true));
final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -297,7 +299,7 @@ public class ApexManagerTest {
*/
@Test
public void testRegisterApkInApexDoesNotRegisterSimilarPrefix() throws RemoteException {
when(mApexService.getActivePackages()).thenReturn(createApexInfo(true, true));
when(mApexService.getActivePackages()).thenReturn(createApexInfoForTestPkg(true, true));
final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
@@ -305,7 +307,7 @@ public class ApexManagerTest {
when(fakeApkInApex.getBaseApkPath()).thenReturn("/apex/" + TEST_APEX_PKG + "randomSuffix");
when(fakeApkInApex.getPackageName()).thenReturn("randomPackageName");
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true));
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -314,7 +316,112 @@ public class ApexManagerTest {
assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
}
private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) {
@Test
public void testInstallPackageFailsToInstallNewApex() throws Exception {
when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
File apex = extractResource("test.apex_rebootless_v1", "test.rebootless_apex_v1.apex");
PackageManagerException e = expectThrows(PackageManagerException.class,
() -> mApexManager.installPackage(apex, mPackageParser2));
assertThat(e).hasMessageThat().contains("It is forbidden to install new APEX packages");
}
@Test
public void testInstallPackageDowngrade() throws Exception {
File activeApex = extractResource("test.apex_rebootless_v2",
"test.rebootless_apex_v2.apex");
ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
/* isFactory= */ false, activeApex);
when(mApexService.getAllPackages()).thenReturn(new ApexInfo[]{activeApexInfo});
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
File installedApex = extractResource("test.apex_rebootless_v1",
"test.rebootless_apex_v1.apex");
PackageManagerException e = expectThrows(PackageManagerException.class,
() -> mApexManager.installPackage(installedApex, mPackageParser2));
assertThat(e).hasMessageThat().contains(
"Downgrade of APEX package test.apex.rebootless is not allowed");
}
@Test
public void testInstallPackage() throws Exception {
ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
/* isFactory= */ false, extractResource("test.apex_rebootless_v1",
"test.rebootless_apex_v1.apex"));
when(mApexService.getAllPackages()).thenReturn(new ApexInfo[]{activeApexInfo});
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
/* isFactory= */ false, finalApex);
when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
mApexManager.installPackage(installedApex, mPackageParser2);
PackageInfo newInfo = mApexManager.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_ACTIVE_PACKAGE);
assertThat(newInfo.applicationInfo.sourceDir).isEqualTo(finalApex.getAbsolutePath());
assertThat(newInfo.applicationInfo.longVersionCode).isEqualTo(2);
}
@Test
public void testInstallPackageBinderCallFails() throws Exception {
ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
/* isFactory= */ false, extractResource("test.apex_rebootless_v1",
"test.rebootless_apex_v1.apex"));
when(mApexService.getAllPackages()).thenReturn(new ApexInfo[]{activeApexInfo});
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
when(mApexService.installAndActivatePackage(anyString())).thenThrow(
new RuntimeException("install failed :("));
File installedApex = extractResource("test.apex_rebootless_v1",
"test.rebootless_apex_v1.apex");
assertThrows(PackageManagerException.class,
() -> mApexManager.installPackage(installedApex, mPackageParser2));
}
@Test
public void testInstallPackageSignedWithWrongCertificate() throws Exception {
File activeApex = extractResource("shim_v1", "com.android.apex.cts.shim.apex");
ApexInfo activeApexInfo = createApexInfo("com.android.apex.cts.shim", 1,
/* isActive= */ true, /* isFactory= */ false, activeApex);
when(mApexService.getAllPackages()).thenReturn(new ApexInfo[]{activeApexInfo});
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
File installedApex = extractResource("shim_different_certificate",
"com.android.apex.cts.shim.v2_different_certificate.apex");
PackageManagerException e = expectThrows(PackageManagerException.class,
() -> mApexManager.installPackage(installedApex, mPackageParser2));
assertThat(e).hasMessageThat().contains("APK container signature of ");
assertThat(e).hasMessageThat().contains(
"is not compatible with currently installed on device");
}
@Test
public void testInstallPackageUnsignedApexContainer() throws Exception {
File activeApex = extractResource("shim_v1", "com.android.apex.cts.shim.apex");
ApexInfo activeApexInfo = createApexInfo("com.android.apex.cts.shim", 1,
/* isActive= */ true, /* isFactory= */ false, activeApex);
when(mApexService.getAllPackages()).thenReturn(new ApexInfo[]{activeApexInfo});
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
File installedApex = extractResource("shim_unsigned_apk_container",
"com.android.apex.cts.shim.v2_unsigned_apk_container.apex");
PackageManagerException e = expectThrows(PackageManagerException.class,
() -> mApexManager.installPackage(installedApex, mPackageParser2));
assertThat(e).hasMessageThat().contains("Failed to collect certificates from ");
}
private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) {
File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
ApexInfo apexInfo = new ApexInfo();
apexInfo.isActive = isActive;
@@ -327,6 +434,17 @@ public class ApexManagerTest {
return new ApexInfo[]{apexInfo};
}
private ApexInfo createApexInfo(String moduleName, int versionCode, boolean isActive,
boolean isFactory, File apexFile) {
ApexInfo apexInfo = new ApexInfo();
apexInfo.moduleName = moduleName;
apexInfo.versionCode = versionCode;
apexInfo.isActive = isActive;
apexInfo.isFactory = isFactory;
apexInfo.modulePath = apexFile.getPath();
return apexInfo;
}
private ApexSessionInfo getFakeStagedSessionInfo() {
ApexSessionInfo stagedSessionInfo = new ApexSessionInfo();
stagedSessionInfo.sessionId = TEST_SESSION_ID;
@@ -358,6 +476,7 @@ public class ApexManagerTest {
} catch (IOException e) {
throw new AssertionError("CreateTempFile IOException" + e);
}
try (
InputStream in = ApexManager.class.getClassLoader()
.getResourceAsStream(fullResourceName);