CVE-2026-28655 AI-generated analysis
platform/frameworks/base, core/java/android/widget/RemoteViews.java (RemoteViews parceling, ApplicationInfoCache, ReflectionAction)
Reworks RemoteViews parceling so that a single ApplicationInfoCache (typed list) is written once by the root RemoteViews and nested RemoteViews reference it by int id, and removes the Parcel.allowSquashing()/restoreAllowSquashing() wrapping of RemoteViews.writeToParcel together with the ALTERNATIVE_DEFAULT ReadWriteHelper trick that forced immediate (non-lazy) Bundle unparceling in ReflectionAction.
Root cause
Before the fix, RemoteViews.writeToParcel enabled Parcel.allowSquashing() for the whole write so that repeated ApplicationInfo objects in nested RemoteViews would be squashed, and each RemoteViews (MODE_NORMAL) wrote its full mApplication via ApplicationInfo.writeToParcel. Because squashing changes how Bundle contents are written, ReflectionAction's BUNDLE case installed a private Parcel.ReadWriteHelper (ALTERNATIVE_DEFAULT) around in.readBundle() purely to defeat lazy Bundle unparceling. The second commit's message states squashing 'doesn't work' for ApplicationInfo and that mApplicationInfoCache had been added as well, making squashing redundant. The combination of squashing on the writer side, per-RemoteViews ApplicationInfo serialization, and the ReadWriteHelper side effect on Bundle reads is the mechanism the fix removes; the material does not spell out the exact faulty read/write sequence.
How the fix works
- Commit aa6e28b4fd2d (Revert^2, applied first): RemoteViews.java ApplicationInfoCache is changed from a Map<Pair<String,Integer>,ApplicationInfo> to a List<ApplicationInfo> (mCachedApplicationInfos) with a Parcel constructor (parcel.createTypedArrayList(ApplicationInfo.CREATOR)) and writeToParcel (dest.writeTypedList). getOrPut/updateAndGetId now return an int index; findId matches on packageName (Objects.equals) and uid; get(int) returns null for id < 0 or id >= size. getPackageUserKey is deleted.
- Commit aa6e28b4fd2d: a new field mApplicationId is added and set alongside mApplication in RemoteViews(ApplicationInfo,int), the landscape/portrait constructor, the sized constructor, the copy constructor, the Parcel constructor for sized and landscape/portrait modes (updateAndGetId), and via getOrPut in configureDescendantsAsChildren and in the root-only method that recreates BitmapCache and ApplicationInfoCache. updateAppInfo now looks up via findId and stores via updateAndGetId (without assigning mApplicationId).
- Commit aa6e28b4fd2d: wire format change in RemoteViews.writeToParcel: in all three modes (MODE_NORMAL, MODE_HAS_SIZED_REMOTEVIEWS, landscape/portrait) the root writes mApplicationInfoCache.writeToParcel right after mBitmapCache.writeBitmapsToParcel; in MODE_NORMAL dest.writeInt(mApplicationId) replaces mApplication.writeToParcel. In the Parcel constructor the root reads mApplicationInfoCache = new ApplicationInfoCache(parcel) (children receive it via configureAsChild(rootData)), and MODE_NORMAL reads mApplicationId = parcel.readInt() and mApplication = mApplicationInfoCache.get(mApplicationId) instead of ApplicationInfo.CREATOR.createFromParcel.
- Commit ae484e3d7a20 (applied second; its pre-image blob f0e6c3e09518 is the post-image of aa6e28b4fd2d): RemoteViews.writeToParcel no longer calls dest.allowSquashing() / dest.restoreAllowSquashing(prevSquashingAllowed).
- Commit ae484e3d7a20: ReflectionAction(Parcel) BUNDLE case is reduced to this.value = in.readBundle(); the static ALTERNATIVE_DEFAULT Parcel.ReadWriteHelper and the hasReadWriteHelper/setReadWriteHelper handling are removed.
Completeness
- All three RemoteViews parcel modes are updated symmetrically on write and read; the cache is written only when mIsRoot and children reuse it via HierarchyRootData.
- ApplicationInfoCache.get(int) bounds-checks the id read from the parcel and returns null rather than throwing; callers that then use mApplication must tolerate null (the old getOrPut could also return null when packageName was null).
- ApplicationInfoCache(Parcel) uses createTypedArrayList, which can yield null list entries; findId dereferences cachedApplicationInfo.packageName without a null check, so a null entry would cause an exception in findId.
- Squashing is only removed from RemoteViews.writeToParcel; the material does not show whether other Parcelables in frameworks/base still use Parcel.allowSquashing.
- Lazy Bundle unparceling is now permitted again for ReflectionAction BUNDLE values; per the removed comment, the forced non-lazy read existed because of allowSquashing, so its removal is tied to squashing no longer being enabled by RemoteViews.
Behavior changes
- RemoteViews parcel wire format changes: the root writes a typed list of ApplicationInfo after the bitmap cache, and each MODE_NORMAL RemoteViews writes an int index instead of a full ApplicationInfo. Writer and reader must both use the new format.
- ApplicationInfo instances with the same packageName and uid are deduplicated into one cache slot; updateAndGetId replaces the existing slot, and on read all nested RemoteViews sharing an id get the identical ApplicationInfo instance.
- RemoteViews.writeToParcel no longer enables Parcel squashing itself.
- Bundles in ReflectionAction are read with plain readBundle() and may be unparceled lazily rather than immediately.
- mApplication becomes null if the parceled id is -1 or out of range for the cache.
Per major
- Android 14: same fix, two commits in android-security-14.0.0_r29: aa6e28b4fd2d (Revert^2 ApplicationInfoCache parceling, cherry-pick of 2137c24fb2d1) followed by ae484e3d7a20 (remove allowSquashing and the Bundle ReadWriteHelper hack).
- Android 15: adapted fix: 04faeb30ca94 in android-security-15.0.0_r17 applies the Revert^2 ApplicationInfoCache change with 15-specific context (mCollectionCache written/read alongside the caches, writeTypedObject/readTypedObject for mApplication replaced, HierarchyRootData with four arguments). The material does not show the squashing-removal commit for 15.
- Android 16: adapted fix: 6fe346abe6fb in android-security-16.0.0_r8 applies the squashing/Bundle-helper removal with 16-specific context (field named mValue, private writeToParcel(Parcel,int,SparseArray<Intent>) with mHasDrawInstructions). The material does not show the ApplicationInfoCache commit for 16.
- Android 17: adapted fix: b78e05338801 in android-security-17.0.0_r1 applies the squashing/Bundle-helper removal with 17-specific context (mValue, writeToParcel also writes mOriginalDensity). The material does not show the ApplicationInfoCache commit for 17.
Unknowns
- The exact faulty read/write sequence (how squashed ApplicationInfo plus lazy/non-lazy Bundle reads led to the EoP) is not described in the material.
- Whether the squashing-removal commit landed on Android 15, and whether the ApplicationInfoCache parceling commit was already present or separately landed on Android 16 and 17, is not shown; per-major facts report only partial presence (4/8 or 6/8 lines) of the patched region.
- Whether any other frameworks/base Parcelable still enables Parcel.allowSquashing is not shown.
- The name of the root-only method that recreates the caches and calls getOrPut is not shown in the diff.