Allow APK rollback without rollback capability on previous key

Currently if an APK rollback is requested after an update to an APK
that just rotated its signing key the rollback will only be allowed
if the previous signing key in the lineage has the rollback
capability granted. This commit allows a rollback to the previously
installed APK without requiring the rollback capability if the
rollback API is used. This allows apps to take advantage of APK
rollbacks without losing the benefit of key rotation by needing to
set the rollback capability on the previous signing key.

Bug: 175231724
Test: atest RollbackTest
Test: atest RollbackManaagerTest
Test: atest PkgInstallSignatureVerificationTest
Change-Id: I460ef4e7d4c221509b6442f2e8d3079229ad07c8
This commit is contained in:
Michael Groover
2021-01-25 23:55:58 -08:00
parent c64d83723f
commit 0ee1e6b15c
4 changed files with 69 additions and 9 deletions

View File

@@ -18654,9 +18654,11 @@ public class PackageManagerService extends IPackageManager.Stub
final VersionInfo versionInfo = request.versionInfos.get(installPackageName);
final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
final boolean isRollback = installArgs != null
&& installArgs.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
final boolean compatMatch = verifySignatures(signatureCheckPs,
disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
compareRecover);
compareRecover, isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
removeAppKeySetData = true;
@@ -19625,6 +19627,7 @@ public class PackageManagerService extends IPackageManager.Stub
final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
final boolean virtualPreload =
((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
final boolean isRollback = args.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
@ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
// moving a complete application; perform an initial scan on the new install location
@@ -19805,7 +19808,8 @@ public class PackageManagerService extends IPackageManager.Stub
parsedPackage);
// We don't care about disabledPkgSetting on install for now.
final boolean compatMatch = verifySignatures(signatureCheckPs, null,
parsedPackage.getSigningDetails(), compareCompat, compareRecover);
parsedPackage.getSigningDetails(), compareCompat, compareRecover,
isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
synchronized (mLock) {
@@ -20082,15 +20086,23 @@ public class PackageManagerService extends IPackageManager.Stub
+ pkgName11);
}
} else {
SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
// default to original signature matching
if (!parsedPackage.getSigningDetails().checkCapability(
oldPackage.getSigningDetails(),
if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
SigningDetails.CertCapabilities.INSTALLED_DATA)
&& !oldPackage.getSigningDetails().checkCapability(
parsedPackage.getSigningDetails(),
&& !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
SigningDetails.CertCapabilities.ROLLBACK)) {
throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package has a different signature: " + pkgName11);
// Allow the update to proceed if this is a rollback and the parsed
// package's current signing key is the current signer or in the lineage
// of the old package; this allows a rollback to a previously installed
// version after an app's signing key has been rotated without requiring
// the rollback capability on the previous signing key.
if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
parsedPkgSigningDetails)) {
throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package has a different signature: " + pkgName11);
}
}
}

View File

@@ -621,7 +621,7 @@ public class PackageManagerServiceUtils {
*/
public static boolean verifySignatures(PackageSetting pkgSetting,
PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
boolean compareCompat, boolean compareRecover)
boolean compareCompat, boolean compareRecover, boolean isRollback)
throws PackageManagerException {
final String packageName = pkgSetting.name;
boolean compatMatch = false;
@@ -655,6 +655,13 @@ public class PackageManagerServiceUtils {
match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
}
if (!match && isRollback) {
// Since a rollback can only be initiated for an APK previously installed on the
// device allow rolling back to a previous signing key even if the rollback
// capability has not been granted.
match = pkgSetting.signatures.mSigningDetails.hasAncestorOrSelf(parsedSignatures);
}
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + packageName +

View File

@@ -563,6 +563,7 @@ class Rollback {
params.setRequestDowngrade(true);
params.setRequiredInstalledVersionCode(
pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
params.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
if (isStaged()) {
params.setStaged();
}

View File

@@ -1225,4 +1225,44 @@ public class RollbackTest {
InstallUtils.dropShellPermissionIdentity();
}
}
/**
* Tests an app can be rolled back to the previous signing key.
*
* <p>The rollback capability in the signing lineage allows an app to be updated to an APK
* signed with a previous signing key in the lineage; however this often defeats the purpose
* of key rotation as a compromised key could then be used to roll an app back to the previous
* key. To avoid requiring the rollback capability to support app rollbacks the PackageManager
* allows an app to be rolled back to the previous signing key if the rollback install reason
* is set.
*/
@Test
public void testRollbackAfterKeyRotation() throws Exception {
try {
InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.MANAGE_ROLLBACKS);
// Uninstall TestApp.A
Uninstall.packages(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
// Install v1 of the app with the original signing key (without rollbacks enabled).
Install.single(TestApp.AOriginal1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Upgrade from v1 to v2 with the rotated signing key, with rollbacks enabled.
Install.single(TestApp.ARotated2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// Roll back the app.
RollbackInfo available = waitForAvailableRollback(TestApp.A);
RollbackUtils.rollback(available.getRollbackId());
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
} finally {
InstallUtils.dropShellPermissionIdentity();
}
}
}