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

Commit d1193502 authored by JW Wang's avatar JW Wang
Browse files

Duplicate packages should fail to install

Fail a staged session if there are duplicate packages in apk and
apk-in-apex.

This CL prevents the issue in b/150940087#comment1.

Bug: 150940087
Fix: 161569960
Test: atest StagedInstallInternalTest#testDuplicateApkInApexShouldFail
Change-Id: I4d5f4ba91f41ea2c4a013fee82fa7167e46229f0
parent 271e922b
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -1637,7 +1637,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     * If session should be sealed, then it's sealed to prevent further modification.
     * If the session can't be sealed then it's destroyed.
     *
     * Additionally for staged APEX sessions read+validate the package and populate req'd fields.
     * Additionally for staged APEX/APK sessions read+validate the package and populate req'd
     * fields.
     *
     * <p> This is meant to be called after all of the sessions are loaded and added to
     * PackageInstallerService
@@ -1670,6 +1671,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    // APEX installations rely on certain fields to be populated after reboot.
                    // E.g. mPackageName.
                    validateApexInstallLocked();
                } else {
                    // Populate mPackageName for this APK session which is required by the staging
                    // manager to check duplicate apk-in-apex.
                    PackageInstallerSession parent = allSessions.get(mParentSessionId);
                    if (parent != null && parent.isStagedSessionReady()) {
                        validateApkInstallLocked();
                    }
                }
            } catch (PackageManagerException e) {
                Slog.e(TAG, "Package not valid", e);
+37 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -84,6 +85,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -650,6 +652,7 @@ public class StagingManager {
        try {
            if (hasApex) {
                checkInstallationOfApkInApexSuccessful(session);
                checkDuplicateApkInApex(session);
                snapshotAndRestoreForApexSession(session);
                Slog.i(TAG, "APEX packages in session " + session.sessionId
                        + " were successfully activated. Proceeding with APK packages, if any");
@@ -829,6 +832,40 @@ public class StagingManager {
        return null;
    }

    /**
     * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex.
     */
    private void checkDuplicateApkInApex(@NonNull PackageInstallerSession session)
            throws PackageManagerException {
        if (!session.isMultiPackage()) {
            return;
        }
        final int[] childSessionIds = session.getChildSessionIds();
        final Set<String> apkNames = new ArraySet<>();
        synchronized (mStagedSessions) {
            for (int id : childSessionIds) {
                final PackageInstallerSession s = mStagedSessions.get(id);
                if (!isApexSession(s)) {
                    apkNames.add(s.getPackageName());
                }
            }
        }
        final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
        for (PackageInstallerSession apexSession : apexSessions) {
            String packageName = apexSession.getPackageName();
            for (String apkInApex : mApexManager.getApksInApex(packageName)) {
                if (!apkNames.add(apkInApex)) {
                    throw new PackageManagerException(
                            SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                            "Package: " + packageName + " in session: "
                                    + apexSession.sessionId + " has duplicate apk-in-apex: "
                                    + apkInApex, null);

                }
            }
        }
    }

    private void installApksInSession(@NonNull PackageInstallerSession session)
            throws PackageManagerException {

+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ android_test_helper_app {
    srcs: ["app/src/**/*.java"],
    static_libs: ["androidx.test.rules", "cts-install-lib"],
    test_suites: ["general-tests"],
    java_resources: [
        ":com.android.apex.apkrollback.test_v2",
    ],
}

java_test_host {
+23 −0
Original line number Diff line number Diff line
@@ -49,6 +49,11 @@ import java.util.function.Consumer;
public class StagedInstallInternalTest {

    private static final String TAG = StagedInstallInternalTest.class.getSimpleName();
    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
    private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
            APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
    private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
            APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");

    private File mTestStateFile = new File(
            InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
@@ -81,6 +86,24 @@ public class StagedInstallInternalTest {
        Files.deleteIfExists(mTestStateFile.toPath());
    }

    @Test
    public void testDuplicateApkInApexShouldFail_Commit() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
        // Duplicate packages(TestApp.A) in TEST_APEX_WITH_APK_V2(apk-in-apex) and TestApp.A2(apk)
        // should fail to install.
        int sessionId = Install.multi(TEST_APEX_WITH_APK_V2, TestApp.A2).setStaged().commit();
        storeSessionId(sessionId);
    }

    @Test
    public void testDuplicateApkInApexShouldFail_Verify() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
        int sessionId = retrieveLastSessionId();
        PackageInstaller.SessionInfo info =
                InstallUtils.getPackageInstaller().getSessionInfo(sessionId);
        assertThat(info.isStagedSessionFailed()).isTrue();
    }

    @Test
    public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception {
        int sessionId = Install.single(TestApp.A1).setStaged().commit();
+55 −0
Original line number Diff line number Diff line
@@ -24,11 +24,14 @@ import static org.junit.Assume.assumeTrue;

import android.cts.install.lib.host.InstallUtilsHost;

import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
import com.android.tests.rollback.host.AbandonSessionsRule;
import com.android.tests.util.ModuleTestUtils;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.ProcessInfo;

import org.junit.After;
@@ -49,6 +52,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
    public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
    private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
    private static final String APK_A = "TestAppAv1.apk";
    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";

    private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
    private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
@@ -74,6 +78,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
        } catch (AssertionError e) {
            Log.e(TAG, e);
        }
        deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
                "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
    }

    @Before
@@ -86,6 +92,55 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
        cleanUp();
    }

    /**
     * Deletes files and reboots the device if necessary.
     * @param files the paths of files which might contain wildcards
     */
    private void deleteFiles(String... files) throws Exception {
        boolean found = false;
        for (String file : files) {
            CommandResult result = getDevice().executeShellV2Command("ls " + file);
            if (result.getStatus() == CommandStatus.SUCCESS) {
                found = true;
                break;
            }
        }

        if (found) {
            if (!getDevice().isAdbRoot()) {
                getDevice().enableAdbRoot();
            }
            getDevice().remountSystemWritable();
            for (String file : files) {
                getDevice().executeShellCommand("rm -rf " + file);
            }
            getDevice().reboot();
        }
    }

    private void pushTestApex() throws Exception {
        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
        final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
        final File apex = buildHelper.getTestFile(fileName);
        if (!getDevice().isAdbRoot()) {
            getDevice().enableAdbRoot();
        }
        getDevice().remountSystemWritable();
        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
        getDevice().reboot();
    }

    /**
     * Tests that duplicate packages in apk-in-apex and apk should fail to install.
     */
    @Test
    public void testDuplicateApkInApexShouldFail() throws Exception {
        pushTestApex();
        runPhase("testDuplicateApkInApexShouldFail_Commit");
        getDevice().reboot();
        runPhase("testDuplicateApkInApexShouldFail_Verify");
    }

    @Test
    public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception {
        runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit");