[ADI][45/N] bypass developer verification in emergency cases
An emergency situation is when the developer verification policy
is BLOCK_FAIL_CLOSED, and the verifier fails to be launched or
connected or it times out, while one of the following apps are being
installed or updated:
* The verifier itself
* The original update-owner of the verifier as specified in sysconfig
* The emergency installer of the verifier's original update-owner
Because these are critical packages, we don't want to block them in the
emergency situations. This CL allows the developer verification bypass
under such situations.
BUG: 360129657
FLAG: android.content.pm.verification_service
Test: atest CtsRootDeveloperVerificationPrivInstallerTarget36TestCases CtsRootDeveloperVerificationInstallerTarget36TestCases CtsRootDeveloperVerificationPrivInstallerTarget35TestCases CtsRootDeveloperVerificationInstallerTarget35TestCases
Test: atest com.android.server.pm.PackageInstallerSessionTest
Change-Id: I925b2b4cfb7c92277adef9f09bf322fb5af9d3eb
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5f9d1a8..8a2ba85 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -175,7 +175,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.EventLogTags;
-import com.android.server.SystemConfig;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.parsing.PackageCacher;
@@ -336,8 +335,7 @@
final String oldUpdateOwner =
pkgAlreadyExists ? oldPkgSetting.getInstallSource().mUpdateOwnerPackageName : null;
final String updateOwnerFromSysconfig = isApex || !pkgSetting.isSystem() ? null
- : mPm.mInjector.getSystemConfig().getSystemAppUpdateOwnerPackageName(
- parsedPackage.getPackageName());
+ : mPm.getSystemAppUpdateOwnerPackageName(parsedPackage.getPackageName());
final boolean isUpdateOwnershipDenylisted =
mUpdateOwnershipHelper.isUpdateOwnershipDenylisted(parsedPackage.getPackageName());
final boolean isUpdateOwnershipEnabled = oldUpdateOwner != null;
@@ -483,12 +481,11 @@
if (listItems != null && !listItems.isEmpty()) {
mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(),
listItems);
- SystemConfig config = SystemConfig.getInstance();
synchronized (mPm.mLock) {
for (String unownedPackage : listItems) {
PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage);
if (unownedSetting != null
- && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) {
+ && mPm.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) {
unownedSetting.setUpdateOwnerPackage(null);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 386b269..67ad2ba 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -55,6 +55,7 @@
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.verify.developer.DeveloperVerificationSession.DEVELOPER_VERIFICATION_BYPASSED_REASON_ADB;
+import static android.content.pm.verify.developer.DeveloperVerificationSession.DEVELOPER_VERIFICATION_BYPASSED_REASON_EMERGENCY;
import static android.content.pm.verify.developer.DeveloperVerificationSession.DEVELOPER_VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE;
import static android.os.Process.INVALID_UID;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
@@ -3090,17 +3091,62 @@
return false;
}
}
+ return true;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ boolean shouldAllowDeveloperVerificationEmergencyBypass(String packageName, Computer snapshot) {
final String verifierPackageName = mDeveloperVerifierController.getVerifierPackageName();
- synchronized (mLock) {
- if (TextUtils.equals(verifierPackageName, mPackageName)) {
- // The verifier itself is being updated. Skip.
- // TODO(b/360129657): log bypass reason and this bypass should only happen if the
- // current verifier cannot be connected or isn't responding.
- Slog.w(TAG, "Skipping verification service because the verifier is being updated");
+ if (verifierPackageName == null) {
+ // Impossible condition. The verifier must exist because otherwise we wouldn't get
+ // here. Added to prevent lint warnings.
+ return false;
+ }
+ if (packageName == null) {
+ return false;
+ }
+ PackageStateInternal ps = snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID);
+ if (ps == null || !ps.isSystem()) {
+ // The app being installed must be a system app to be considered a critical app for
+ // the emergency bypass.
+ return false;
+ }
+ // Check if app being installed is the verifier itself.
+ if (TextUtils.equals(verifierPackageName, packageName)) {
+ Slog.d(TAG, "Bypassing developer verification because the verifier is being updated");
+ return true;
+ }
+ // Check if app being installed is the sysconfig-specified update-owner of the verifier.
+ final String updateOwnerPackageName = mPm.getSystemAppUpdateOwnerPackageName(
+ verifierPackageName);
+ if (updateOwnerPackageName == null) {
+ // No sysconfig-specified update-owner for the verifier. No need to check further.
+ return false;
+ }
+ if (TextUtils.equals(updateOwnerPackageName, packageName)) {
+ Slog.d(TAG, "Bypassing verification service because the sysconfig-specified "
+ + "update owner of the verifier is being updated");
+ return true;
+ }
+ // Check if app being installed is the emergency installer of the sysconfig-specified
+ // update-owner of the verifier.
+ if (isEmergencyInstallerEnabled(updateOwnerPackageName, snapshot, userId, ps.getAppId())) {
+ final PackageStateInternal psUpdateOwner = snapshot.getPackageStateInternal(
+ updateOwnerPackageName, Process.SYSTEM_UID);
+ if (psUpdateOwner == null || psUpdateOwner.getPkg() == null) {
+ // Impossible condition, because the if clause above already checked this.
+ // Added to prevent lint warnings.
return false;
}
+ String emergencyInstallerPackageName = psUpdateOwner.getPkg().getEmergencyInstaller();
+ if (emergencyInstallerPackageName != null
+ && TextUtils.equals(emergencyInstallerPackageName, packageName)) {
+ Slog.d(TAG, "Bypassing verification service because the "
+ + "emergency installer of the verifier's update owner is being updated");
+ return true;
+ }
}
- return true;
+ return false;
}
private void retryDeveloperVerificationSession(Supplier<Computer> snapshotSupplier) {
@@ -3241,6 +3287,17 @@
resumeVerify();
return;
}
+ if (shouldAllowDeveloperVerificationEmergencyBypass(
+ getPackageName(), mPm.snapshotComputer())) {
+ // Bypass verification when critical package is being updated and the verifier
+ // cannot be connected.
+ synchronized (mMetrics) {
+ mMetrics.onDeveloperVerificationBypassed(
+ DEVELOPER_VERIFICATION_BYPASSED_REASON_EMERGENCY);
+ }
+ resumeVerify();
+ return;
+ }
mVerificationUserActionNeededReason =
DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_UNKNOWN;
@@ -3267,6 +3324,17 @@
resumeVerify();
return;
}
+ if (shouldAllowDeveloperVerificationEmergencyBypass(
+ getPackageName(), mPm.snapshotComputer())) {
+ // Bypass verification when critical package is being updated and the verifier
+ // cannot be connected.
+ synchronized (mMetrics) {
+ mMetrics.onDeveloperVerificationBypassed(
+ DEVELOPER_VERIFICATION_BYPASSED_REASON_EMERGENCY);
+ }
+ resumeVerify();
+ return;
+ }
mVerificationUserActionNeededReason =
DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_UNKNOWN;
@@ -3304,6 +3372,17 @@
resumeVerify();
return;
}
+ if (shouldAllowDeveloperVerificationEmergencyBypass(
+ getPackageName(), mPm.snapshotComputer())) {
+ // Bypass verification when critical package is being updated and the verifier
+ // has timed out.
+ synchronized (mMetrics) {
+ mMetrics.onDeveloperVerificationBypassed(
+ DEVELOPER_VERIFICATION_BYPASSED_REASON_EMERGENCY);
+ }
+ resumeVerify();
+ return;
+ }
mVerificationUserActionNeededReason =
DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_UNKNOWN;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 081a5c3..1f7a050 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8450,4 +8450,12 @@
String getDeveloperVerificationPolicyDelegatePackageName() {
return mDeveloperVerificationPolicyDelegatePackage;
}
+
+ /**
+ * @return The update-owner of the given package name as specified in the system config file.
+ */
+ @Nullable
+ public String getSystemAppUpdateOwnerPackageName(String packageName) {
+ return mInjector.getSystemConfig().getSystemAppUpdateOwnerPackageName(packageName);
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 0bd9377..73572f7 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -28,11 +28,14 @@
import android.os.Parcel
import android.os.PersistableBundle
import android.os.Process
+import android.os.UserHandle
import android.platform.test.annotations.Presubmit
import android.util.AtomicFile
import android.util.Slog
import android.util.Xml
import com.android.internal.os.BackgroundThread
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.server.pm.pkg.PackageStateInternal
import com.android.server.pm.verify.developer.DeveloperVerifierController
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
@@ -49,6 +52,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@@ -62,6 +66,7 @@
private const val TAG_SESSIONS = "sessions"
private const val TEST_KEY_FOR_EXTENSION_PARAMS = "testKey"
private const val TEST_VALUE_FOR_EXTENSION_PARAMS = "testValue"
+ private const val USER_ID = 456
}
@JvmField
@@ -73,6 +78,8 @@
@Mock
lateinit var mMockPackageManagerInternal: PackageManagerService
+ @Mock
+ lateinit var mMockDeveloperVerifierController: DeveloperVerifierController
@Mock
lateinit var mSnapshot: Computer
@@ -151,6 +158,138 @@
writeRestoreAssert(listOf(session, childSession1, childSession2))
}
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassReturnsFalseForNullPackageName() {
+ // Test no package name
+ val session = createSession()
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ null, mSnapshot)).isFalse()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassReturnsFalseForNonVerifierPackageName() {
+ // Test no verifier package name
+ whenever(mMockDeveloperVerifierController.verifierPackageName).thenReturn(null)
+ val session = createSession()
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ "testPackageName", mSnapshot)).isFalse()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassReturnsFalseForNonPreinstalledApp() {
+ val testPackageName = "testPackageName"
+ val session = createSession()
+ whenever(mSnapshot.getPackageStateInternal(eq(testPackageName), eq(Process.SYSTEM_UID)))
+ .thenReturn(null)
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ testPackageName, mSnapshot)).isFalse()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassReturnsFalseForNonSystemApp() {
+ val testPackageName = "testPackageName"
+ val session = createSession()
+ val mockPs = mock(PackageStateInternal::class.java)
+ whenever(mockPs.isSystem).thenReturn(false)
+ whenever(mSnapshot.getPackageStateInternal(eq(testPackageName), eq(Process.SYSTEM_UID)))
+ .thenReturn(mockPs)
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ testPackageName, mSnapshot)).isFalse()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassForVerifier() {
+ val verifierPackageName = "verifierPackageName"
+ val mockPs = mock(PackageStateInternal::class.java)
+ whenever(mMockDeveloperVerifierController.verifierPackageName).thenReturn(
+ verifierPackageName)
+ whenever(mockPs.isSystem).thenReturn(true)
+ whenever(mSnapshot.getPackageStateInternal(
+ eq(verifierPackageName), eq(Process.SYSTEM_UID)))
+ .thenReturn(mockPs)
+ val session = createSession()
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ verifierPackageName, mSnapshot)).isTrue()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassReturnsFalseForNonUpdateOwner() {
+ val verifierPackageName = "verifierPackageName"
+ val updateOwnerName = "updateOwnerPackageName"
+ val mockPs = mock(PackageStateInternal::class.java)
+ whenever(mMockDeveloperVerifierController.verifierPackageName).thenReturn(
+ verifierPackageName)
+ whenever(mockPs.isSystem).thenReturn(true)
+ whenever(mSnapshot.getPackageStateInternal(
+ eq(updateOwnerName), eq(Process.SYSTEM_UID)))
+ .thenReturn(mockPs)
+ whenever(mMockPackageManagerInternal.getSystemAppUpdateOwnerPackageName(
+ anyString())).thenReturn(null)
+ val session = createSession()
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ updateOwnerName, mSnapshot)).isFalse()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassForUpdateOwner() {
+ val verifierPackageName = "verifierPackageName"
+ val updateOwnerName = "updateOwnerPackageName"
+ val mockPs = mock(PackageStateInternal::class.java)
+ whenever(mMockDeveloperVerifierController.verifierPackageName).thenReturn(
+ verifierPackageName)
+ whenever(mockPs.isSystem).thenReturn(true)
+ whenever(mSnapshot.getPackageStateInternal(
+ eq(updateOwnerName), eq(Process.SYSTEM_UID)))
+ .thenReturn(mockPs)
+ whenever(mMockPackageManagerInternal.getSystemAppUpdateOwnerPackageName(
+ eq(verifierPackageName))).thenReturn(updateOwnerName)
+ val session = createSession()
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ updateOwnerName, mSnapshot)).isTrue()
+ }
+
+ @Test
+ fun testShouldAllowDeveloperVerificationEmergencyBypassForEmergencyInstaller() {
+ val verifierPackageName = "verifierPackageName"
+ val updateOwnerName = "updateOwnerPackageName"
+ val emergencyInstallerPackageName = "emergencyInstallerPackageName"
+ val mockPs = mock(PackageStateInternal::class.java)
+ val mockUpdateOwnerPs = mock(PackageStateInternal::class.java)
+ val mockUpdateOwnerPkg = mock(AndroidPackageInternal::class.java)
+ val mockUid = 10001
+ val mockUpdateOwnerUid = 10200
+ whenever(mockPs.appId).thenReturn(mockUid)
+ whenever(mockUpdateOwnerPs.appId).thenReturn(mockUpdateOwnerUid)
+ whenever(mockUpdateOwnerPkg.emergencyInstaller).thenReturn(
+ emergencyInstallerPackageName)
+ whenever(mMockDeveloperVerifierController.verifierPackageName).thenReturn(
+ verifierPackageName)
+ whenever(mockPs.isSystem).thenReturn(true)
+ whenever(mockUpdateOwnerPs.isSystem).thenReturn(true)
+ whenever(mockUpdateOwnerPs.pkg).thenReturn(mockUpdateOwnerPkg)
+ whenever(mSnapshot.getPackageStateInternal(
+ eq(updateOwnerName), eq(Process.SYSTEM_UID)))
+ .thenReturn(mockUpdateOwnerPs)
+ whenever(mSnapshot.getPackageStateInternal(
+ eq(updateOwnerName)))
+ .thenReturn(mockUpdateOwnerPs)
+ whenever(mSnapshot.getPackageStateInternal(
+ eq(emergencyInstallerPackageName), eq(Process.SYSTEM_UID)))
+ .thenReturn(mockPs)
+ whenever(mSnapshot.getPackagesForUid(eq(mockUid))).thenReturn(
+ listOf(emergencyInstallerPackageName).toTypedArray())
+ whenever(mSnapshot.checkUidPermission(anyString(),
+ eq(UserHandle.getUid(USER_ID, mockUpdateOwnerUid))))
+ .thenReturn(PackageManager.PERMISSION_GRANTED)
+ whenever(mSnapshot.checkUidPermission(anyString(), eq(mockUid)))
+ .thenReturn(PackageManager.PERMISSION_GRANTED)
+ whenever(mMockPackageManagerInternal.getSystemAppUpdateOwnerPackageName(
+ eq(verifierPackageName))).thenReturn(updateOwnerName)
+ val session = createSession()
+ assertThat(session.shouldAllowDeveloperVerificationEmergencyBypass(
+ emergencyInstallerPackageName, mSnapshot)).isTrue()
+ }
+
private fun createSession(
staged: Boolean = false,
sessionId: Int = 123,
@@ -183,7 +322,7 @@
/* looper */ BackgroundThread.getHandler().looper,
/* stagingManager */ null,
/* sessionId */ sessionId,
- /* userId */ 456,
+ /* userId */ USER_ID,
/* installerUid */ Process.myUid(),
/* installSource */ installSource,
/* sessionParams */ params,
@@ -205,7 +344,7 @@
/* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
/* stagedSessionErrorMessage */ "some error",
/* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
- /* VerifierController */ mock(DeveloperVerifierController::class.java),
+ /* VerifierController */ mMockDeveloperVerifierController,
/* initialVerificationPolicy */ DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_OPEN,
/* currentVerificationPolicy */ DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
/* installDependencyHelper */ null