Loading services/core/java/com/android/server/rollback/Rollback.java +75 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; Loading @@ -36,6 +37,7 @@ import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.os.UserManager; import android.os.ext.SdkExtensions; import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; Loading @@ -43,8 +45,11 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.File; import java.io.IOException; Loading Loading @@ -175,10 +180,9 @@ class Rollback { private int mNumPackageSessionsWithSuccess; /** * The extension versions supported at the time of rollback creation. May be null if not set * at creation time. * The extension versions supported at the time of rollback creation. */ @Nullable private final SparseIntArray mExtensionVersions; private final SparseIntArray mExtensionVersions; /** * Constructs a new, empty Rollback instance. Loading Loading @@ -211,7 +215,8 @@ class Rollback { Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId, String installerPackageName) { this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null, null); this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null, new SparseIntArray(0)); } /** Loading Loading @@ -297,7 +302,7 @@ class Rollback { * Returns the extension versions that were supported at the time that the rollback was created, * as a mapping from SdkVersion to ExtensionVersion. */ @Nullable SparseIntArray getExtensionVersions() { SparseIntArray getExtensionVersions() { return mExtensionVersions; } Loading Loading @@ -470,6 +475,15 @@ class Rollback { return; } if (containsApex() && wasCreatedAtLowerExtensionVersion()) { PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) { sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE, "Rollback may violate a minExtensionVersion constraint"); return; } } // Get a context to use to install the downgraded version of the package. Context pkgContext; try { Loading Loading @@ -845,6 +859,56 @@ class Rollback { } } /** * Returns true if there is an app installed that specifies a minExtensionVersion greater * than what was present at the time this Rollback was created. */ @VisibleForTesting static boolean extensionVersionReductionWouldViolateConstraint( SparseIntArray rollbackExtVers, PackageManagerInternal pmi) { if (rollbackExtVers.size() == 0) { return false; } List<String> packages = pmi.getPackageList().getPackageNames(); for (int i = 0; i < packages.size(); i++) { AndroidPackage pkg = pmi.getPackage(packages.get(i)); SparseIntArray minExtVers = pkg.getMinExtensionVersions(); if (minExtVers == null) { continue; } for (int j = 0; j < rollbackExtVers.size(); j++) { int minExt = minExtVers.get(rollbackExtVers.keyAt(j), -1); if (rollbackExtVers.valueAt(j) < minExt) { return true; } } } return false; } /** * Returns true if for any SDK version, the extension version recorded at the time of rollback * creation is lower than the current extension version. */ private boolean wasCreatedAtLowerExtensionVersion() { for (int i = 0; i < mExtensionVersions.size(); i++) { if (SdkExtensions.getExtensionVersion(mExtensionVersions.keyAt(i)) > mExtensionVersions.valueAt(i)) { return true; } } return false; } private boolean containsApex() { for (PackageRollbackInfo pkgInfo : info.getPackages()) { if (pkgInfo.isApex()) { return true; } } return false; } void dump(IndentingPrintWriter ipw) { synchronized (mLock) { ipw.println(info.getRollbackId() + ":"); Loading @@ -871,6 +935,12 @@ class Rollback { ipw.decreaseIndent(); ipw.println("-committedSessionId: " + info.getCommittedSessionId()); } if (mExtensionVersions.size() > 0) { ipw.println("-extensionVersions:"); ipw.increaseIndent(); ipw.println(mExtensionVersions.toString()); ipw.decreaseIndent(); } ipw.decreaseIndent(); } } Loading services/core/java/com/android/server/rollback/RollbackStore.java +3 −6 Original line number Diff line number Diff line Loading @@ -179,10 +179,7 @@ class RollbackStore { } private static @Nullable JSONArray extensionVersionsToJson( @Nullable SparseIntArray extensionVersions) throws JSONException { if (extensionVersions == null) { return null; } SparseIntArray extensionVersions) throws JSONException { JSONArray array = new JSONArray(); for (int i = 0; i < extensionVersions.size(); i++) { JSONObject entryJson = new JSONObject(); Loading @@ -193,10 +190,10 @@ class RollbackStore { return array; } private static @Nullable SparseIntArray extensionVersionsFromJson(@Nullable JSONArray json) private static @Nullable SparseIntArray extensionVersionsFromJson(JSONArray json) throws JSONException { if (json == null) { return null; return new SparseIntArray(0); } SparseIntArray extensionVersions = new SparseIntArray(json.length()); for (int i = 0; i < json.length(); i++) { Loading services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -234,8 +234,8 @@ public class RollbackStoreTest { @Test public void loadFromJsonNoExtensionVersions() throws Exception { Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null, null); Rollback expectedRb = mRollbackStore.createNonStagedRollback( ID, USER, INSTALLER, null, new SparseIntArray(0)); expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z")); expectedRb.setRestoreUserDataInProgress(true); Loading Loading @@ -337,7 +337,8 @@ public class RollbackStoreTest { @Test public void saveAndDelete() { Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null, null); Rollback rollback = mRollbackStore.createNonStagedRollback( ID, USER, INSTALLER, null, new SparseIntArray(0)); RollbackStore.saveRollback(rollback); Loading services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +93 −0 Original line number Diff line number Diff line Loading @@ -24,12 +24,18 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManagerInternal; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.util.IntArray; import android.util.SparseIntArray; import android.util.SparseLongArray; import com.android.server.pm.PackageList; import com.android.server.pm.parsing.pkg.PackageImpl; import com.google.common.collect.Range; import org.junit.Before; Loading @@ -44,6 +50,7 @@ import java.io.File; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @RunWith(JUnit4.class) public class RollbackUnitTest { Loading @@ -56,10 +63,17 @@ public class RollbackUnitTest { private static final String INSTALLER = "some.installer"; @Mock private AppDataRollbackHelper mMockDataHelper; @Mock private PackageManagerInternal mMockPmi; private List<String> mPackages; private PackageList mPackageList; @Before public void setUp() { MockitoAnnotations.initMocks(this); mPackages = new ArrayList<>(); mPackageList = new PackageList(mPackages, null); when(mMockPmi.getPackageList()).thenReturn(mPackageList); } @Test Loading Loading @@ -340,6 +354,85 @@ public class RollbackUnitTest { assertThat(rollback.allPackagesEnabled()).isTrue(); } @Test public void minExtVerConstraintNotViolated() { addPkgWithMinExtVersions("pkg0", new int[][] {{30, 4}}); addPkgWithMinExtVersions("pkg1", new int[][] {}); addPkgWithMinExtVersions("pkg2", new int[][] {{30, 5}, {31, 1}}); addPkgWithMinExtVersions("pkg3", new int[][] {{31, 7}, {32, 15}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{30, 5}}), mMockPmi)).isFalse(); } @Test public void minExtVerConstraintExists() { addPkgWithMinExtVersions("pkg0", null); addPkgWithMinExtVersions("pkg1", new int[][] {{30, 5}, {31, 1}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{30, 4}}), mMockPmi)).isTrue(); } @Test public void minExtVerConstraintExistsOnOnePackage() { addPkgWithMinExtVersions("pkg0", new int[][] {{30, 4}}); addPkgWithMinExtVersions("pkg1", new int[][] {}); addPkgWithMinExtVersions("pkg2", new int[][] {{30, 5}, {31, 1}}); addPkgWithMinExtVersions("pkg3", new int[][] {{31, 7}, {32, 15}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{30, 4}}), mMockPmi)).isTrue(); } @Test public void minExtVerConstraintDifferentSdk() { addPkgWithMinExtVersions("pkg0", null); addPkgWithMinExtVersions("pkg1", new int[][] {{30, 5}, {31, 1}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{32, 4}}), mMockPmi)).isFalse(); } @Test public void minExtVerConstraintNoneRecordedOnRollback() { addPkgWithMinExtVersions("pkg0", new int[][] {{30, 4}}); addPkgWithMinExtVersions("pkg1", new int[][] {}); addPkgWithMinExtVersions("pkg2", new int[][] {{30, 5}, {31, 1}}); addPkgWithMinExtVersions("pkg3", new int[][] {{31, 7}, {32, 15}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( new SparseIntArray(0), mMockPmi)).isFalse(); } @Test public void minExtVerConstraintNoMinsRecorded() { addPkgWithMinExtVersions("pkg0", null); addPkgWithMinExtVersions("pkg1", null); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{32, 4}}), mMockPmi)).isFalse(); } private void addPkgWithMinExtVersions(String pkg, int[][] minExtVersions) { mPackages.add(pkg); PackageImpl pkgImpl = new PackageImpl(pkg, "baseCodePath", "codePath", null, false); pkgImpl.setMinExtensionVersions(sparseArrayFrom(minExtVersions)); when(mMockPmi.getPackage(pkg)).thenReturn(pkgImpl); } private static SparseIntArray sparseArrayFrom(int[][] arr) { if (arr == null) { return null; } SparseIntArray result = new SparseIntArray(arr.length); for (int[] pair : arr) { result.put(pair[0], pair[1]); } return result; } private static PackageRollbackInfo newPkgInfoFor( String packageName, long fromVersion, long toVersion, boolean isApex) { return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), Loading Loading
services/core/java/com/android/server/rollback/Rollback.java +75 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; Loading @@ -36,6 +37,7 @@ import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.os.UserManager; import android.os.ext.SdkExtensions; import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; Loading @@ -43,8 +45,11 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.File; import java.io.IOException; Loading Loading @@ -175,10 +180,9 @@ class Rollback { private int mNumPackageSessionsWithSuccess; /** * The extension versions supported at the time of rollback creation. May be null if not set * at creation time. * The extension versions supported at the time of rollback creation. */ @Nullable private final SparseIntArray mExtensionVersions; private final SparseIntArray mExtensionVersions; /** * Constructs a new, empty Rollback instance. Loading Loading @@ -211,7 +215,8 @@ class Rollback { Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId, String installerPackageName) { this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null, null); this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null, new SparseIntArray(0)); } /** Loading Loading @@ -297,7 +302,7 @@ class Rollback { * Returns the extension versions that were supported at the time that the rollback was created, * as a mapping from SdkVersion to ExtensionVersion. */ @Nullable SparseIntArray getExtensionVersions() { SparseIntArray getExtensionVersions() { return mExtensionVersions; } Loading Loading @@ -470,6 +475,15 @@ class Rollback { return; } if (containsApex() && wasCreatedAtLowerExtensionVersion()) { PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) { sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE, "Rollback may violate a minExtensionVersion constraint"); return; } } // Get a context to use to install the downgraded version of the package. Context pkgContext; try { Loading Loading @@ -845,6 +859,56 @@ class Rollback { } } /** * Returns true if there is an app installed that specifies a minExtensionVersion greater * than what was present at the time this Rollback was created. */ @VisibleForTesting static boolean extensionVersionReductionWouldViolateConstraint( SparseIntArray rollbackExtVers, PackageManagerInternal pmi) { if (rollbackExtVers.size() == 0) { return false; } List<String> packages = pmi.getPackageList().getPackageNames(); for (int i = 0; i < packages.size(); i++) { AndroidPackage pkg = pmi.getPackage(packages.get(i)); SparseIntArray minExtVers = pkg.getMinExtensionVersions(); if (minExtVers == null) { continue; } for (int j = 0; j < rollbackExtVers.size(); j++) { int minExt = minExtVers.get(rollbackExtVers.keyAt(j), -1); if (rollbackExtVers.valueAt(j) < minExt) { return true; } } } return false; } /** * Returns true if for any SDK version, the extension version recorded at the time of rollback * creation is lower than the current extension version. */ private boolean wasCreatedAtLowerExtensionVersion() { for (int i = 0; i < mExtensionVersions.size(); i++) { if (SdkExtensions.getExtensionVersion(mExtensionVersions.keyAt(i)) > mExtensionVersions.valueAt(i)) { return true; } } return false; } private boolean containsApex() { for (PackageRollbackInfo pkgInfo : info.getPackages()) { if (pkgInfo.isApex()) { return true; } } return false; } void dump(IndentingPrintWriter ipw) { synchronized (mLock) { ipw.println(info.getRollbackId() + ":"); Loading @@ -871,6 +935,12 @@ class Rollback { ipw.decreaseIndent(); ipw.println("-committedSessionId: " + info.getCommittedSessionId()); } if (mExtensionVersions.size() > 0) { ipw.println("-extensionVersions:"); ipw.increaseIndent(); ipw.println(mExtensionVersions.toString()); ipw.decreaseIndent(); } ipw.decreaseIndent(); } } Loading
services/core/java/com/android/server/rollback/RollbackStore.java +3 −6 Original line number Diff line number Diff line Loading @@ -179,10 +179,7 @@ class RollbackStore { } private static @Nullable JSONArray extensionVersionsToJson( @Nullable SparseIntArray extensionVersions) throws JSONException { if (extensionVersions == null) { return null; } SparseIntArray extensionVersions) throws JSONException { JSONArray array = new JSONArray(); for (int i = 0; i < extensionVersions.size(); i++) { JSONObject entryJson = new JSONObject(); Loading @@ -193,10 +190,10 @@ class RollbackStore { return array; } private static @Nullable SparseIntArray extensionVersionsFromJson(@Nullable JSONArray json) private static @Nullable SparseIntArray extensionVersionsFromJson(JSONArray json) throws JSONException { if (json == null) { return null; return new SparseIntArray(0); } SparseIntArray extensionVersions = new SparseIntArray(json.length()); for (int i = 0; i < json.length(); i++) { Loading
services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -234,8 +234,8 @@ public class RollbackStoreTest { @Test public void loadFromJsonNoExtensionVersions() throws Exception { Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null, null); Rollback expectedRb = mRollbackStore.createNonStagedRollback( ID, USER, INSTALLER, null, new SparseIntArray(0)); expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z")); expectedRb.setRestoreUserDataInProgress(true); Loading Loading @@ -337,7 +337,8 @@ public class RollbackStoreTest { @Test public void saveAndDelete() { Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null, null); Rollback rollback = mRollbackStore.createNonStagedRollback( ID, USER, INSTALLER, null, new SparseIntArray(0)); RollbackStore.saveRollback(rollback); Loading
services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +93 −0 Original line number Diff line number Diff line Loading @@ -24,12 +24,18 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManagerInternal; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.util.IntArray; import android.util.SparseIntArray; import android.util.SparseLongArray; import com.android.server.pm.PackageList; import com.android.server.pm.parsing.pkg.PackageImpl; import com.google.common.collect.Range; import org.junit.Before; Loading @@ -44,6 +50,7 @@ import java.io.File; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @RunWith(JUnit4.class) public class RollbackUnitTest { Loading @@ -56,10 +63,17 @@ public class RollbackUnitTest { private static final String INSTALLER = "some.installer"; @Mock private AppDataRollbackHelper mMockDataHelper; @Mock private PackageManagerInternal mMockPmi; private List<String> mPackages; private PackageList mPackageList; @Before public void setUp() { MockitoAnnotations.initMocks(this); mPackages = new ArrayList<>(); mPackageList = new PackageList(mPackages, null); when(mMockPmi.getPackageList()).thenReturn(mPackageList); } @Test Loading Loading @@ -340,6 +354,85 @@ public class RollbackUnitTest { assertThat(rollback.allPackagesEnabled()).isTrue(); } @Test public void minExtVerConstraintNotViolated() { addPkgWithMinExtVersions("pkg0", new int[][] {{30, 4}}); addPkgWithMinExtVersions("pkg1", new int[][] {}); addPkgWithMinExtVersions("pkg2", new int[][] {{30, 5}, {31, 1}}); addPkgWithMinExtVersions("pkg3", new int[][] {{31, 7}, {32, 15}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{30, 5}}), mMockPmi)).isFalse(); } @Test public void minExtVerConstraintExists() { addPkgWithMinExtVersions("pkg0", null); addPkgWithMinExtVersions("pkg1", new int[][] {{30, 5}, {31, 1}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{30, 4}}), mMockPmi)).isTrue(); } @Test public void minExtVerConstraintExistsOnOnePackage() { addPkgWithMinExtVersions("pkg0", new int[][] {{30, 4}}); addPkgWithMinExtVersions("pkg1", new int[][] {}); addPkgWithMinExtVersions("pkg2", new int[][] {{30, 5}, {31, 1}}); addPkgWithMinExtVersions("pkg3", new int[][] {{31, 7}, {32, 15}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{30, 4}}), mMockPmi)).isTrue(); } @Test public void minExtVerConstraintDifferentSdk() { addPkgWithMinExtVersions("pkg0", null); addPkgWithMinExtVersions("pkg1", new int[][] {{30, 5}, {31, 1}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{32, 4}}), mMockPmi)).isFalse(); } @Test public void minExtVerConstraintNoneRecordedOnRollback() { addPkgWithMinExtVersions("pkg0", new int[][] {{30, 4}}); addPkgWithMinExtVersions("pkg1", new int[][] {}); addPkgWithMinExtVersions("pkg2", new int[][] {{30, 5}, {31, 1}}); addPkgWithMinExtVersions("pkg3", new int[][] {{31, 7}, {32, 15}}); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( new SparseIntArray(0), mMockPmi)).isFalse(); } @Test public void minExtVerConstraintNoMinsRecorded() { addPkgWithMinExtVersions("pkg0", null); addPkgWithMinExtVersions("pkg1", null); assertThat(Rollback.extensionVersionReductionWouldViolateConstraint( sparseArrayFrom(new int[][] {{32, 4}}), mMockPmi)).isFalse(); } private void addPkgWithMinExtVersions(String pkg, int[][] minExtVersions) { mPackages.add(pkg); PackageImpl pkgImpl = new PackageImpl(pkg, "baseCodePath", "codePath", null, false); pkgImpl.setMinExtensionVersions(sparseArrayFrom(minExtVersions)); when(mMockPmi.getPackage(pkg)).thenReturn(pkgImpl); } private static SparseIntArray sparseArrayFrom(int[][] arr) { if (arr == null) { return null; } SparseIntArray result = new SparseIntArray(arr.length); for (int[] pair : arr) { result.put(pair[0], pair[1]); } return result; } private static PackageRollbackInfo newPkgInfoFor( String packageName, long fromVersion, long toVersion, boolean isApex) { return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), Loading