diff options
5 files changed, 127 insertions, 1 deletions
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index ff9edd511e84..2807a1e4c081 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -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); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index f9bf54a11df0..ac05aabf998f 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -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 { diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index 530d0e492f2e..76f8df02465b 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -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 { diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index 02597d548361..12f1750fb78c 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -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(), @@ -82,6 +87,24 @@ public class StagedInstallInternalTest { } @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(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index 55def498a0cd..792e29d93be2 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -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"); |