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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<String> getStagingDirectories() throws DeviceNotAvailableException {
|
||||
String baseDir = "/data/app-staging";
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user