Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ac7bc339 authored by Oli Lan's avatar Oli Lan
Browse files

Block rollback commit if it may violate min extension constraint.

This CL adds a check when a rollback is committed, if the rollback
contains an apex and an extension version has increased since
the rollback was created. The check looks for any installed app with
a minExtensionVersion greater than was present when the rollback was
created.

If such an app exists, the rollback commit is not allowed to occur.

See go/sdk-extensions-and-rollback for more details.

Bug: 152737927
Test: atest RollbackUnitTest
Test: atest RollbackTest
Test: atest RollbackManagerHostTest
Merged-In: I7fd7d98a03ecb1edb1b79475fadb944bc4f2fd6f
Change-Id: I7fd7d98a03ecb1edb1b79475fadb944bc4f2fd6f
parent 2986572a
Loading
Loading
Loading
Loading
+75 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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.
@@ -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));
    }

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

@@ -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 {
@@ -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() + ":");
@@ -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();
        }
    }
+3 −6
Original line number Diff line number Diff line
@@ -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();
@@ -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++) {
+4 −3
Original line number Diff line number Diff line
@@ -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);
@@ -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);

+93 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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 {
@@ -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
@@ -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),