CVE-2026-0018 AI-generated analysis
platform/frameworks/base, services/accessibility (AccessibilityManagerService, AccessibilityUserState, new AccessibilityTileUtils)
AccessibilityManagerService now validates each app-declared accessibility QS tile service through PackageManagerInternal, outside the service lock: the service must resolve, be exported, be protected by BIND_QUICK_SETTINGS_TILE and be enabled. AccessibilityUserState records a feature-to-tile mapping only when the tile component is in that validated set.
Root cause
AccessibilityUserState.updateTileServiceMapForAccessibilityServiceLocked() and updateTileServiceMapForAccessibilityActivityLocked() built mA11yServiceToTileService and mA11yActivityToTileService directly from the string returned by AccessibilityServiceInfo.getTileServiceName() and AccessibilityShortcutInfo.getTileServiceName(). Each non-empty string became a ComponentName(featurePackage, tileServiceName) and was stored without any check that the component exists, is exported, is protected by BIND_QUICK_SETTINGS_TILE, or is enabled.
How the fix works
- Commit f3ef4dd5056e (android-security-15.0.0_r13) adds the new file AccessibilityTileUtils.java. getValidA11yTileServices(context, PackageManagerInternal, serviceInfos, shortcutInfos, userId) walks both lists, builds ComponentName(featurePackage, tileServiceName) for each non-empty tile name, and keeps it only if isComponentValidTileService() passes.
- isComponentValidTileService() calls pm.resolveService() with an Intent(TileService.ACTION_QS_TILE) targeted at the component (flags 0, the given userId, Process.myUid()). It rejects a null ResolveInfo or serviceInfo, a service that is not exported, and a serviceInfo.permission other than exactly Manifest.permission.BIND_QUICK_SETTINGS_TILE. It also rejects the component when resolveEnabledComponent(pm.getComponentEnabledSetting(...), serviceInfo.enabled) is false: ENABLED gives true, DEFAULT gives the manifest value, anything else gives false. If pm is null the result is an empty set.
- AccessibilityManagerService.java: the valid set is computed with AccessibilityTileUtils.getValidA11yTileServices(...) before entering synchronized (mLock) or mManagerService.getLock() at all three sites that parse infos. The sites are the user-switch/initialization path (the method that checks mCurrentUserId == userId && mInitialized), the PackageMonitor onSomePackagesChanged path, and the PackageMonitor handler that tracks reboundAService. As a result, PackageManager calls no longer run while the AMS lock is held (per the commit message). The set is passed through new @NonNull Set<ComponentName> validA11yTileServices parameters of onSomePackagesChangedLocked, readConfigurationForUserStateLocked, readInstalledAccessibilityServiceLocked and readInstalledAccessibilityShortcutLocked.
- readInstalledAccessibilityServiceLocked and readInstalledAccessibilityShortcutLocked: the map rebuild moves out of the 'lists changed' branch and now runs unconditionally, with the changed flag still returned. The code comment says PackageManager may not resolve the TileService on the initial load.
- AccessibilityUserState.updateTileServiceMapForAccessibilityServiceLocked(Set) and updateTileServiceMapForAccessibilityActivityLocked(Set) clear the map, return early if the valid set is empty, and call put(a11yFeature, tileService) only when validA11yTileServices.contains(tileService).
- Tests: new AccessibilityTileUtilsTest and a new test helper, utils/TileServiceUtil, which mocks PackageManagerInternal.resolveService and getComponentEnabledSetting. AccessibilityManagerServiceTest and AccessibilityUserStateTest are updated to pass the valid set.
- Commit 40729ba7fc18 (android-security-15.0.0_r16) is test-only. It adds the @Mock PackageManagerInternal mMockPackageManagerInternal field and its LocalServices registration to AccessibilityManagerServiceTest, which fixes the Android 15 test build ('missing variable').
Completeness
- Both maps are covered. The accessibility-service map and the accessibility-activity (shortcut) map both filter against the validated set.
- All three parse sites in AccessibilityManagerService that feed readConfigurationForUserStateLocked compute the valid set and pass it on. Because the update methods' signatures changed, the old no-argument form has no remaining caller.
- The tile ComponentName is always built with the feature's own package name. Validation requires that the service resolves, is exported, has exactly the BIND_QUICK_SETTINGS_TILE permission, and is effectively enabled.
- The valid set is computed outside the lock and used later inside it. Within one pass, a package change between those two points is not re-checked.
- In the service branch, getValidA11yTileServices dereferences a11yServiceInfo.getResolveInfo().serviceInfo without a null check.
- The fix validates only when the maps are built. The material does not show whether the code that consumes the maps runs any further check at use time.
Behavior changes
- Accessibility features whose declared tileService does not resolve, is not exported, lacks BIND_QUICK_SETTINGS_TILE protection, or is disabled are no longer associated with a QS tile in AccessibilityUserState. A Slog.w with the reason is logged for each rejected component.
- If PackageManagerInternal is unavailable (null), the valid set is empty and both maps end up empty.
- The feature-to-tile maps are now rebuilt on every readConfigurationForUserStateLocked pass, not only when the installed service or shortcut lists changed.
- Each parse pass makes one resolveService call per non-empty declared tile name, plus a getComponentEnabledSetting call for tiles that pass the resolve, export and permission checks. All of this runs before the lock is taken.
Per major
- Android 14: Not affected per the bulletin (not listed). At android-security-14.0.0_r29 only 5/8 lines of the patched region are present. The material does not show whether the tile-service map code exists there.
- Android 15: Same fix: f3ef4dd5056e cherry-picked into android-security-15.0.0_r13, plus the test-only follow-up 40729ba7fc18 in android-security-15.0.0_r16, which adds mMockPackageManagerInternal to AccessibilityManagerServiceTest. 6/8 lines of the patched region are present at android-security-15.0.0_r17.
- Android 16: Adapted fix: 7783a5265907 in android-security-16.0.0_r4. AccessibilityTileUtils and the logic are identical. readInstalledAccessibilityShortcutLocked carries the Flags.clearShortcutsWhenActivityUpdatesToService() block. 6/8 lines are present at android-security-16.0.0_r8.
- Android 17: Adapted fix: the original 637f5b4aa4fb in android-17.0.0_r1 (base). It uses mInstalledServicesMap, buildInstalledServicesMapLocked and getInstalledServices() instead of mInstalledServices, and adds extra test setup in AccessibilityManagerServiceTest. 6/8 lines are present at android-security-17.0.0_r1.
Unknowns
- Which consumer of mA11yServiceToTileService or mA11yActivityToTileService fails when given an invalid component, and so the concrete DoS mechanism. The material shows only how the maps are built.
- Whether the vulnerable tile-service map code exists on Android 14. The bulletin lists 14 as not affected, and the material shows only a partial region match.
- What the second linked bug (441506794) covers, and what moving the PackageManager calls outside mLock addressed in practice.
- Whether a package change between computing the valid set and using it under the lock is always followed by a recomputation.