CVE-2025-26421 AI-generated analysis
platform/packages/apps/Settings (app info: AppButtonsPreferenceController, spa PackageInfoPresenter, Utils) plus platform/frameworks/base core/res config resource
Adds an OEM-configurable list of "biometric protected" packages (frameworks/base resource config_biometric_protected_package_names). The Settings app-info screens (legacy AppButtonsPreferenceController and SPA PackageInfoPresenter) now require lock-screen confirmation via AppInfoDashboardFragment.showLockScreen before disabling, force-stopping or uninstalling (updates of) a package on that list.
Root cause
In Settings, the app-info actions that modify a package ran as soon as the button or dialog was clicked, with no extra user verification. AppButtonsPreferenceController.handleDialogClick ran DisableChangerRunnable (COMPONENT_ENABLED_STATE_DISABLED_USER) and forceStopPackage directly. AppButtonsPreferenceController.uninstallPkg started ACTION_UNINSTALL_PACKAGE directly. The SPA PackageInfoPresenter.disable/forceStop/startUninstallActivity called setApplicationEnabledSetting, forceStopPackageAsUser and startUninstallActivity directly. There was no concept of a protected package whose modification requires the user to re-authenticate.
How the fix works
- 1) platform/frameworks/base 6c2c795c4bc0: core/res/res/values/config.xml adds an empty, non-translatable string-array config_biometric_protected_package_names. core/res/res/values/symbols.xml exports it as a java-symbol of type array, so Settings can read com.android.internal.R.array.config_biometric_protected_package_names.
- 2) Settings 38fefded38a4, src/com/android/settings/Utils.java: new static Utils.isProtectedPackage(Context, String) reads that array and returns true if it contains the package name.
- 3) Settings, AppButtonsPreferenceController: new private requireAuthAndExecute(Runnable). If Utils.isProtectedPackage(mContext, mAppEntry.info.packageName) is true, it routes the action through AppInfoDashboardFragment.showLockScreen(mContext, ...); otherwise it runs the action directly. handleDialogClick wraps both the DISABLE case (metrics + DisableChangerRunnable DISABLED_USER) and the FORCE_STOP case (forceStopPackage) in it. uninstallPkg wraps the whole uninstall-intent construction and mFragment.startActivityForResult in it. The system-app button handler is split: the enabled-but-disabled-until-used case (ACTION_SETTINGS_DISABLE_APP, DisableChangerRunnable COMPONENT_ENABLED_STATE_DEFAULT) now goes through requireAuthAndExecute, while the enable path stays unauthenticated.
- 4) Settings, spa/app/appinfo/PackageInfoPresenter.kt: the same private requireAuthAndExecute(action) helper. disable(), startUninstallActivity() and forceStop() now run their coroutine or intent bodies inside it. enable() is not wrapped, and the diff does not change clearInstantApp().
- 5) Tests: ShadowUtils gains an isProtectedPackage shadow and setter, and AppButtonsPreferenceControllerTest registers the shadow. PackageInfoPresenterTest adds *_protectedPackage tests. They static-mock Utils.isProtectedPackage through ExtendedMockito and use an ordinary KeyguardManager mock whose isKeyguardSecure returns false.
Completeness
- Gating depends entirely on config_biometric_protected_package_names, which ships empty in AOSP. With no overlay, Utils.isProtectedPackage always returns false and behaviour is unchanged for every package.
- Covered: disable, force-stop and uninstall in AppButtonsPreferenceController (dialog clicks, uninstallPkg, the disabled-until-used disable branch) and in PackageInfoPresenter (disable, forceStop, startUninstallActivity).
- Not gated by this change: the enable paths in both classes, PackageInfoPresenter.clearInstantApp, and the separate AppInfoDashboardFragment.uninstallPkg(String, boolean, boolean). Per index search at android-14.0.0_r75, that method builds and starts an ACTION_UNINSTALL_PACKAGE intent without a protected-package check. Whether it is reachable is not shown.
- The check exists only in the Settings UI. The diffs do not change PackageManager.setApplicationEnabledSetting, ActivityManager.forceStopPackageAsUser or the uninstaller.
- The actual authentication is delegated to the existing AppInfoDashboardFragment.showLockScreen, whose implementation is not in the material. The new tests stub KeyguardManager.isKeyguardSecure to false in a helper named setAuthPassesAutomatically. This suggests, but does not confirm, that the action proceeds without a challenge when no secure lock screen is set.
- In PackageInfoPresenter the metrics logAction call still happens before the authentication step.
Behavior changes
- For packages listed in config_biometric_protected_package_names, Settings routes disable, force-stop and uninstall through AppInfoDashboardFragment.showLockScreen. The action runs only in its callback.
- New system resource com.android.internal.R.array.config_biometric_protected_package_names (empty by default) and new public helper Utils.isProtectedPackage in Settings.
- In AppButtonsPreferenceController the system-app button handler now logs ACTION_SETTINGS_DISABLE_APP and ACTION_SETTINGS_ENABLE_APP from separate branches instead of a ternary. For unprotected packages the semantics are unchanged.
- No behaviour change for packages not on the list, or on devices that do not overlay the array.
Per major
- Android 13: Listed as affected by the bulletin, but the material includes no Android 13 commit. Fix status on 13 is unknown from this material.
- Android 14: Same fix: frameworks/base 6c2c795c4bc0 (resource + symbol) and Settings 38fefded38a4 in android-security-14.0.0_r20 (Merged-In I0c494e307b02229d751de118abcc89e4e61a6861). At android-security-14.0.0_r29, 8/8 lines of the patched region are present.
- Android 15: Adapted fix: Settings commit f16fa58405ed in android-security-15.0.0_r8. The logic is the same, but PackageInfoPresenter.forceStop also wraps the android.app.Flags.appRestrictionsApi()/noteAppRestrictionEnabled block, disable has no reloadPackageInfo, the handleDialogClick DISABLE case keeps getPackageNameForMetric(), ShadowUtils lives under tests/robotests/testutils, and the test uses mockito-kotlin. 8/8 lines present at android-security-15.0.0_r17.
- Android 16: Not affected (not listed by the bulletin). At android-security-16.0.0_r8, 4/4 lines of the patched region are present.
- Android 17: Not affected (not listed by the bulletin). At android-security-17.0.0_r1, 4/4 lines of the patched region are present.
Unknowns
- The implementation of AppInfoDashboardFragment.showLockScreen (which challenge it uses, and what it does when no secure keyguard is set) is not in the material.
- Which packages Google or OEMs put in config_biometric_protected_package_names; the AOSP default is empty.
- Whether an Android 13 fix was shipped (the bulletin lists 13 as affected, but no 13 commit is provided).
- The concrete scenario behind the High EoP rating (bugs 352504490, 344865740) is not described in the material.
- Whether AppInfoDashboardFragment.uninstallPkg is still reachable in security branches.