From 0ee1e6b15cceebb537b8072294cfaebf4ed44874 Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Mon, 25 Jan 2021 23:55:58 -0800 Subject: [PATCH] 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 --- .../server/pm/PackageManagerService.java | 28 +++++++++---- .../server/pm/PackageManagerServiceUtils.java | 9 ++++- .../com/android/server/rollback/Rollback.java | 1 + .../android/tests/rollback/RollbackTest.java | 40 +++++++++++++++++++ 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 68b06989b24e8..ea2b08deaf7f4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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); + } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index d3d7c6055fcec..e6a4282950996 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -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 + diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index e12991a220c96..9560f59924dee 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -563,6 +563,7 @@ class Rollback { params.setRequestDowngrade(true); params.setRequiredInstalledVersionCode( pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode()); + params.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK); if (isStaged()) { params.setStaged(); } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 0db2b2af72601..7b2a07fd80f8a 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -1225,4 +1225,44 @@ public class RollbackTest { InstallUtils.dropShellPermissionIdentity(); } } + + /** + * Tests an app can be rolled back to the previous signing key. + * + *

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(); + } + } }