CVE-2025-48612 AI-generated analysis
platform/packages/apps/Settings, NFC default payment app picker (com.android.settings.nfc.DefaultPaymentSettings / PaymentBackend)
In Settings' NFC default payment app selector, the picker key used to be a "flattenedComponentName userId" string that was parsed back on selection. The fix replaces it with an opaque hash key looked up in a map of PaymentAppInfo entries, so the component and user passed to setDefaultPaymentApp always come from an enumerated candidate instead of a string parse.
Root cause
DefaultPaymentSettings.getDefaultKey() built the picker key as componentName.flattenToString() + " " + userHandle.getIdentifier(). setDefaultKey(String key) rebuilt the target with key.split(" "), ComponentName.unflattenFromString(keys[0]) and Integer.parseInt(keys[1]). Because the key was parsed back instead of being mapped to an enumerated candidate, the ComponentName and user id passed to PaymentBackend.setDefaultPaymentApp were never checked against the candidates obtained from getPaymentAppInfos(). The commit calls this string injection.
How the fix works
- src/com/android/settings/nfc/PaymentBackend.java: PaymentAppInfo gains getKey(), which returns Integer.toString(hashCode()). hashCode() is Objects.hash(componentName, userHandle), with a matching equals() on componentName and userHandle. The key no longer embeds the flattened component string.
- src/com/android/settings/nfc/DefaultPaymentSettings.java: onAttach() now stores mAppInfos as a Map<String, PaymentAppInfo>, built from mPaymentBackend.getPaymentAppInfos() with Collectors.toMap(PaymentAppInfo::getKey, Function.identity()).
- src/com/android/settings/nfc/DefaultPaymentSettings.java: getDefaultKey() returns defaultAppInfo.getKey(), or null if there is no default. The flattenToString concatenation is removed.
- src/com/android/settings/nfc/DefaultPaymentSettings.java: setDefaultKey(String key) looks the key up in mAppInfos. If the key is absent it returns true and does nothing. Otherwise it calls mPaymentBackend.setDefaultPaymentApp(appInfo.componentName, appInfo.userHandle.getIdentifier()) with the stored candidate's fields. The split/unflattenFromString/parseInt parsing and the android.content.ComponentName import are removed.
- DefaultPaymentSettings.getCandidates() iterates mAppInfos.values() instead of the list. Nothing else in it changes.
- On Android 14 the change went through three commits: c90fee1f1ea4 (r24); a full revert, da3689792a45 (r28, 'patch needs to be updated to fix regression'); then Revert^2 4b903dacbc6c (r28). The reland's hunks are the same as the original's, but they apply to a different DefaultPaymentSettings.java blob (d75844bb0ff -> b5057ed64f2, versus ddac08beb00 -> 440f09e3d9b originally).
Completeness
- Covers the only parse site shown. setDefaultKey can now only select a component/user pair that PaymentBackend.getPaymentAppInfos() returned at onAttach time.
- The key is Integer.toString(Objects.hash(componentName, userHandle)). It is opaque but not guaranteed collision-free. Collectors.toMap throws on duplicate keys, so two candidates with colliding hashes would cause an exception in onAttach rather than a wrong selection.
- The diff fills mAppInfos only in onAttach. It does not show whether the map is refreshed when payment apps change while the fragment is attached. If it is not, a key for a newly appearing app would be ignored.
- PaymentBackend.setDefaultPaymentApp itself is not changed; the fix sits only at the Settings picker layer.
- The new equals() dereferences componentName and userHandle without null checks, and getDefaultKey carries @SuppressWarnings("NullAway"). The material does not show what happens when these fields are null.
Behavior changes
- Picker keys are now numeric hash strings instead of 'pkg/cls userId'. Any key in the old format no longer matches and is ignored.
- setDefaultKey with an unknown key does nothing and still returns true. Before, keys with fewer than two space-separated parts were ignored and all others were parsed and applied.
- getCandidates() now iterates the collected map's values, so the order may differ from the original list order. Whether candidates are sorted afterwards is not shown in the material.
- Duplicate (componentName, userHandle) pairs from getPaymentAppInfos() would now throw in onAttach because of Collectors.toMap.
Per major
- Android 14: same fix: c90fee1f1ea4 in android-security-14.0.0_r24 (backwards-compatible port of ag/35084316). It was reverted by da3689792a45 and relanded with the same hunks by 4b903dacbc6c, both in android-security-14.0.0_r28. 8/8 patched lines are present at android-security-14.0.0_r29.
- Android 15: same fix: adac3d79c0f3 in android-security-15.0.0_r12, same diff. 8/8 lines are present at android-security-15.0.0_r17.
- Android 16: same fix: aa744e8988f0 in android-security-16.0.0_r3, same diff. 8/8 lines are present at android-security-16.0.0_r8.
- Android 17: same fix in base: 1553d28f0f57 in android-17.0.0_r1, same diff. 8/8 lines are present at android-security-17.0.0_r1.
Unknowns
- The material does not describe the regression that led to the Android 14 revert da3689792a45. The reland has the same hunks.
- Where the componentName in PaymentAppInfo comes from (for example, whether an installed app controls it) is not shown in the material.
- The shape of input that made the old parse select a different component or user is not shown.
- The material does not show how DefaultAppPickerFragment feeds keys into setDefaultKey (UI selection only, or also persisted or external values).
- Whether getCandidates() sorts candidates after iterating, and whether mAppInfos is refreshed outside onAttach, is not shown.
- The behavior of PaymentBackend.setDefaultPaymentApp for an arbitrary component or user id (the EoP consequence) is not shown.