From e501fd8379b1849cd3ae4f7a0485c7890aa87179 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Tue, 20 Jul 2021 01:19:44 +0100 Subject: [PATCH 1/2] Restrict what APEXes different installers can update * Mainline APEXes can only be updated by a modulesInstaller (an entry in whitelisted-staged-installer.xml with isModulesInstaller="true") * The whitelisted-staged-installer.xml file can have only one entry with isModulesInstaller="true" * Vendor APEXes can only be updated by an installer defined in the corresponding entry in vendor-apex-allowlist.xml. BYPASS_INCLUSIVE_LANGUAGE_REASON=existing occurrences Test: atest StagedInstallInternalTest Test: atest GtsStagedInstallHostTestCases Test: atest CtsRollbackManagerHostTestCases Test: atest CtsStagedInstallHostTestCases Bug: 190802380 Change-Id: I729abb5153df079fb6885016a4b41d0acc827e3c --- .../java/com/android/server/SystemConfig.java | 32 ++++++- .../server/pm/PackageInstallerSession.java | 26 +++++- .../server/systemconfig/SystemConfigTest.java | 47 +++++++++- .../StagedInstallInternalTest.java | 92 ++++++++++++++++++- .../host/StagedInstallInternalTest.java | 80 +++++++++++++++- 5 files changed, 263 insertions(+), 14 deletions(-) diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index a8dcbaffeeb51..6cbace4c65ba4 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -241,7 +241,11 @@ public class SystemConfig { private final ArraySet mRollbackWhitelistedPackages = new ArraySet<>(); private final ArraySet mWhitelistedStagedInstallers = new ArraySet<>(); - private final ArraySet mAllowedVendorApexes = new ArraySet<>(); + // A map from package name of vendor APEXes that can be updated to an installer package name + // allowed to install updates for it. + private final ArrayMap mAllowedVendorApexes = new ArrayMap<>(); + + private String mModulesInstallerPackageName; /** * Map of system pre-defined, uniquely named actors; keys are namespace, @@ -412,10 +416,14 @@ public class SystemConfig { return mWhitelistedStagedInstallers; } - public Set getAllowedVendorApexes() { + public Map getAllowedVendorApexes() { return mAllowedVendorApexes; } + public String getModulesInstallerPackageName() { + return mModulesInstallerPackageName; + } + public ArraySet getAppDataIsolationWhitelistedApps() { return mAppDataIsolationWhitelistedApps; } @@ -1210,12 +1218,21 @@ public class SystemConfig { case "whitelisted-staged-installer": { if (allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); + boolean isModulesInstaller = XmlUtils.readBooleanAttribute( + parser, "isModulesInstaller", false); if (pkgname == null) { Slog.w(TAG, "<" + name + "> without package in " + permFile + " at " + parser.getPositionDescription()); } else { mWhitelistedStagedInstallers.add(pkgname); } + if (isModulesInstaller) { + if (mModulesInstallerPackageName != null) { + throw new IllegalStateException( + "Multiple modules installers"); + } + mModulesInstallerPackageName = pkgname; + } } else { logNotAllowedInPartition(name, permFile, parser); } @@ -1224,11 +1241,18 @@ public class SystemConfig { case "allowed-vendor-apex": { if (allowVendorApex) { String pkgName = parser.getAttributeValue(null, "package"); + String installerPkgName = parser.getAttributeValue( + null, "installerPackage"); if (pkgName == null) { Slog.w(TAG, "<" + name + "> without package in " + permFile + " at " + parser.getPositionDescription()); - } else { - mAllowedVendorApexes.add(pkgName); + } + if (installerPkgName == null) { + Slog.w(TAG, "<" + name + "> without installerPackage in " + permFile + + " at " + parser.getPositionDescription()); + } + if (pkgName != null && installerPkgName != null) { + mAllowedVendorApexes.put(pkgName, installerPkgName); } } else { logNotAllowedInPartition(name, permFile, parser); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 00ef97d7a17a6..7cc49fd7aab24 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2252,9 +2252,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { (params.installFlags & PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK) == 0; synchronized (mLock) { - if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName)) { + if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName, + mInstallSource.installerPackageName)) { onSessionValidationFailure(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, - "Update of APEX package " + mPackageName + " is not allowed"); + "Update of APEX package " + mPackageName + " is not allowed for " + + mInstallSource.installerPackageName); return; } } @@ -2798,9 +2800,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return sessionContains((s) -> !s.isApexSession()); } - private boolean isApexUpdateAllowed(String apexPackageName) { - return mPm.getModuleInfo(apexPackageName, 0) != null - || SystemConfig.getInstance().getAllowedVendorApexes().contains(apexPackageName); + private boolean isApexUpdateAllowed(String apexPackageName, String installerPackageName) { + if (mPm.getModuleInfo(apexPackageName, 0) != null) { + final String modulesInstaller = + SystemConfig.getInstance().getModulesInstallerPackageName(); + if (modulesInstaller == null) { + Slog.w(TAG, "No modules installer defined"); + return false; + } + return modulesInstaller.equals(installerPackageName); + } + final String vendorApexInstaller = + SystemConfig.getInstance().getAllowedVendorApexes().get(apexPackageName); + if (vendorApexInstaller == null) { + Slog.w(TAG, apexPackageName + " is not allowed to be updated"); + return false; + } + return vendorApexInstaller.equals(installerPackageName); } /** diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index 9044b27d49944..5eb21a58c38e8 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -19,6 +19,7 @@ package com.android.server.systemconfig; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.testng.Assert.expectThrows; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; @@ -200,6 +201,46 @@ public class SystemConfigTest { assertThat(mSysConfig.getWhitelistedStagedInstallers()) .containsExactly("com.android.package1"); + assertThat(mSysConfig.getModulesInstallerPackageName()).isNull(); + } + + @Test + public void readPermissions_parsesStagedInstallerWhitelist_modulesInstaller() + throws IOException { + final String contents = + "\n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "staged-installer-whitelist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); + + assertThat(mSysConfig.getWhitelistedStagedInstallers()) + .containsExactly("com.android.package1"); + assertThat(mSysConfig.getModulesInstallerPackageName()) + .isEqualTo("com.android.package1"); + } + + @Test + public void readPermissions_parsesStagedInstallerWhitelist_multipleModulesInstallers() + throws IOException { + final String contents = + "\n" + + " \n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "staged-installer-whitelist.xml", contents); + + IllegalStateException e = expectThrows( + IllegalStateException.class, + () -> mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0)); + + assertThat(e).hasMessageThat().contains("Multiple modules installers"); } /** @@ -230,14 +271,16 @@ public class SystemConfigTest { throws IOException { final String contents = "\n" - + " \n" + + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "vendor-apex-allowlist.xml", contents); mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); - assertThat(mSysConfig.getAllowedVendorApexes()).containsExactly("com.android.apex1"); + assertThat(mSysConfig.getAllowedVendorApexes()) + .containsExactly("com.android.apex1", "com.installer"); } /** 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 738e68e336741..60585e84d4ef5 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -55,7 +55,7 @@ public class StagedInstallInternalTest { 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 static final TestApp APEX_WRONG_SHA_V2 = new TestApp( - "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, + "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, "com.android.apex.cts.shim.v2_wrong_sha.apex"); private File mTestStateFile = new File( @@ -236,6 +236,96 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); } + @Test + public void testApexInstallerNotInAllowListCanNotInstall_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); + // We don't really care which APEX we are trying to install here, since the session creation + // should fail immediately. + InstallUtils.commitExpectingFailure( + SecurityException.class, + "Installer not allowed to commit staged install", + Install.single(APEX_WRONG_SHA_V2).setBypassStangedInstallerCheck(false) + .setStaged()); + } + + @Test + public void testApexInstallerNotInAllowListCanNotInstall_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); + // We don't really care which APEX we are trying to install here, since the session creation + // should fail immediately. + InstallUtils.commitExpectingFailure( + SecurityException.class, + "Installer not allowed to commit non-staged APEX install", + Install.single(APEX_WRONG_SHA_V2).setBypassStangedInstallerCheck(false)); + } + + @Test + public void testApexNotInAllowListCanNotInstall_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + } + + @Test + public void testApexNotInAllowListCanNotInstall_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + } + + @Test + public void testVendorApexWrongInstaller_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + } + + @Test + public void testVendorApexWrongInstaller_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + } + + @Test + public void testVendorApexCorrectInstaller_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + int sessionId = + Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged().commit(); + InstallUtils.getPackageInstaller().abandonSession(sessionId); + } + + @Test + public void testVendorApexCorrectInstaller_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + Install.single(apex).setBypassAllowedApexUpdateCheck(false).commit(); + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(2); + } + @Test public void testRebootlessUpdates() throws Exception { InstallUtils.dropShellPermissionIdentity(); 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 3bd3767ac6d95..c6b6aabb74ace 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -43,7 +43,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; import java.util.List; import java.util.stream.Collectors; @@ -60,6 +62,9 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { private static final String APK_A = "TestAppAv1.apk"; private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String TEST_VENDOR_APEX_ALLOW_LIST = + "/vendor/etc/sysconfig/test-vendor-apex-allow-list.xml"; + private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); /** @@ -87,7 +92,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex", "/system/apex/test.rebootless_apex_v1.apex", - "/data/apex/active/test.apex.rebootless*.apex"); + "/data/apex/active/test.apex.rebootless*.apex", + TEST_VENDOR_APEX_ALLOW_LIST); } @Before @@ -134,7 +140,23 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } getDevice().remountSystemWritable(); assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); - getDevice().reboot(); + } + + private void pushTestVendorApexAllowList(String installerPackageName) throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + File file = File.createTempFile("test-vendor-apex-allow-list", ".xml"); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + final String fmt = + "\n" + + " \n" + + ""; + writer.write(String.format(fmt, installerPackageName)); + } + getDevice().pushFile(file, TEST_VENDOR_APEX_ALLOW_LIST); } /** @@ -144,6 +166,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { @LargeTest public void testDuplicateApkInApexShouldFail() throws Exception { pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + getDevice().reboot(); + runPhase("testDuplicateApkInApexShouldFail_Commit"); getDevice().reboot(); runPhase("testDuplicateApkInApexShouldFail_Verify"); @@ -388,9 +412,61 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testApexIsNotActivatedIfNotInCheckpointMode_VerifyPostReboot"); } + @Test + public void testApexInstallerNotInAllowListCanNotInstall() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + runPhase("testApexInstallerNotInAllowListCanNotInstall_staged"); + runPhase("testApexInstallerNotInAllowListCanNotInstall_nonStaged"); + } + + @Test + @LargeTest + public void testApexNotInAllowListCanNotInstall() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + + runPhase("testApexNotInAllowListCanNotInstall_staged"); + runPhase("testApexNotInAllowListCanNotInstall_nonStaged"); + } + + @Test + @LargeTest + public void testVendorApexWrongInstaller() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestVendorApexAllowList("com.wrong.installer"); + pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + + runPhase("testVendorApexWrongInstaller_staged"); + runPhase("testVendorApexWrongInstaller_nonStaged"); + } + + @Test + @LargeTest + public void testVendorApexCorrectInstaller() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestVendorApexAllowList("com.android.tests.stagedinstallinternal"); + pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + + runPhase("testVendorApexCorrectInstaller_staged"); + runPhase("testVendorApexCorrectInstaller_nonStaged"); + } + @Test public void testRebootlessUpdates() throws Exception { pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + runPhase("testRebootlessUpdates"); } From db9892300e9233557d47cf43c5975357419a9d8c Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Thu, 22 Jul 2021 01:37:20 +0100 Subject: [PATCH 2/2] Reject non-staged APEX install if there is staged install of same APEX There is a interesting interaction between staged and non-staged installs of the same APEX. Let's say an installer staged v1 -> v2 APEX update, and then does a non-staged update to v3. After device is rebooted, apexd will apply the staged v1 -> v2 session, silently downgrading an APEX from v3. For apks, this problem is solved by storing an expected version. When an APK session is being applied during boot, Package Manager will check if the currently installed version is equal to the expected one stored in the staged session. If they mismatch, an install is failed. Unfortunately, implementing the same logic in apexd will require a non-trivial refactoring which is too late to do in S. Instead we are just going to fail the non-staged installation. Test: atest StagedInstallInternalTest Bug: 187864524 Change-Id: I9000f40cede9a324a5059a09deb8eb5be13b21f9 --- .../server/pm/PackageInstallerSession.java | 15 +++++ .../com/android/server/pm/StagingManager.java | 20 +++++++ .../android/server/pm/StagingManagerTest.java | 60 +++++++++++++++++++ tests/StagedInstallTest/Android.bp | 1 + .../StagedInstallInternalTest.java | 16 +++++ .../host/StagedInstallInternalTest.java | 8 +++ 6 files changed, 120 insertions(+) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 7cc49fd7aab24..542948491dc8c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2260,6 +2260,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } } + + if (!params.isStaged) { + // For non-staged APEX installs also check if there is a staged session that + // contains the same APEX. If that's the case, we should fail this session. + synchronized (mLock) { + int sessionId = mStagingManager.getSessionIdByPackageName(mPackageName); + if (sessionId != -1) { + onSessionValidationFailure( + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, + "Staged session " + sessionId + " already contains " + + mPackageName); + return; + } + } + } } if (params.isStaged) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 4ac5be2ec7c7c..bdbcb277e8d66 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -777,6 +777,26 @@ public class StagingManager { } } + /** + * Returns id of a committed and non-finalized stated session that contains same + * {@code packageName}, or {@code -1} if no sessions have this {@code packageName} staged. + */ + int getSessionIdByPackageName(@NonNull String packageName) { + synchronized (mStagedSessions) { + for (int i = 0; i < mStagedSessions.size(); i++) { + StagedSession stagedSession = mStagedSessions.valueAt(i); + if (!stagedSession.isCommitted() || stagedSession.isDestroyed() + || stagedSession.isInTerminalState()) { + continue; + } + if (stagedSession.getPackageName().equals(packageName)) { + return stagedSession.sessionId(); + } + } + } + return -1; + } + @VisibleForTesting void createSession(@NonNull StagedSession sessionInfo) { synchronized (mStagedSessions) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index f91cb2801bc12..521be70df633f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -459,6 +459,66 @@ public class StagingManagerTest { assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } + @Test + public void getSessionIdByPackageName() throws Exception { + FakeStagedSession session = new FakeStagedSession(239); + session.setCommitted(true); + session.setSessionReady(); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(239); + } + + @Test + public void getSessionIdByPackageName_appliedSession_ignores() throws Exception { + FakeStagedSession session = new FakeStagedSession(37); + session.setCommitted(true); + session.setSessionApplied(); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_failedSession_ignores() throws Exception { + FakeStagedSession session = new FakeStagedSession(73); + session.setCommitted(true); + session.setSessionFailed(1, "whatevs"); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_destroyedSession_ignores() throws Exception { + FakeStagedSession session = new FakeStagedSession(23); + session.setCommitted(true); + session.setDestroyed(true); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_noSessions() throws Exception { + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_noSessionHasThisPackage() throws Exception { + FakeStagedSession session = new FakeStagedSession(37); + session.setCommitted(true); + session.setSessionApplied(); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.bar")).isEqualTo(-1); + } + private StagingManager.StagedSession createSession(int sessionId, String packageName, long committedMillis) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index 1aa04996f6827..cac14a72a7061 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -32,6 +32,7 @@ android_test_helper_app { test_suites: ["general-tests"], java_resources: [ ":com.android.apex.apkrollback.test_v2", + ":StagedInstallTestApexV2", ":StagedInstallTestApexV2_WrongSha", ":test.rebootless_apex_v1", ":test.rebootless_apex_v2", 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 60585e84d4ef5..4684f0182d034 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -57,6 +57,9 @@ public class StagedInstallInternalTest { private static final TestApp APEX_WRONG_SHA_V2 = new TestApp( "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, "com.android.apex.cts.shim.v2_wrong_sha.apex"); + private static final TestApp APEX_V2 = new TestApp( + "ApexV2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, + "com.android.apex.cts.shim.v2.apex"); private File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), @@ -388,6 +391,19 @@ public class StagedInstallInternalTest { } } + @Test + public void testRebootlessUpdate_hasStagedSessionWithSameApex_fails() throws Exception { + assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); + + int sessionId = Install.single(APEX_V2).setStaged().commit(); + assertSessionReady(sessionId); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Staged session " + sessionId + " already contains " + SHIM_APEX_PACKAGE_NAME, + Install.single(APEX_V2)); + + } + private static void assertSessionApplied(int sessionId) { assertSessionState(sessionId, (session) -> { assertThat(session.isStagedSessionApplied()).isTrue(); 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 c6b6aabb74ace..5021009f65ae8 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -470,6 +470,14 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testRebootlessUpdates"); } + @Test + public void testRebootlessUpdate_hasStagedSessionWithSameApex_fails() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + runPhase("testRebootlessUpdate_hasStagedSessionWithSameApex_fails"); + } + private List getStagingDirectories() throws DeviceNotAvailableException { String baseDir = "/data/app-staging"; try {