{
 "month": "2026-03",
 "generated": "2026-10-06T12:05+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 39,
    "certain": 39,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2025-48631",
     "osv_id": "ASB-A-444671303",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a hard 4096 px per-dimension limit in LocalImageResolver.onHeaderDecoded so that decoding of oversized images is aborted (RuntimeException, surfaced as IOException from resolveImage) instead of being attempted at the risk of exhausting device memory.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit: one constant plus one guard at the top of onHeaderDecoded. Only the hunk line offset differs between 14 (line 249) and 15/16 (line 252). The test needs the binary resource test16000x16000.gif."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "34926e40b0d858fc7ed75d0a85017a08d3402b54",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-43766",
     "osv_id": "ASB-A-288144143",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes the Bluetooth stack start LE link encryption as soon as an ACL connection to an already-bonded LE device is established (in StackAclBtmAcl::btm_establish_continue). Follow-up commits suppress spurious SMP failure handling, fix the order of the L2CAP link-state update and save the peer CSRK with the correct security level.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the three Android 14 commits in this order. First 796182896347 (btm_acl.cc, encrypt on LE reconnect). Then b6008f926a4e (btm_ble.cc early return and btm_ble_complete_evt_ignore, plus l2c_ble.cc link_state ordering; backports of aosp/3424923, aosp/3420039 and aosp/3394039, which address regressions in the initial fix). Then f80c558b456d (smp_act.cc CSRK ordering, backport of aosp/3254899; its message says it must be included in the fix for b#288144143 to prevent regressions).",
       "The Android 14 version of the main fix is a relocation of ag/29056565, whose content is not shown. The signature of btm_establish_continue_from_address is unchanged, so its mocks in system/test/mock/mock_stack_acl.cc/.h (per index search) are not affected."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "796182896347292a3cd2757d2074c792117539c5",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "b6008f926a4e47f93e26ce98f663ca44a670eba5",
       "tag": "android-security-14.0.0_r19",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "f80c558b456d5e503521e85f5b7ee2ca9a0ace36",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "public_before_announcement_days": 90,
     "first_commit_before_announcement_days": 454,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-32313",
     "osv_id": "ASB-A-399155883",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Fixes Parcel::appendFrom to decide whether to grow the buffer based on the current write position (mDataPos) instead of mDataSize, and adds validation in UsageEvents(Parcel) and NotificationHistory(Parcel) that a parcel-supplied position value does not exceed the rebuilt parcel's data size before it is applied with setDataPosition.",
      "confidence": "medium",
      "backport_notes": [
       "On 14 the fix has three pieces: the libbinder appendFrom condition change plus the UsageEvents and NotificationHistory position checks.",
       "The native hunk changes a single condition line. In binderParcelUnitTest.cpp the new test sits after a different neighbouring test on each branch (AppendOverObject on 14, AppendPlainDataPartial on 15)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r27",
      "date": "2026-05-04",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d02a81da9715e23bda95703b74f3e20574ffa6b6",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "daa876427451f48414bc2122d20c3ed377b70778",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "4e28321ba1983c82588e4f3644aceda2ca291d94",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "059f3abcb66435fbe0ce2f1871946602e907752c",
       "tag": "android-security-14.0.0_r27",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "8afead9da1fafcca51f337c233302763bbc17ddf",
       "tag": "android-security-14.0.0_r27",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48544",
     "osv_id": "ASB-A-415783046",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the internal Bundle extra MediaProvider#INCLUDED_DEFAULT_DIRECTORIES. In its place, an explicit Optional<List<String>> parameter is passed through getQueryBuilder, appendAccessCheckQuery and AccessChecker.getWhereForConstrainedAccess. As a result, only the FUSE directory-rename path supplies the default-directory access SQL, and the contents of an extras Bundle no longer affect it.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix; the material shows no prerequisite commits. Because the getQueryBuilder signature changes, every call site in the target tree must be updated, and the compiler will flag any that are missed. appendAccessCheckQuery has one getWhereForConstrainedAccess call on Android 14 and two on Android 15 and 16; update all of them."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r22",
      "date": "2025-09-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r22",
      "date": "2025-09-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "f79b709905cf4495f2a4e2f5c2b2eeb123255a94",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 180,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48567",
     "osv_id": "ASB-A-377888957",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now NFD-normalizes file paths and strips Unicode default-ignorable code points (e.g. zero-width space) before the regex-based private app directory (Android/data, Android/obb) checks in isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse.",
      "confidence": "medium",
      "backport_notes": [
       "One commit per branch. It uses android.icu.lang UCharacter/UProperty and java.text.Normalizer. The 14 and 15 variants are unconditional. The 16 variant needs the aconfig flag and its generated Flags class, so when backporting from 16 either drop the Flags.enablePathSanitization() guard or make sure the flag is enabled (general platform knowledge: fixed-read-only aconfig flags are resolved at build time).",
       "On a customized MediaProvider, map every caller of the old isUidAllowedAccessToDataOrObbPathForFuse on purpose: the public @Keep wrapper normalizes the path, the new private isUidAllowedAccessToDataOrObbPath does not."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "e599e8d934f2f06968cf74ca74a174b26ee34330",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48568",
     "osv_id": "ASB-A-322157041",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes user switching to a secure user wait for a SystemUI lock-now callback, and makes SystemUI reject keyguard exit animations whose going-away request was made for a user other than the current one. This keeps the keyguard from being dismissed across a user switch.",
      "confidence": "medium",
      "backport_notes": [
       "The system_server and SystemUI parts belong together. UserController now blocks for up to 20 seconds waiting for a SystemUI callback and throws if it does not arrive. Check that any customized KeyguardViewMediator (OEM or Car) still reaches doKeyguardLocked/notifyLockNowCallback with the lockNow options.",
       "The Android 14 and 15 versions differ structurally (setCurrentUser vs before-switching/switching handlers, KeyguardUpdateMonitor.getCurrentUser vs SelectedUserInteractor). Use the version that matches the branch's KeyguardViewMediator."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-opus-5-5",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ec3f9fa9145c0b67da42de553d3541cb9ad6ac33",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48574",
     "osv_id": "ASB-A-428700812",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DisplayPolicy.validateAddingWindowLw now enforces MANAGE_ACTIVITY_TASKS against the explicitly supplied callingPid/callingUid when a window requests PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, instead of calling ActivityTaskManagerService.enforceTaskPermission(), which reads the Binder calling identity that has already been cleared at that point.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained change in DisplayPolicy.validateAddingWindowLw with no new dependencies; it relies on the method already receiving callingPid/callingUid and having mContext, both present on 14, 15 and 16. Google committed it separately per major; the context after the hunk differs on 16 (systemUiPermission), so check the context when applying. The test change relies on spyOn(mWm.mContext), which is present in the 14 test."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "04b124bf59a844786c73c145c5bb74ec06ec0377",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48577",
     "osv_id": "ASB-A-413380719",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "KeyguardViewMediator now re-checks the target user id at several points in the biometric-auth and keyguardDone/keyguardGoingAway dismissal chain. If the current user changed in between, it resets the keyguard instead of letting a pending dismissal started before the user switch complete.",
      "confidence": "high",
      "backport_notes": [
       "One commit per branch. The user lookup differs: KeyguardUpdateMonitor.getCurrentUser() on 14, mSelectedUserInteractor.getSelectedUserId() on 15/16. On 14, keyguardDonePending has an extra primaryAuth parameter. When porting to a 14-based tree, consider also taking the 15/16 mDismissToken and removeMessages(HIDE) additions in handleBeforeUserSwitching if the delayed dismiss() post exists there."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2acf4a1801c1c23b262112e8a6920982a0210281",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48578",
     "osv_id": "ASB-A-418225717",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes MediaProvider restore the caller identity it had cleared before an exception propagates: in MediaProvider#updateInternal when ContentUris.parseId throws NumberFormatException, and (on 15/16) in MediaProvider#getResultForGetMediaUri when an unsupported document authority causes an IllegalArgumentException.",
      "confidence": "high",
      "backport_notes": [
       "For a 14-based tree, apply the updateInternal hunk as in 12fbdbc6beda. Then check whether your MediaProvider#getResultForGetMediaUri has the authority check against MediaDocumentsProvider.AUTHORITY / EXTERNAL_STORAGE_PROVIDER_AUTHORITY after the identity is cleared. If it does, add restoreCallingIdentity(token) before the throw, as in the 15/16 commits. The two hunks use different APIs: restoreLocalCallingIdentity (LocalCallingIdentity token) in updateInternal and restoreCallingIdentity(token) in getResultForGetMediaUri.",
       "MediaProvider is a mainline (APEX) module, so the fix may also ship through a module update and not only through the platform security tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "12fbdbc6beda4f07db57b37753629a905f1cd162",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48579",
     "osv_id": "ASB-A-417195606",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now restores the cleared calling identity before rethrowing when ContentUris.parseId throws NumberFormatException in updateInternal (and, in the 15/16 commits, before throwing IllegalArgumentException for an unsupported authority in getResultForGetMediaUri), so these exceptions no longer leave MediaProvider's own identity in effect.",
      "confidence": "high",
      "backport_notes": [
       "Each branch has a single commit, reached through a chain of cherry-picks of one change (Change-Id Ia295e56530a44c437db3a159e97c12d1a239b547). On a 14-based tree, check whether getResultForGetMediaUri has the same authority-check throw after clearing identity. If it does, apply the restoreCallingIdentity(token) hunk from the 15/16 diffs, because the 14 commit omits it.",
       "The 14 hunk applies to MediaProvider.java around line 7548 (updateInternal). In 15/16 that hunk sits at 8225/8978 and the getResultForGetMediaUri hunk at 7097/7674. Both hunks are small and anchored by context."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "12fbdbc6beda4f07db57b37753629a905f1cd162",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48582",
     "osv_id": "ASB-A-369105011",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now records the uid of the app that created a bulk create request inside the PendingIntent extras for PermissionActivity, and PermissionActivity uses that uid, instead of Activity.getCallingPackage(), to decide whether user consent is needed and to which package URI permissions are granted.",
      "confidence": "high",
      "backport_notes": [
       "Single commit; on branches where MediaStore.EXTRA_CALLING_PACKAGE_UID does not yet exist (as on 14) the apex/framework MediaStore.java hunk must be included along with the MediaProvider.java and PermissionActivity.java changes."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "45db544a1f1a3ba731a92a64f34b94a13e48b7e4",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48602",
     "osv_id": "ASB-A-407562568",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "KeyguardViewMediator now drops any queued START_KEYGUARD_EXIT_ANIM message on user switch and, when the exit animation has been flagged as canceled, the post-traversal completion callback finishes the remote animation with the keyguard showing and forces the showing state instead of only logging and returning.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit per branch. The user-switch hook differs by release: setCurrentUser on 14 (flag already set in the isSecure branch) versus onBeforeUserSwitching/handleBeforeUserSwitching on 15/16 (flag set unconditionally); setShowingLocked takes a reason argument on 16. The test subclasses KeyguardViewMediator in the test constructor to override postAfterTraversal, so the constructor argument list must match the branch."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1c3c01a414a07f920b3085424c08b5fa734175e9",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48609",
     "osv_id": "ASB-A-414388731",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsProvider.deleteDataRows now verifies that the file path read from a part/drm row's _data column canonically resolves under the provider's \"parts\" directory before calling File.delete(), reusing the same check openFile already performed.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the only dependency is the pre-existing PARTS_DIR_NAME constant and openFile's existing getDir(PARTS_DIR_NAME, 0) usage. When porting, thread the Context parameter through deleteParts/deleteTempDrmData/deleteDataRows and every caller (delete() and the deleteMessages loop), since these are static methods without access to getContext()."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "1552b67718fcd8ec7f14663b3e3a56d7a44977c4",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2025-48619",
     "osv_id": "ASB-A-414387646",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ContentProvider's binder Transport now sanitizes the file open mode string in openFile and openAssetFile, silently removing the truncate ('t') and append ('a') flags when the write ('w') flag is absent, before both the permission check and the call into the provider.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained change to the Transport inner class of ContentProvider.java: add validateFileMode and pass its result to enforceFilePermission and to mInterface.openFile/openAssetFile. Across 14, 15 and 16 the only difference is the trace tag constant in the context lines (TRACE_TAG_DATABASE on 14, TRACE_TAG_ACTIVITY_MANAGER on 15 and 16)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "f54544f81d7dae9aa46880a024ec7c39ec9b8205",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48630",
     "osv_id": "ASB-A-455563813",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SkiaRenderEngine::drawLayersInternal now clips the canvas to the layer bounds before drawing blur regions when no rounded-corner clip is present, so a client-supplied blur region larger than the layer can no longer draw blurred content outside the layer's bounds.",
      "confidence": "high",
      "backport_notes": [
       "The hunk adds a 4-line else branch to the existing `if (!roundRectClip.isEmpty())` block in drawLayersInternal and has the same content on 14, 15 and 16, differing only in line offset. Forks with a customized blur path should make sure the clip is set inside the SkAutoCanvasRestore scope, before both the background blur draw and the blurRegions loop."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "6906658b06e354f4139dc67dc42fb2d6a74ca8d8",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48634",
     "osv_id": "ASB-A-406243581",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a permission-based sanitizer (sanitizePrivateFlags) to WindowManagerService relayout so that a client cannot newly set PRIVATE_FLAG_TRUSTED_OVERLAY / PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY without INTERNAL_SYSTEM_WINDOW, or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP without MANAGE_ACTIVITY_TASKS.",
      "confidence": "high",
      "backport_notes": [
       "The WMS hunk is self-contained: insert the sanitizePrivateFlags call directly after displayPolicy.adjustWindowParamsLw(win, attrs) in the relayout block and add the three helpers next to the existing sanitize* methods; the diff adds no imports to WMS, so PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY and PRIVATE_FLAG_TRUSTED_OVERLAY must already be statically imported there (the drag-and-drop flag is referenced as LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP). The 14 test hunk switches createWindow(...) to newWindowBuilder(...).build(); on a fork whose WindowTestsBase lacks newWindowBuilder keep createWindow.",
       "When porting, verify the hasPermission argument order against Context.checkPermission(String, int pid, int uid): the call sites pass (uid, pid) into a helper declared (pid, uid). The 17 branch differs by one line from the 14/15/16 patched region; compare that line before copying the 14 version verbatim."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c88804906997fa53368b3e1a7dfab92105906917",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48635",
     "osv_id": "ASB-A-446678690",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "TaskFragmentOrganizerController now refuses to register a TaskFragment organizer from a non-positive pid and only hands the real activity token to an organizer when both the activity's pid and uid match the organizer's, preventing an activity token from being leaked to another process.",
      "confidence": "medium",
      "backport_notes": [
       "Single-file change: add the `pid <= 0` guard before mTaskFragmentOrganizerState.put(...) in the registration path, and the `&& activity.getUid() == mOrganizerUid` clause in prepareActivityReparentedToTask. Both use fields and methods already present in the Android 14 code shown.",
       "When checking a branch, verify both hunks separately rather than relying on the line-presence count."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "36e65fce2d5119ad1d62b4696c003f93df649e52",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48641",
     "osv_id": "ASB-A-392699284",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the ST NFC AIDL HAL (platform/hardware/st/nfc, aidl/Nfc.h), the static eventCallback and dataCallback now copy the static shared_ptr Nfc::mCallback into a local std::shared_ptr under a new static pthread mutex sCallbackLock and call sendEvent/sendData on that local strong reference, instead of null-checking and dereferencing the shared static directly.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, header-only change to aidl/Nfc.h. Android 14 and 15 share the pre-fix blob 2ac623b; Android 16 differs only by the dump() declaration. When backporting, also check where mCallback is written (Nfc::open/close) to decide whether those writes should take sCallbackLock. This commit does not touch them, and the material does not show them."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/st/nfc",
       "sha": "502f1802c4e124e03aeccf1560128ea02d10e018",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/st/nfc"
     ]
    },
    {
     "cve": "CVE-2025-48642",
     "osv_id": "ASB-A-455777515",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes pvmfw's aarch64 data-cache maintenance from \"dc cvau\" (clean to Point of Unification) to \"dc cvac\" (clean to Point of Coherency) in the exit path jump_to_payload and in the flush_region helper, so that zeroed memory that could hold secrets reaches main memory.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with a mechanical change: replace every \"dc cvau\" with \"dc cvac\" in pvmfw's jump_to_payload and in the file holding flush_region on the target branch (pvmfw/src/helpers.rs on 14, vmbase/src/memory/util.rs on 15, libs/libvmbase/src/arch.rs on 16). Grep the whole Virtualization tree for \"cvau\", because the flush helpers live in different files across releases.",
       "No Fix-in-fork section was provided, so no per-file fork verdict can be reported."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Virtualization",
       "sha": "ed2818f8301e357e7e0be9790c5fbfe73898b9a6",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Virtualization"
     ]
    },
    {
     "cve": "CVE-2025-48644",
     "osv_id": "ASB-A-449181366",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Caps the cumulative size of resolved InputMethodInfo metadata attributes at MAX_METADATA_SIZE_BYTES via a TypedArrayWrapper/MetadataReadBytesTracker in the InputMethodInfo constructor, and moves the IInputMethodManager list-returning IPCs (getInputMethodList, getEnabledInputMethodList, getEnabledInputMethodSubtypeList) to blob-backed InputMethodInfoSafeList/InputMethodSubtypeSafeList parcelables to avoid oversized Binder transactions.",
      "confidence": "high",
      "backport_notes": [
       "Apply in order: 558ee85e1d4a (self-contained; uses the existing MAX_METADATA_SIZE_BYTES constant and the file-size validation block), then b9455ddd2a42, then bc52aa800354. bc52aa800354 depends on b9455ddd2a42: it refactors InputMethodInfoSafeList onto AbstractSafeList and removes empty(). The two SafeList commits change IInputMethodManager.aidl return types, so IInputMethodManagerGlobalInvoker, InputMethodManagerService and any other IInputMethodManager implementation in the target tree must be updated in the same step. Per index search, a ZeroJankProxy wrapper implementing IInputMethodManager methods exists; check whether the target branch has it, since the shown diffs do not touch it.",
       "For branches with the IME switcher revamp flag, keep the languageSettingsActivity read inside the TypedArrayWrapper block and add it to getMaxLength(), as in the 15/16 variants. On 14 that attribute is not read."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "558ee85e1d4ad12e51183eaeb35411aa9084538a",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "b9455ddd2a4225d32b2ef890ae44423af440287c",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "bc52aa8003546052e78e0c3fc62d9cba01b93a61",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48645",
     "osv_id": "ASB-A-443062265",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Wraps the pm.getText() call in DeviceAdminInfo.loadDescription in a try/catch that converts an OutOfMemoryError into a NotFoundException, so an oversized device-admin description resource no longer propagates an Error to the caller.",
      "confidence": "high",
      "backport_notes": [
       "The hunk is identical across 14, 15 and 16; only the line offset differs. It applies cleanly wherever loadDescription still calls pm.getText directly. If a tree has a loadDescriptionSafe helper, check whether callers were moved to it, since that is not part of this commit."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ecf88469d44f78128b174ffed730f6adb27e1d02",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48646",
     "osv_id": "ASB-A-457742426",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In ActivityStarter's FLAG_ACTIVITY_FORWARD_RESULT trampoline handling, the fix no longer always copies sourceRecord.launchedFromPackage into callingPackage. It now does so only after PackageManagerInternal.getPackageUid shows the package belongs to the same app as callingUid (UserHandle.isSameApp).",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. The code hunk is identical across 14, 15 and 16; only the line offsets differ. It uses mService.getPackageManagerInternalLocked(), PackageManagerInternal.getPackageUid and UserHandle.isSameApp, as the diff on each of these branches shows.",
       "The test relies on ActivityBuilder.setLaunchedFromPackage/setLaunchedFromUid and on mMockPackageManager in wmtests. Check that these helpers exist on the target branch."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ad0b8a7b63ac8bfea0808ff4ed8d543dfa10ba07",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-64783",
     "osv_id": "ASB-A-483074175",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Google fixed this High-severity information disclosure in external/dng_sdk by updating the whole bundled Adobe DNG SDK to version 1.7.1 build 2410 (61 files, about 2700 insertions) rather than with an isolated point fix.",
      "confidence": "low",
      "backport_notes": [
       "This is a whole-SDK upgrade, not a targeted patch. Backporting means taking the full 1.7.1/2410 tree while keeping Android-specific code (for example, the GOOGLE MODIFICATION blocks in dng_misc_opcodes.cpp). The API renames (dng_fingerprint::data to Data()/MutableData(), dng_md5_printer::Process to ProcessPtr, new WriteTIFF/WriteDNG parameters) will break any out-of-tree callers, which must be fixed in the same change.",
       "The same Change-Id was applied to 14, 15 and 16, and no per-branch adaptation is visible in the shown diffs, so the expected path is to cherry-pick the whole commit."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "d0bfe8eeee73a24e8fbe11c728c8e7ab17681cfb",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2025-64784",
     "osv_id": "ASB-A-483074618",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Google fixed this High-severity information-disclosure issue in platform/external/dng_sdk by replacing the vendored Adobe DNG SDK with upstream version 1.7.1 build 2410 (61 files, +2721/-694). This is a wholesale update with many parser, stream and fingerprinting changes rather than a single targeted check.",
      "confidence": "low",
      "backport_notes": [
       "This is a wholesale vendored-SDK bump, not a minimal patch, applied via the same change to 14, 15 and 16. Backporting means importing the full 61-file update while keeping Android's local GOOGLE MODIFICATION blocks (e.g. dng_misc_opcodes.cpp) and the qAndroid path in dng_file_stream.cpp. The 14 r29 tree also carries a later build (2502).",
       "Expect API breakage for any in-tree consumer that touches dng_fingerprint::data directly, calls dng_md5_printer::Process(ptr, len), instantiates dng_md5_printer, or calls WriteTIFF/WriteDNG with positional arguments beyond gainMapImage. A Soong build of all dependents is the quickest check (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "d0bfe8eeee73a24e8fbe11c728c8e7ab17681cfb",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2025-64893",
     "osv_id": "ASB-A-483075215",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix syncs platform/external/dng_sdk to Adobe DNG SDK 1.7.1 build 2410 (61 files, +2721/-694). Among many unrelated changes it adds a size cap on the new GainMapMetadata tag, rewrites the dng_opcode_GainMap::ProcessArea loop, adds new JXL chunked reader/writer classes with checked sizing, and encapsulates dng_fingerprint and dng_md5_printer. The material does not isolate which change closes the bulletin's information-disclosure bug.",
      "confidence": "low",
      "backport_notes": [
       "This is a single monolithic SDK sync, linked by Change-Id across 14, 15 and 16. The material does not show a small targeted patch that could be split out. It breaks source compatibility for out-of-tree users of dng_fingerprint::data, dng_md5_printer::Process(ptr, len) or the dng_md5_printer constructor. Positional callers of WriteTIFF/WriteTIFFWithProfile are also affected, because gainMapMetadataBlock is inserted before useHalfFloat.",
       "The 14 branch head (r29) already carries a later SDK build (2502), so compare a backport target against that newer state, not only against the 2410 diff."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "d0bfe8eeee73a24e8fbe11c728c8e7ab17681cfb",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2026-0005",
     "osv_id": "ASB-A-366405211",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds IActivityTaskManager.rebuildSystemLockTaskPinnedMode() (gated by enforceTaskPermission) that re-applies the recents, task-change-listener, status bar and keyguard state of app pinned mode for the existing locked task, has SystemUI's KeyguardViewMediator call it when ATMS reports LOCK_TASK_MODE_PINNED, and makes KeyguardServiceDelegate keep the keyguard enabled flag across a keyguard service disconnect, so that the device stays in app pinned mode upon a SystemUI crash.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit touching system_server (ATMS, LockTaskController, KeyguardServiceDelegate), SystemUI (KeyguardViewMediator) and the IActivityTaskManager AIDL, all in frameworks/base; they must ship together since SystemUI calls the new binder method. Android 14 uses ActivityTaskManager.getService() on the SystemUI side, 15/16 use the mActivityTaskManagerService field; pick the form matching the target.",
       "Forks with a customized KeyguardViewMediator should place the rebuild post at the same point (after the mBootSendUserPresent check); the LockTaskController helper relies on existing setStatusBarState/setKeyguardState/getStatusBarService helpers."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "9ff1c081e9f75cdd1ff86d413ac6d8146290604f",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0011",
     "osv_id": "ASB-A-454062218",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes Settings.enableSystemPackageLPw able to reuse the disabled PackageSetting of a shared-UID system app when its update is uninstalled. addPackageLPw now accepts the package even when registerExistingAppId fails for a shared-UID package, and the shared user app ID is restored on the reused setting, so the preloaded app is no longer re-scanned and installed as a new app with a possibly different UID.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. On branches with the 17-argument addPackageLPw (Android 14), apply the r25 diff. On branches with the compact signature that includes isSdkLibrary, use the 15/16 variant. Update every addPackageLPw caller and the MockSystem.kt mock argument count, or mockingservicestests will not compile."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "77d8e59a1af6a756c07fb2997e3de0a4897fa0d0",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0012",
     "osv_id": "ASB-A-392614656",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In SystemUI ExpandableNotificationRow.setHideSensitive, the decision to skip the hide-sensitive (public/private layout) animation and apply visibility and content alphas immediately is now taken also when the row is showing the public (redacted) layout and the showing layout did not change; before the fix this depended only on the `animated` flag (Android 14/15) or used a feature flag plus layout-unchanged (Android 16).",
      "confidence": "medium",
      "backport_notes": [
       "For trees based on Android 14/15 (no flag-gated skip), apply the two-part hunk: introduce isShowingLayoutNotChanged and the shouldSkipHideSensitiveAnimation OR-term on the `!animated` branch; for trees with the Flags.skipHideSensitiveNotifAnimation() form, only the one-line condition swap is needed. Single commit, no dependencies shown."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "334c15a0ebee4f864da7f8966d64ed67cd7808e8",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0013",
     "osv_id": "ASB-A-447135012",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PickActivity.setupLayout now calls setSelector(null) on the copied \"more apps\" intent so a caller-supplied selector can no longer redirect the activity launch DocumentsUI performs on the caller's behalf.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single-statement insertion after `moreApps.setPackage(null);` in PickActivity.setupLayout. It sits before the cross-profile code that differs between branches (a method call on 14, a field plus private-space checks on 15/16). DocumentsUI is also shipped as a mainline module (general platform knowledge), so check whether a given build gets the fix through a module update."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/DocumentsUI",
       "sha": "a48245c5bfa71b54684e11f71dd1a961e57b4667",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2026-0014",
     "osv_id": "ASB-A-443742082",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early `Process.isSdkSandboxUid(uid)` check to the attribution-tag validation helper in AppOpsService, so that for a non-null packageName the helper returns false for SDK sandbox UIDs instead of treating them as trusted/system.",
      "confidence": "medium",
      "backport_notes": [
       "A plain three-line insertion between the `packageName == null` return and the `appId < Process.FIRST_APPLICATION_UID` check in AppOpsService.java. It is identical across 14/15/16 apart from line offsets. `Process.isSdkSandboxUid` depends on the SDK sandbox UID range introduced with the SDK Runtime, so it is not available on branches before Android 13 (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7320b5316d7678c865ea32ba2d505f0a2660699f",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0015",
     "osv_id": "ASB-A-445917646",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppOpsService now only accepts proxied attribution tags from trusted proxies (flags without OP_FLAG_UNTRUSTED_PROXIED). It also checks that the proxy package really belongs to the proxy uid before treating it as system, stops the start dry run and finish paths from creating per-tag state, and finds the AttributedOp to finish by clientId and trust flag.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit, but the finish-path lookup differs by branch. Android 14 uses op.mAttributions directly; 15 and 16 use the virtual-device-aware map (getPersistentId vs getPersistentDeviceIdForOp). The dry run is a parameter on 14 and a separate method on 15/16.",
       "The new AttributedOp.hasInProgressEvent needs InProgressStartOpEvent to expose getClientId() and getFlags(). The Android 14 diff also adds a java.util.function.Consumer import that the new method does not use."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a404da3cbbc6ec5d779656f21665dbf5c614994e",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0020",
     "osv_id": "ASB-A-453649815",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Package manifest parsing now trims leading/trailing whitespace from <permission>, <permission-tree> and <permission-group> names in ParsedPermissionUtils, so a declared name is stored in trimmed form rather than as written in the manifest.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit fix. On trees based on Android 14 or earlier, apply it to services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java. Consider the 15/16 placement of the permission-tree trim (before the name checks) so those checks also run on the trimmed name.",
       "In parsePermissionGroup, keep setName(...trim()) inside the try block that holds the TypedArray, as in the fix."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d7a73fb8bfc2ea98dbb4b6fc8248b8a2f6115815",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0021",
     "osv_id": "ASB-A-430047417",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In packages/apps/Settings, AppInfoBase's caller permission check now, on devices with activity embedding (multi-pane) enabled, verifies that the SettingsActivity's initial calling package holds INTERACT_ACROSS_USERS_FULL instead of relying on getLaunchedFromUid, and the Settings$AppUsageAccessSettingsActivity intent-filter gets android:priority=\"1\".",
      "confidence": "medium",
      "backport_notes": [
       "Single commit, applied with the same content on 14, 15 and 16. Before applying it to a customized Settings fork, verify that the target tree has ActivityEmbeddingUtils.isEmbeddingActivityEnabled(Activity), SettingsActivity.getInitialCallingPackage(), the mPm field in AppInfoBase, and the TextUtils/Log/Manifest imports."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "19171d5c31ad2c8b8c990b22f7408a773306671a",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-0023",
     "osv_id": "ASB-A-459461121",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In PackageInstallerService.java, the session creation path now always clears PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE from the caller-supplied params.installFlags, then adds it back only when DevicePolicyManagerInternal.isUserOrganizationManaged(userId) is true.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained two-line hunk anchored on the `final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);` line in PackageInstallerService.java. Across 14, 15 and 16 the hunk is identical and only its offset differs."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a9550d0a75d0bdd23677e3183a53cf30e0af72cd",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0024",
     "osv_id": "ASB-A-326211886",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider's redaction decision for files opened via ContentResolver now also considers the UID passed in MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, so media opened by a picker on behalf of an app lacking ACCESS_MEDIA_LOCATION gets location metadata redacted.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It needs LocalCallingIdentity.fromExternal (called as fromExternal(getContext(), mUserCache, uid, null, null)), the mUserCache field, the PERMISSION_IS_REDACTION_NEEDED constant and MediaStore.EXTRA_MEDIA_CAPABILITIES_UID to exist. The opts Bundle must also be in scope in the open helper that calls isRedactionNeededForOpenViaContentResolver. MediaProvider is a mainline module (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "50387ce9d85a81fe82d6f03997d1a7100de31d86",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-0025",
     "osv_id": "ASB-A-433746973",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Notification.java now reads EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES with getParcelableArrayFromBundle(..., Bundle.class) instead of extras.getParcelableArray(..., Parcelable.class), so the element type is restricted to Bundle and arbitrary Parcelable element types are no longer accepted.",
      "confidence": "medium",
      "backport_notes": [
       "This is a single commit with the same Notification.java hunks on 14, 15 and 16, at different line offsets. It depends on the existing helper Notification.getParcelableArrayFromBundle(Bundle, String, Class), which all three branches use without changing it.",
       "Update every site: visitUris (two reads), hasImage, and the MessagingStyle restore code (two reads). Consider adding @Test to testNoUriGrantsForBadMessagesList so that it runs."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5e1623a880d2cbb2419e55db011755ca5e3d6bf6",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0026",
     "osv_id": "ASB-A-321711213",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return in PermissionManagerServiceImpl.removePermission() so that a non-dynamic permission is no longer removed from the permission registry after the \"not allowed to modify non-dynamic permission\" condition is detected.",
      "confidence": "high",
      "backport_notes": [
       "Single-line, self-contained change: insert `return;` after the Slog.wtf call inside the `if (!bp.isDynamic())` block of removePermission(), before mRegistry.removePermission(permName).",
       "No fork comparison section was provided in the material, so no per-file fork verdict can be reported."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r9",
      "date": "2024-06-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r9",
      "date": "2024-06-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6c22d6c1e69676c5c68d21928aa5486bfd1bd131",
       "tag": "android-security-14.0.0_r9",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 636,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0034",
     "osv_id": "ASB-A-428701593",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Two frameworks/base commits bound the per-user ManagedServices approval state: uninstalling a package now also removes its entries from mUserSetServices, and setPackageOrComponentEnabled refuses to add more than MAX_SERVICE_ENTRIES (100) entries per user to mApproved / mUserSetServices, returning a boolean so NotificationManagerService callers skip follow-up work when nothing changed.",
      "confidence": "high",
      "backport_notes": [
       "Apply the user-set cleanup commit (Ibd585dd1af) before the limit commit (Iddd8044997): the second commit's ManagedServices.java diff is based on the first's output (index 2d0ea16c0a45), and its test setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet relies on onPackagesChanged freeing mUserSetServices slots.",
       "The void-to-boolean signature change must be propagated to every subclass override of setPackageOrComponentEnabled (the assistant and listener overrides in NotificationManagerService.java) and to Mockito stubs in NotificationManagerServiceTest; an unstubbed mock returns false and would make the NMS callers return early, breaking existing tests."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "00fd7553361477967ec3557fbcdc574015ff6a74",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "cb1ce8020b4388333936bfaf6306a3433d27a89e",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0035",
     "osv_id": "ASB-A-418773439",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now rejects MediaStore CREATE_WRITE/DELETE/..._REQUEST calls with an IllegalArgumentException when any requested URI does not resolve to exactly one existing row at request time.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single self-contained hunk. It depends only on queryForSingleItem, clearLocalCallingIdentity/restoreLocalCallingIdentity and FileColumns._ID already existing in MediaProvider.java, and it applies identically across 14/15/16 apart from line offsets. MediaProvider is a mainline (APEX) module, so the fix may also arrive through a module update rather than a platform tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "003dd55147bf2dc2b524b898f08234100c425ea9",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r25"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "285936283"
     ],
     "commits": [
      {
       "project": "platform/external/sqlite",
       "sha": "db53e52eb3da36d844a2b463eb611a29055d9bdf",
       "subject": "Upgrade SQLite to version 3.42.0",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "378087531"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6a3e551a7d3f0fec2a42fbf140350e578d9da98f",
       "subject": "Prevent long press on profile notifs when locked",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "433251166"
     ],
     "commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "dab4de0e4b228ae77489b6394639a4ff566d73b4",
       "subject": "Truncate WindowInfo.name during parcelling.",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "435737668"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "e45172ad904be61cef3b853f616e0fc8861e51e1",
       "subject": "RESTRICT AUTOMERGE Update RuntimePermissionsUpgradeController to accept null PermGroups",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix",
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "435737668",
      "456903810"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "35b4e29a41962622913dde11a0ae5672f1a85fc7",
       "subject": "RESTRICT AUTOMERGE V2 Merge all permissions in a shared UID for every package in that UID",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix",
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "315320090"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "1b3339bc22ddfe1ed4839b7be518b405bbf50bc7",
       "subject": "RESTRICT AUTOMERGE Add SystemGalleryRoleBehavior",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix",
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "315320090",
      "418085016"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "d935a1e0b18dd79326c57e9bfe1db88635f3747a",
       "subject": "Revert^2 \"Ensure Gallery always shows as system fixed\"",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": []
      },
      {
       "project": "platform/packages/modules/Permission",
       "sha": "08781ef6be957c3f5b31645d823e740a3db0f7fa",
       "subject": "Revert \"Ensure Gallery always shows as system fixed\"",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    },
    {
     "bug_ids": [
      "388530367"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "d3b6d700d84f468826ad08ff34d78b0d8ca7477f",
       "subject": "Fix SQL injection vulnerability in SMS/MMS providers",
       "tag": "android-security-14.0.0_r25",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r25"
     ],
     "tag": "android-security-14.0.0_r25"
    }
   ],
   "coverage": {
    "n_ingested_tags": 30,
    "first_ingested_tag": "android-security-14.0.0_r1",
    "latest_ingested_tag": "android-security-14.0.0_r30",
    "window_base_tag": "android-14.0.0_r1",
    "window_start": "2023-10-04",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {
    "CVE-2025-48631": {
     "cve": "CVE-2025-48631",
     "summary": "Adds a hard 4096 px per-dimension limit in LocalImageResolver.onHeaderDecoded so that decoding of oversized images is aborted (RuntimeException, surfaced as IOException from resolveImage) instead of being attempted at the risk of exhausting device memory.",
     "component": "platform/frameworks/base, core/java/com/android/internal/widget/LocalImageResolver (MessagingStyle/ConversationStyle image loading via ImageDecoder)",
     "weakness": "Missing upper bounds check on image dimensions (uncontrolled resource consumption leading to DoS)",
     "root_cause": "LocalImageResolver.onHeaderDecoded is the ImageDecoder header listener used by resolveImage. It reads the image size from ImageDecoder.ImageInfo and goes straight to computing a ratio against maxWidth/maxHeight (DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480 when called from resolveImage(Uri, Context)). Nothing rejected very large declared dimensions first. The commit message notes that formats such as JPEG allow up to 65,535x65,535 pixels, which cannot realistically fit in memory, and says a notification caused a crashloop without this change.",
     "fix_analysis": [
      "core/java/com/android/internal/widget/LocalImageResolver.java: adds a new constant DEFAULT_DECODE_HARD_LIMIT_PX = 4096. Its comment says images larger than this will not even be decoded.",
      "LocalImageResolver.onHeaderDecoded: right after info.getSize(), if size.getWidth() > 4096 or size.getHeight() > 4096, it throws RuntimeException(\"Image dimensions (WxH) exceed the maximum allowed size.\"). This happens before the existing ratio computation, in the header callback that ImageDecoder calls once the image size is known.",
      "LocalImageResolver.resolveImage(Uri, Context) (shown in context) wraps any Exception from ImageDecoder.decodeDrawable in an IOException, so callers see the documented IOException.",
      "core/tests/coretests: adds res/drawable/test16000x16000.gif and the test LocalImageResolverTest.resolveImage_veryLargeResource_throwsException. The test expects an IOException when that resource is resolved through an android.resource:// Uri."
     ],
     "completeness": [
      "The check covers every decode that routes through onHeaderDecoded. The context shows resolveImage(Uri, Context) doing so; whether the other resolveImage overloads do is not shown.",
      "The limit is a fixed 4096 px per dimension with a strict greater-than test, so an image of exactly 4096x4096 is still accepted. The test uses 16000x16000.",
      "Only dimensions are checked. The fix does not bound total pixel count, animated frame count or file size, and the rest of onHeaderDecoded is unchanged.",
      "LocalImageResolver paths that do not decode through ImageDecoder with this listener are not touched. The test names mention a non-resizing fallback for non-bitmap resource icons, but the material does not show that code."
     ],
     "behavior_changes": [
      "Images with a width or height above 4096 px now fail to resolve. resolveImage throws an IOException that wraps a RuntimeException whose message includes the dimensions.",
      "Images at or below 4096 px in both dimensions are handled as before."
     ],
     "preconditions": [
      "An image (resource or content Uri) is resolved by LocalImageResolver.resolveImage, the MessagingStyle/ConversationStyle image loading helper, and its declared dimensions are large enough to exhaust memory when decoded.",
      "ImageDecoder must parse the header so that onHeaderDecoded is called. Per index search, ImageDecoder.callHeaderDecoded builds the ImageInfo from mWidth/mHeight and calls the listener."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 34926e40b0d8 in android-security-14.0.0_r24 (cherry-pick of 449f35b532d5); onHeaderDecoded hunk at line 249; 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base b645abd5b328 in android-security-15.0.0_r12; same change, onHeaderDecoded hunk at line 252; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base d6df825fda3a in android-security-16.0.0_r3; same change, onHeaderDecoded hunk at line 252; 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region is already present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit: one constant plus one guard at the top of onHeaderDecoded. Only the hunk line offset differs between 14 (line 249) and 15/16 (line 252). The test needs the binary resource test16000x16000.gif."
     ],
     "confidence": "high",
     "unknowns": [
      "Which process crashloops (the commit message only says a notification caused a crashloop) and the exact call chain from notification handling into LocalImageResolver.resolveImage.",
      "The rest of onHeaderDecoded and the other resolveImage overloads are not in the material, so it is not shown whether all overloads use this listener or why downscaling alone was not enough.",
      "How notification callers handle the new IOException.",
      "How the 4096 value was chosen, beyond the commit message's note that the limit may be raised in the future."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43766": {
     "cve": "CVE-2024-43766",
     "summary": "Makes the Bluetooth stack start LE link encryption as soon as an ACL connection to an already-bonded LE device is established (in StackAclBtmAcl::btm_establish_continue). Follow-up commits suppress spurious SMP failure handling, fix the order of the L2CAP link-state update and save the peer CSRK with the correct security level.",
     "component": "platform/packages/modules/Bluetooth, system/stack (ACL/BTM/SMP/L2CAP LE security)",
     "weakness": "Missing encryption of an LE link when reconnecting to a bonded device",
     "root_cause": "In system/stack/acl/btm_acl.cc, StackAclBtmAcl::btm_establish_continue only did BR/EDR-specific work after connection (packet types, link policy) and did nothing for the LE transport. When an LE ACL came up to a peer with a security record and a known LE link key, the stack did not start link encryption itself. Encryption depended on later security requests, such as the L2CAP LE security check in btm_ble.cc that sets BTM_BLE_SEC_ENCRYPT or returns L2CAP_LE_RESULT_INSUFFICIENT_ENCRYP. The commit message states that the LE link must be encrypted immediately on connection if the devices are already bonded. The bulletin classifies the issue as information disclosure (ID), High severity.",
     "fix_analysis": [
      "Commit 796182896347 (android-security-14.0.0_r15), system/stack/acl/btm_acl.cc: StackAclBtmAcl::btm_establish_continue gains an `else if (p_acl->is_transport_ble())` branch. It looks up the security record with btm_find_dev(p_acl->remote_addr). If there is no record, it logs a warning. If p_dev_rec->is_le_link_key_known(), it calls btm_ble_set_encryption(p_acl->remote_addr, BTM_BLE_SEC_ENCRYPT, role), where role is HCI_ROLE_CENTRAL or HCI_ROLE_PERIPHERAL depending on p_dev_rec->role_central. NotifyAclLinkUp(*p_acl) still runs afterwards. The commit message says this is a backport of ag/29056565, placed in a different location because the original relies on recent feature work.",
      "Commit b6008f926a4e (android-security-14.0.0_r19), system/stack/btm/btm_ble.cc: in btm_ble_set_encryption, case BTM_BLE_SEC_ENCRYPT with link_role == HCI_ROLE_CENTRAL, the function now returns BTM_SUCCESS early if p_rec->is_le_device_encrypted(). This avoids a redundant btm_ble_start_encrypt on a link that is already encrypted.",
      "Same commit, system/stack/btm/btm_ble.cc: adds a new static function btm_ble_complete_evt_ignore(p_dev_rec, p_data), called at the top of the SMP_COMPLT_EVT case in btm_proc_smp_cback. It applies when all of these hold: the SMP completion failed (reason != SMP_SUCCESS); the local device is peripheral (!role_central); btm_cb.pairing_bda matches neither bd_addr nor ble.pseudo_addr; the LE link key is known; and key_type != BTM_LE_KEY_NONE. In that case, if the device is already encrypted, the failure is ignored. If the reason is SMP_CONN_TOUT, the failure is also ignored and l2cu_start_post_bond_timer(p_dev_rec->ble_hci_handle) is called. When the failure is ignored, btm_proc_smp_cback returns BTM_SUCCESS without the normal completion handling. Per the code comment, this covers peripheral-role encryption requests that become SMP Security Requests, where some centrals do not handle the cross-over or the link drops.",
      "Same commit, system/stack/l2cap/l2c_ble.cc: l2cble_notify_le_connection now sets p_lcb->link_state = LST_CONNECTED before calling btm_establish_continue_from_address(bda, BT_TRANSPORT_LE) rather than after it.",
      "Commit f80c558b456d (android-security-14.0.0_r24), system/stack/smp/smp_act.cc: smp_proc_srk_info now calls smp_key_distribution_by_transport(p_cb, NULL) right after smp_update_key_mask. Previously the call came after the peer CSRK (le_key.pcsrk_key.sec_level = p_cb->sec_level) was built and saved through btm_sec_save_le_key(..., BTM_LE_KEY_PCSRK, ...). Per the commit message, the security level is set after LTK generation. Saving the CSRK first stored sec_level ZERO, which could clear the MITM_AUTH flag when the key was reloaded in the next session. The commit is a backport of aosp/3254899, included to prevent regressions described in b#428948282."
     ],
     "completeness": [
      "The new encryption trigger only applies in the LE transport branch of btm_establish_continue, and only when a security record exists and is_le_link_key_known() is true. If btm_find_dev returns nullptr, the code only logs a warning.",
      "Both roles go through the btm_ble_set_encryption call. In central role this leads to btm_ble_start_encrypt. In peripheral role it leads to an SMP Security Request, per the comment in btm_ble_complete_evt_ignore.",
      "The early return added in btm_ble_set_encryption is only on the HCI_ROLE_CENTRAL path. The peripheral path of BTM_BLE_SEC_ENCRYPT is unchanged.",
      "btm_ble_complete_evt_ignore only suppresses SMP failures in peripheral role, outside an active pairing, for bonded devices. Central-role failures still go through the existing completion handling.",
      "The fix does not change the service-level security checks, such as the L2CAP LE check in btm_ble.cc that returns L2CAP_LE_RESULT_INSUFFICIENT_ENCRYP.",
      "btm_establish_continue is reached from l2cble_notify_le_connection through btm_establish_continue_from_address. The material does not show whether other connection paths reach it."
     ],
     "behavior_changes": [
      "On every LE ACL connection to a device with a known LE link key, the stack now starts encryption right away instead of waiting for a service to request it. In central role it calls btm_ble_start_encrypt; in peripheral role it sends an SMP Security Request.",
      "btm_ble_set_encryption(BTM_BLE_SEC_ENCRYPT) in central role returns BTM_SUCCESS without starting encryption again when the device is already encrypted.",
      "Failed SMP_COMPLT_EVT events for bonded devices in peripheral role, with no pairing in progress, are now ignored in two cases. If the link is already encrypted, the failure is simply ignored. On SMP_CONN_TOUT, the failure is ignored and the L2CAP post-bond timer is started.",
      "In l2cble_notify_le_connection, the L2CAP link_state is set to LST_CONNECTED before btm_establish_continue_from_address runs.",
      "The peer CSRK is now saved after smp_key_distribution_by_transport, so the stored record carries the sec_level set at that point instead of zero."
     ],
     "preconditions": [
      "An LE ACL connection is established to a remote device that has a local security record with is_le_link_key_known() true (an LE-bonded device).",
      "Path shown in the material: l2cble_notify_le_connection -> btm_establish_continue_from_address -> StackAclBtmAcl::btm_establish_continue.",
      "The regression-handling path (btm_ble_complete_evt_ignore) requires the local device to be peripheral on that connection, with no pairing in progress for that address."
     ],
     "per_major": {
      "14": "Adapted fix. The main change sits in StackAclBtmAcl::btm_establish_continue (btm_acl.cc) in android-security-14.0.0_r15 because the original ag/29056565 relies on recent feature work. Regression backports (btm_ble.cc, l2c_ble.cc) landed in r19 and the CSRK ordering fix (smp_act.cc) in r24. At android-security-14.0.0_r29, 3/3 lines of the patched region are present.",
      "15": "Resolved in android-security-15.0.0_r12. The material shows only the CSRK ordering commit d259085c6103 (smp_act.cc, the same two-line move as on 14). Where the main encrypt-on-reconnect change landed on 15 is not shown. At android-security-15.0.0_r17, 8/8 lines of the patched region are present.",
      "16": "Resolved in android-security-16.0.0_r3 by commit 769a3dea8016. It removes the aconfig flag save_peer_csrk_after_ltk_gen (flags/pairing.aconfig) and makes the smp_proc_srk_info ordering unconditional in smp_act.cc. The main encrypt-on-reconnect change on 16 is not shown. At android-security-16.0.0_r8, 5/8 lines of the patched region are present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 0/3 lines of the patched region are present."
     },
     "backport_notes": [
      "Apply the three Android 14 commits in this order. First 796182896347 (btm_acl.cc, encrypt on LE reconnect). Then b6008f926a4e (btm_ble.cc early return and btm_ble_complete_evt_ignore, plus l2c_ble.cc link_state ordering; backports of aosp/3424923, aosp/3420039 and aosp/3394039, which address regressions in the initial fix). Then f80c558b456d (smp_act.cc CSRK ordering, backport of aosp/3254899; its message says it must be included in the fix for b#288144143 to prevent regressions).",
      "The Android 14 version of the main fix is a relocation of ag/29056565, whose content is not shown. The signature of btm_establish_continue_from_address is unchanged, so its mocks in system/test/mock/mock_stack_acl.cc/.h (per index search) are not affected."
     ],
     "confidence": "medium",
     "unknowns": [
      "The content and location of the original upstream change ag/29056565, and how the main encrypt-on-reconnect change was applied on Android 15 and 16 (only the CSRK-related commits are shown for those branches).",
      "What data could be exposed while the link is unencrypted (the bulletin only states type ID, High).",
      "Why only 5/8 lines of the patched region match at android-security-16.0.0_r8.",
      "Why the patched region is absent at android-security-17.0.0_r1.",
      "Whether LE connection paths other than l2cble_notify_le_connection reach btm_establish_continue."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-32313": {
     "cve": "CVE-2025-32313",
     "summary": "Fixes Parcel::appendFrom to decide whether to grow the buffer based on the current write position (mDataPos) instead of mDataSize, and adds validation in UsageEvents(Parcel) and NotificationHistory(Parcel) that a parcel-supplied position value does not exceed the rebuilt parcel's data size before it is applied with setDataPosition.",
     "component": "platform/frameworks/native libbinder Parcel; platform/frameworks/base core (android.app.usage.UsageEvents, android.app.NotificationHistory)",
     "weakness": "Incorrect bounds/capacity check (Parcel::appendFrom grows based on data size, not write position) combined with missing validation of an untrusted position value read from a Parcel",
     "root_cause": "In libs/binder/Parcel.cpp, Parcel::appendFrom checked `(mDataSize+len) > mDataCapacity` to decide whether to call growData(len). The fix makes the check depend on mDataPos, which implies the appended bytes go at the current data position. If mDataPos had been moved beyond mDataSize (for example by setDataPosition with an unchecked value), the old capacity test could pass even though the range at mDataPos + len went beyond mDataCapacity. On the Java side, UsageEvents(Parcel) and NotificationHistory(Parcel) read `positionInParcel` directly from the incoming blob. They passed it to mParcel.setDataPosition() without comparing it to mParcel.dataSize(), so a crafted parcel could leave the internal Parcel at an arbitrary data position.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::appendFrom (frameworks/native 4e28321ba198): the grow condition changes from `(mDataSize+len) > mDataCapacity` to `(mDataPos + len) > mDataCapacity`. A new test, binderParcelUnitTest.cpp TEST(Parcel, AppendWithBadDataPos), sets dataCapacity 8 and dataPosition 10000 and expects appendFrom(&p1, 0, 8) to return BAD_VALUE.",
      "core/java/android/app/usage/UsageEvents.java:UsageEvents(Parcel in) (frameworks/base d02a81da9715): after appendFrom and setDataSize(mParcel.dataPosition()), adds `if (positionInParcel > mParcel.dataSize()) throw new IllegalArgumentException(...)` before mParcel.setDataPosition(positionInParcel).",
      "core/java/android/app/NotificationHistory.java:NotificationHistory(Parcel in) (frameworks/base daa876427451): the same check, throwing IllegalStateException when positionInParcel > mParcel.dataSize(), before mParcel.setDataPosition(positionInParcel)."
     ],
     "completeness": [
      "The native change covers only Parcel::appendFrom. The diff touches no other Parcel write path, and the material does not show whether other writers compute growth from mDataSize.",
      "The Java checks guard only the upper bound (positionInParcel > dataSize), so positionInParcel == dataSize is accepted. The new condition does not reject negative values; the material does not show how setDataPosition handles them.",
      "Only UsageEvents and NotificationHistory, the two parcelables changed by the frameworks/base commits for this bug, get the position validation. Both use the same blob + listByteLength + positionInParcel layout. The material does not show whether other Parcelables use the same pattern.",
      "Per-major facts report the native patched region as 5/6 lines present at android-security-14.0.0_r29 and 15.0.0_r17. The Parcel.cpp code context supplied for r29 does not cover the appendFrom region, so the material cannot confirm the exact state of that line."
     ],
     "behavior_changes": [
      "UsageEvents(Parcel) now throws IllegalArgumentException(\"Obtained an invalid position value(...) from Parcel.\") when the embedded position exceeds the reconstructed parcel's data size.",
      "NotificationHistory(Parcel) now throws IllegalStateException with a similar message under the same condition.",
      "Parcel::appendFrom now decides growth from mDataPos + len instead of mDataSize + len; per the new unit test, an append at a position far beyond capacity returns BAD_VALUE. Per index search, errors reach callers such as android_os_Parcel_appendFrom (signalExceptionForError) and AParcel_appendFrom (PruneStatusT)."
     ],
     "preconditions": [
      "A Parcel is unmarshalled into UsageEvents or NotificationHistory through their Parcel constructors, with mEventCount/mHistoryCount > 0 and a crafted positionInParcel field.",
      "For the native flaw: a caller invokes Parcel::appendFrom on a Parcel whose data position was set beyond its data size. Per index search, appendFrom is reachable from Java via android_os_Parcel_appendFrom and from the NDK via AParcel_appendFrom.",
      "The material does not show which privileged process consumes the crafted parcelable (the EoP target)."
     ],
     "per_major": {
      "14": "Same fix. Three commits in android-security-14.0.0_r25: frameworks/native 4e28321ba198 (appendFrom), and frameworks/base d02a81da9715 (UsageEvents, RESTRICT AUTOMERGE) and daa876427451 (NotificationHistory). At r29 the native patched region is reported as 5/6 lines present, and both Java checks appear in the r29 context.",
      "15": "Same native fix: frameworks/native dc54178de228 in android-security-15.0.0_r13. It is the same one-line change at a different offset (hunk at line 542), with the test inserted after AppendPlainDataPartial. The material does not show Java-side commits for 15. At r17 the native patched region is 5/6 lines present.",
      "16": "The material shows frameworks/base 283bef4b2546 (NotificationHistory check, same diff as on 14) in android-security-16.0.0_r4. The native and UsageEvents commits for 16 are not shown. At r8 the patched region is 4/4 lines present.",
      "17": "Not affected per the bulletin. At android-security-17.0.0_r1 the patched region is 5/5 lines present."
     },
     "backport_notes": [
      "On 14 the fix has three pieces: the libbinder appendFrom condition change plus the UsageEvents and NotificationHistory position checks.",
      "The native hunk changes a single condition line. In binderParcelUnitTest.cpp the new test sits after a different neighbouring test on each branch (AppendOverObject on 14, AppendPlainDataPartial on 15)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which privileged consumer of UsageEvents/NotificationHistory parcels is the EoP target.",
      "The code path inside growData/continueWrite that yields BAD_VALUE in the new test; growData is not shown.",
      "Whether 15 and 16 also received the UsageEvents change, whether 15 received the NotificationHistory change, and whether 16 received the native change. Only the listed commits are shown.",
      "Why the native patched region is reported as 5/6 lines present on 14 r29 and 15 r17; the Parcel.cpp context supplied does not cover appendFrom.",
      "Whether the UsageEvents and NotificationHistory commits depend on the native change."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48544": {
     "cve": "CVE-2025-48544",
     "summary": "Removes the internal Bundle extra MediaProvider#INCLUDED_DEFAULT_DIRECTORIES. In its place, an explicit Optional<List<String>> parameter is passed through getQueryBuilder, appendAccessCheckQuery and AccessChecker.getWhereForConstrainedAccess. As a result, only the FUSE directory-rename path supplies the default-directory access SQL, and the contents of an extras Bundle no longer affect it.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider access checks (AccessChecker / getQueryBuilder)",
     "weakness": "Improper access control. An internal authorization signal was carried in a Bundle extra that callers could set, and the access-check SQL generator trusted it. The code relied on stripping the key at some entry points instead of using a trusted channel.",
     "root_cause": "MediaProvider defined a package-private Bundle key, INCLUDED_DEFAULT_DIRECTORIES (\"android:included-default-directories\"). The FUSE directory-rename path put it into qbExtras, filled from getIncludedDefaultDirectories(); the removed javadoc describes this as the System Gallery exception for DCIM, Pictures and Movies. AccessChecker.getWhereForDefaultDirectoryMatch(Bundle) read that string list from whatever extras Bundle reached getWhereForConstrainedAccess. For each directory it added a \"relative_path LIKE '<dir>/%'\" clause as an extra OR option in the constrained-access WHERE clause for the FILES case. The only protection was four extras.remove(INCLUDED_DEFAULT_DIRECTORIES) calls, including ones in query, delete and update. The commit message says the extra \"can be misused by setting it in the bundle\". Each added OR option widens the set of rows that match the constrained-access clause.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java: removes the INCLUDED_DEFAULT_DIRECTORIES constant, so the Bundle key no longer exists.",
      "src/com/android/providers/media/AccessChecker.java:getWhereForConstrainedAccess: the signature changes from (identity, uriType, forWrite, Bundle extras) to (identity, uriType, forWrite, Optional<List<String>> includedDefaultDirectoriesOptional). The default-directory SQL is added only when the Optional isPresent(). getWhereForDefaultDirectoryMatch now takes a List<String> instead of reading the list from a Bundle.",
      "src/com/android/providers/media/MediaProvider.java:getQueryBuilder / getQueryBuilderInternal / appendAccessCheckQuery: each gains the Optional<List<String>> parameter and passes it, not extras, to getWhereForConstrainedAccess. All other getQueryBuilder call sites, and getWhereClauseForMatchableVisibleFromFilePath, pass Optional.empty().",
      "src/com/android/providers/media/MediaProvider.java:updateDatabaseForFuseRename (5- and 6-argument overloads) and deleteForFuseRename: gain the Optional parameter and forward it to getQueryBuilder. The 4-argument overload and the oldPathGrantedUri rename path pass Optional.empty().",
      "src/com/android/providers/media/MediaProvider.java: the directory-rename loop no longer builds a qbExtras Bundle. It passes new Bundle() and Optional.of(getIncludedDefaultDirectories()) directly, which makes it the only caller that supplies a non-empty list.",
      "src/com/android/providers/media/MediaProvider.java: the four extras.remove(INCLUDED_DEFAULT_DIRECTORIES) / queryArgs.remove(...) calls are deleted because the key no longer exists.",
      "tests/src/com/android/providers/media/AccessCheckerTest.java: existing tests are updated to pass Optional.empty(). A new assertion checks that Optional.of(DCIM, Pictures, Movies) produces the three RELATIVE_PATH LIKE clauses for FILES."
     ],
     "completeness": [
      "The fix covers this mechanism. The Bundle key is deleted and the directory list is now carried only as a typed method parameter, so Bundle contents can no longer feed the default-directory clause. The signature change makes every getQueryBuilder call site choose a value explicitly, and all of them except the directory-rename loop pass Optional.empty().",
      "The diff does not change getIncludedDefaultDirectories() itself, and the material does not show how it is gated.",
      "getWhereForDefaultDirectoryMatch still builds each LIKE clause by concatenating the directory name into the SQL string. The input now comes only from the internal getIncludedDefaultDirectories() list.",
      "The qbExtras Bundle parameter remains on updateDatabaseForFuseRename and deleteForFuseRename. Other extras keys, such as QUERY_ARG_REDACTED_URI and QUERY_ARG_RELATED_URI, are still handled by the existing extras.remove pattern, which this fix does not change."
     ],
     "behavior_changes": [
      "A Bundle containing the key android:included-default-directories no longer has any effect on MediaProvider access checks, because the key no longer exists.",
      "No API or error-code changes. The System Gallery directory-rename behaviour is kept via Optional.of(getIncludedDefaultDirectories()).",
      "Internal signature changes: AccessChecker.getWhereForConstrainedAccess and MediaProvider.getQueryBuilder, getQueryBuilderInternal, appendAccessCheckQuery, updateDatabaseForFuseRename and deleteForFuseRename gain an Optional<List<String>> parameter."
     ],
     "preconditions": [
      "A caller that can pass a Bundle of extras into a MediaProvider operation that reaches getQueryBuilder with the extra still present. The material does not identify the exact entry point.",
      "The directory list is honoured in the FILES case of getWhereForConstrainedAccess, as the new AccessCheckerTest assertion shows. This applies to a caller without full collection access, whose rows are limited by the constrained-access clause.",
      "getWhereForConstrainedAccess is called from MediaProvider.appendAccessCheckQuery, which every getQueryBuilder user reaches, and from MediaProvider.getWhereClauseForMatchableVisibleFromFilePath (per diff and index search)."
     ],
     "per_major": {
      "14": "Same fix: f79b709905cf in android-security-14.0.0_r22 (cherry-pick of a631dca1a8c0). appendAccessCheckQuery has a single getWhereForConstrainedAccess call. At android-security-14.0.0_r29, 7 of 8 lines of the patched region are present.",
      "15": "Adapted fix: c34a7f642548 in android-security-15.0.0_r10. The logic is the same, but appendAccessCheckQuery has two getWhereForConstrainedAccess call sites (the isLatestSelectionOnlyRequired branches), and both switch to the Optional parameter. At android-security-15.0.0_r17, 7 of 8 lines of the patched region are present.",
      "16": "Adapted fix: a5f1f1827339 in android-security-16.0.0_r1, same shape as Android 15 (two call sites in appendAccessCheckQuery). At android-security-16.0.0_r8, 7 of 8 lines of the patched region are present.",
      "17": "Not affected (not listed by the bulletin). At android-security-17.0.0_r1, 7 of 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-commit fix; the material shows no prerequisite commits. Because the getQueryBuilder signature changes, every call site in the target tree must be updated, and the compiler will flag any that are missed. appendAccessCheckQuery has one getWhereForConstrainedAccess call on Android 14 and two on Android 15 and 16; update all of them."
     ],
     "confidence": "high",
     "unknowns": [
      "Which MediaProvider entry point let a caller-supplied Bundle containing INCLUDED_DEFAULT_DIRECTORIES reach getQueryBuilder without the key being stripped.",
      "Whether getQueryBuilder call sites without a strip call (for example the clearLocalCallingIdentity wrapper, the playlist-members delete, and the read/write access probes) received caller-controlled extras.",
      "How getIncludedDefaultDirectories() is gated (not shown in the material).",
      "Whether any test beyond the AccessCheckerTest update exercises the end-to-end entry point."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48567": {
     "cve": "CVE-2025-48567",
     "summary": "MediaProvider now NFD-normalizes file paths and strips Unicode default-ignorable code points (e.g. zero-width space) before the regex-based private app directory (Android/data, Android/obb) checks in isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse.",
     "component": "platform/packages/providers/MediaProvider, private app directory path access checks (MediaProvider.java, util/FileUtils.java)",
     "weakness": "Path validation bypass: input not normalized (Unicode default-ignorable code points not removed) before regex-based path checks",
     "root_cause": "The commit message says the lower file system ignores certain characters when saving files, and that malicious apps use these ignorable characters to get past the regex checks that protect private app directories. In isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse, the raw path string was passed to extractPathOwnerPackageName, isExternalMediaDirectory and isCallingIdentityAllowedAccessToDataOrObbPath(extractRelativePathWithDisplayName(path)). A path with default-ignorable code points (the test uses a zero-width space inside 'Android') could therefore fail to match as a private directory path, while the file system treats it as the real one.",
     "fix_analysis": [
      "FileUtils.java: adds public static normalizeAndFilterDefaultIgnorableCodepoints(String path). For null or empty input it returns the input. Otherwise it applies java.text.Normalizer NFD and checks each code point with android.icu.lang.UCharacter.hasBinaryProperty(UProperty.DEFAULT_IGNORABLE_CODE_POINT). If any match, it rebuilds the string without them; if none match, it returns the NFD-normalized string.",
      "MediaProvider.java:isPrivatePackagePathNotAccessibleByCaller: the first statement is now path = FileUtils.normalizeAndFilterDefaultIgnorableCodepoints(path), so extractPathOwnerPackageName, isExternalMediaDirectory and the uid check all see the sanitized path. The final call changed from isUidAllowedAccessToDataOrObbPathForFuse to the new private isUidAllowedAccessToDataOrObbPath, which receives the already normalized path.",
      "MediaProvider.java:isUidAllowedAccessToDataOrObbPathForFuse (public, @Keep): is now a wrapper that normalizes the path and passes it to the new private isUidAllowedAccessToDataOrObbPath(uid, path). That private method holds the previous body (clearLocalCallingIdentity, then isCallingIdentityAllowedAccessToDataOrObbPath(extractRelativePathWithDisplayName(path)), then restoreLocalCallingIdentity).",
      "MediaProvider.java:shouldBypassFuseRestrictions: the call changed from isUidAllowedAccessToDataOrObbPathForFuse to the private, non-normalizing isUidAllowedAccessToDataOrObbPath, which receives the raw filePath.",
      "FileUtils.java:extractRelativePath: 'final String path' changed to 'String path'. No functional change in the shown hunk.",
      "FileUtilsTest.java: adds testNormalizeAndFilterDefaultIgnorableCodepoints, annotated SdkSuppress minSdkVersion S. It checks that a zero-width space inside 'Android' and the U+FE0F variation selector are removed."
     ],
     "completeness": [
      "Normalization is added at two points: isPrivatePackagePathNotAccessibleByCaller (before all of its sub-checks) and the public @Keep isUidAllowedAccessToDataOrObbPathForFuse (before it delegates).",
      "shouldBypassFuseRestrictions now passes the raw filePath to the non-normalizing isUidAllowedAccessToDataOrObbPath. The material shows neither whether its callers normalize filePath nor what effect a non-normalized path has there.",
      "Only default-ignorable code points are removed, after NFD normalization. The material does not show which characters the lower file system actually ignores.",
      "Other path helpers in FileUtils (e.g. extractRelativePath, which only got a cosmetic change) are not normalized by this commit. Whether they feed security decisions is not shown.",
      "On Android 16 the sanitization does nothing unless the aconfig flag enable_path_sanitization is enabled. On 14 and 15 it runs unconditionally."
     ],
     "behavior_changes": [
      "Paths passed to the two normalizing checks are NFD-normalized and stripped of default-ignorable code points before evaluation. A path that contains such code points inside 'Android/data' or 'Android/obb' is now evaluated as the matching private directory path.",
      "NFD normalization is applied even when no ignorable code points are present, so the string that is checked can differ from the input for precomposed characters.",
      "Android 16 only: a new fixed-read-only aconfig flag, com.android.providers.media.flags.enable_path_sanitization, gates the behavior."
     ],
     "preconditions": [
      "A path reaches isPrivatePackagePathNotAccessibleByCaller or isUidAllowedAccessToDataOrObbPathForFuse in MediaProvider. The callers of these methods are not shown in the material.",
      "The path contains Unicode default-ignorable code points in the segments that the private-directory checks match.",
      "Android 16: the enable_path_sanitization flag must be enabled for the fix to be active."
     ],
     "per_major": {
      "14": "Fixed by e599e8d934f2 in android-security-14.0.0_r25 (security-aosp-udc-staging). This is a combined cherry-pick; the commit message references ag/33577707 and ag/33920560. Sanitization is unconditional: the code has no flag check despite the Flag: trailer. At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "Same fix, 2f45f53ce92c in android-security-15.0.0_r13 (security-aosp-24Q3-staging). The MediaProvider.java and FileUtils.java changes are the same as on 14, and sanitization is unconditional. The test also carries @EnableFlags(Flags.FLAG_ENABLE_PATH_SANITIZATION), although FileUtils on this branch has no flag check. At android-security-15.0.0_r17, 6/8 lines of the patched region are present.",
      "16": "Adapted fix, 237fb89f5325 in android-security-16.0.0_r4 (security-aosp-25Q2-staging). It adds the aconfig flag enable_path_sanitization (is_fixed_read_only) to mediaprovider_flags.aconfig, and normalizeAndFilterDefaultIgnorableCodepoints returns its input unchanged when Flags.enablePathSanitization() is false. The hunk that replaces the call with filePath differs: the result is assigned to a boolean isUidAllowedAccessToDataOrObbPath next to owned-photos logic. The test uses SetFlagsRule. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "Not affected (not listed by the bulletin). At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "One commit per branch. It uses android.icu.lang UCharacter/UProperty and java.text.Normalizer. The 14 and 15 variants are unconditional. The 16 variant needs the aconfig flag and its generated Flags class, so when backporting from 16 either drop the Flags.enablePathSanitization() guard or make sure the flag is enabled (general platform knowledge: fixed-read-only aconfig flags are resolved at build time).",
      "On a customized MediaProvider, map every caller of the old isUidAllowedAccessToDataOrObbPathForFuse on purpose: the public @Keep wrapper normalizes the path, the new private isUidAllowedAccessToDataOrObbPath does not."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which exact characters the lower file system ignores; the material only says 'certain characters'.",
      "Who calls the @Keep isUidAllowedAccessToDataOrObbPathForFuse (e.g. native FUSE code); not shown.",
      "Whether filePath reaching shouldBypassFuseRestrictions is normalized by its callers, and what passing a non-normalized path to isCallingIdentityAllowedAccessToDataOrObbPath does there.",
      "The tests testIsDataOrObbRelativePathWithZwsChars_flagEnabled and _flagDisabled named in the commit message are not in the diff, and the contents of the original main CLs are not shown.",
      "On Android 15 the test's @EnableFlags(Flags.FLAG_ENABLE_PATH_SANITIZATION) is added without visible imports or a flag definition; whether the flag exists on that branch is not shown.",
      "The default value or release-config state of enable_path_sanitization in Android 16 builds is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48568": {
     "cve": "CVE-2025-48568",
     "summary": "Makes user switching to a secure user wait for a SystemUI lock-now callback, and makes SystemUI reject keyguard exit animations whose going-away request was made for a user other than the current one. This keeps the keyguard from being dismissed across a user switch.",
     "component": "platform/frameworks/base, ActivityManager UserController and SystemUI keyguard (KeyguardViewMediator, KeyguardUnlockAnimationController)",
     "weakness": "Race condition / improper state validation across user switch (keyguard bypass)",
     "root_cause": "UserController's lockDeviceNowAndWaitForKeyguardShown returned early if WindowManager already reported the keyguard as locked. Otherwise it waited for a generic ScreenObserver keyguard-shown event. When the user switch UI was enabled, completeUserSwitch also called dismissKeyguard for non-secure target users. In SystemUI, KeyguardViewMediator did not record which user a keyguardGoingAway request belonged to. As the added test shows, a going-away request made for one user could lead to an exit animation after a switch to another, secure user, and that animation could hide the keyguard. doKeyguardLocked also short-circuited (reset only) when the keyguard was showing but already going away. setCurrentUser did not cancel pending DISMISS/HIDE work or force the keyguard to show for secure users.",
     "fix_analysis": [
      "KeyguardManager: adds the hidden constant LOCK_ON_USER_SWITCH_CALLBACK (\"onSwitchCallback\").",
      "UserController.Injector.lockDeviceNowAndWaitForKeyguardShown: removes the isKeyguardLocked() early return and the ScreenObserver. It now calls getWindowManager().lockNow(bundle) with an IRemoteCallback stored under LOCK_ON_USER_SWITCH_CALLBACK and waits on a latch. If no callback arrives within 20 seconds it throws a RuntimeException (the commit message describes this as crashing the system).",
      "UserController.completeUserSwitch: removes the await chain, the Injector.dismissKeyguard step and DISMISS_KEYGUARD_TIMEOUT_MS. It now only dismisses the user switch dialog (when the switch UI is enabled) and then sends REPORT_USER_SWITCH_COMPLETE_MSG.",
      "KeyguardViewMediator.doKeyguardLocked: when options carry the callback binder, it registers a LockNowCallback for the current user. The early-return paths (externally disabled, already showing, not provisioned, lockscreen disabled) call notifyLockNowCallback. If the keyguard is showing but going away, it no longer short-circuits and re-shows the keyguard instead.",
      "KeyguardViewMediator.updateActivityLockScreenState (when showing) and handleShow (not-system-ready path) call notifyLockNowCallback. notifyLockNowCallback sends the result only for callbacks whose user id matches the current user.",
      "KeyguardViewMediator.setCurrentUser: on a real user change, it removes pending mDismissToken, DISMISS and HIDE messages, resets keyguard-done state, adjusts the status bar and clears the going-away state. For secure users it sets mIsKeyguardExitAnimationCanceled and pending lock, calls ActivityTaskManager setLockScreenShown(true, ...) and then doKeyguardLocked(null). For other users it resets state.",
      "KeyguardViewMediator: records mGoingAwayRequestedForUserId in mKeyguardGoingAwayRunnable and showSurfaceBehindKeyguard. On a user mismatch, handleStartKeyguardExitAnimation marks the exit animation canceled, finishes the remote animation, clears mHiding, and then either re-locks (secure user) or resets and dismisses. On a mismatch, handleCancelKeyguardExitAnimation sets pending lock.",
      "KeyguardViewMediator.exitKeyguardAndFinishSurfaceBehindRemoteAnimation: returns early if mIsKeyguardExitAnimationCanceled is set, both before and inside the post-traversal step. In the post-traversal step it re-locks if the device is not interactive and no lock is pending. The parameter is renamed from cancelled to showKeyguard. finishSurfaceBehindRemoteAnimation now notifies KeyguardUnlockAnimationController.",
      "KeyguardViewMediator.dismiss ignores requests while the keyguard is going away. The old onUserSwitching/onUserSwitchComplete KeyguardUpdateMonitor callbacks are replaced by a UserTracker onUserChanged callback. If the user is still current and non-secure, it dismisses after a 500 ms delay tied to mDismissToken.",
      "KeyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(showKeyguard): forces the surface behind to full alpha and sets the launcher unlock amount only when showKeyguard is false."
     ],
     "completeness": [
      "Covers both sides: the system_server user-switch handshake (UserController) and the SystemUI keyguard state machine (user validation for going-away and exit animations).",
      "The user-id validation applies to the legacy keyguardGoingAway path in KeyguardViewMediator. On Android 15 the checks are skipped when KeyguardWmStateRefactor is enabled. The shown part of the diff does not change that path (WmLockscreenVisibilityManager), but the diff is truncated.",
      "Per index search, CarKeyguardViewMediator (packages/apps/Car/SystemUI) extends KeyguardViewMediator. This commit does not touch it, and the material does not show whether it overrides the changed methods."
     ],
     "behavior_changes": [
      "system_server throws a RuntimeException if SystemUI does not deliver the LOCK_ON_USER_SWITCH_CALLBACK result within 20 seconds of lockNow during a user switch.",
      "lockDeviceNowAndWaitForKeyguardShown no longer skips lockNow when the keyguard already reports as locked.",
      "UserController no longer dismisses the keyguard for non-secure users. SystemUI dismisses it 500 ms after the user change, if the user is still current and non-secure.",
      "Exit animations whose going-away request was made for a different user are aborted, and the keyguard is re-shown or re-evaluated.",
      "dismiss() calls are ignored while the keyguard is going away."
     ],
     "preconditions": [
      "Device has multiple users and a user switch to a secure (PIN/pattern/password) user occurs.",
      "A keyguard going-away request or unlock/exit animation is in progress or pending around the time of the switch (as in the added tests)."
     ],
     "per_major": {
      "14": "Fixed by frameworks/base ec3f9fa9145c in android-security-14.0.0_r24. The lock-on-switch logic is in setCurrentUser, with a UserTracker onUserChanged callback. The callback handshake is in doKeyguardLocked and notifyLockNowCallback. At r29, 6 of the 8 patched-region lines are present.",
      "15": "Adapted fix bec2efb2d714 in android-security-15.0.0_r12. The shown diff adds UserTracker.isUserSwitching and the BEFORE_USER_SWITCHING/USER_SWITCHING handlers (handleBeforeUserSwitching, handleUserSwitching). It uses mSelectedUserInteractor and gates the user-id checks on !KeyguardWmStateRefactor.isEnabled(). Unlike 14, its handleUserSwitchComplete dismisses (shows the bouncer) for secure users. All 8 patched-region lines are present at r17.",
      "16": "Not affected (not listed by the bulletin). 6/8 patched-region lines present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). 6/8 patched-region lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The system_server and SystemUI parts belong together. UserController now blocks for up to 20 seconds waiting for a SystemUI callback and throws if it does not arrive. Check that any customized KeyguardViewMediator (OEM or Car) still reaches doKeyguardLocked/notifyLockNowCallback with the lockNow options.",
      "The Android 14 and 15 versions differ structurally (setCurrentUser vs before-switching/switching handlers, KeyguardUpdateMonitor.getCurrentUser vs SelectedUserInteractor). Use the version that matches the branch's KeyguardViewMediator."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not describe the exact trigger or attack scenario.",
      "The material does not show why Android 14 r29 has only 6 of the 8 patched-region lines (later rework or partial revert).",
      "The material does not show whether the KeyguardWmStateRefactor path on 15 needs an equivalent check.",
      "The last 175 lines of the Android 15 diff are truncated, including possible setCurrentUser or other changes.",
      "The material does not show whether CarKeyguardViewMediator or OEM subclasses override the changed methods."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-opus-5-5",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48574": {
     "cve": "CVE-2025-48574",
     "summary": "DisplayPolicy.validateAddingWindowLw now enforces MANAGE_ACTIVITY_TASKS against the explicitly supplied callingPid/callingUid when a window requests PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, instead of calling ActivityTaskManagerService.enforceTaskPermission(), which reads the Binder calling identity that has already been cleared at that point.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/DisplayPolicy.java)",
     "weakness": "Permission check performed against the wrong identity (Binder identity already cleared), making the check ineffective for the real caller",
     "root_cause": "In DisplayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid), the branch for attrs.privateFlags containing PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP called the static ActivityTaskManagerService.enforceTaskPermission(\"DisplayPolicy\"). Per the commit message, that helper uses the Binder calling uid/pid, which has been cleared before validateAddingWindowLw() is invoked, so the check did not evaluate the window's real caller. The sibling checks in the same method (PRIVATE_FLAG_TRUSTED_OVERLAY, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_NAVIGATION_BAR) use mContext.enforcePermission(..., callingPid, callingUid, ...) with the passed-in identity; this branch did not.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/DisplayPolicy.java:validateAddingWindowLw: replaces ActivityTaskManagerService.enforceTaskPermission(\"DisplayPolicy\") with mContext.enforcePermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS, callingPid, callingUid, \"DisplayPolicy\"), so the permission is evaluated against the pid/uid parameters handed to the method rather than the cleared Binder identity. This aligns the branch with the neighbouring PRIVATE_FLAG_TRUSTED_OVERLAY and status-bar type checks in the same method.",
      "services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java:testPrivateInterceptGlobalDragDropFlagChecksPermission: the test (which spies mWm.mContext) now verifies enforcePermission(eq(MANAGE_ACTIVITY_TASKS), anyInt(), anyInt(), any()) is called, instead of verifying mWm.mAtmService.enforceTaskPermission(any())."
     ],
     "completeness": [
      "Covers the single PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP check in DisplayPolicy.validateAddingWindowLw; this is the only call site of the flawed static check shown in the material.",
      "The other permission checks in validateAddingWindowLw (PRIVATE_FLAG_TRUSTED_OVERLAY with INTERNAL_SYSTEM_WINDOW; TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_NAVIGATION_BAR with STATUS_BAR_SERVICE) already used mContext.enforcePermission with callingPid/callingUid at android-security-14.0.0_r29 and are unchanged.",
      "The fix does not change where or how the Binder identity is cleared on the add-window path; it only makes this check use the explicit identity. Whether other checks downstream of the identity clearing rely on Binder.getCallingUid() is not shown in the material."
     ],
     "behavior_changes": [
      "Adding a window whose LayoutParams.privateFlags include PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP now throws a SecurityException from mContext.enforcePermission unless the supplied callingPid/callingUid holds android.permission.MANAGE_ACTIVITY_TASKS; previously the check was evaluated against the (cleared) Binder identity.",
      "The permission check is now specifically MANAGE_ACTIVITY_TASKS; the exact set of permissions accepted by the former ActivityTaskManagerService.enforceTaskPermission() is not shown in the material, so callers relying on any alternative permission accepted there could now be rejected."
     ],
     "preconditions": [
      "A process adds a window with PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP set in WindowManager.LayoutParams.privateFlags, reaching DisplayPolicy.validateAddingWindowLw.",
      "Per the commit message, validateAddingWindowLw is reached after the Binder calling identity has been cleared; the exact caller that clears identity is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 04b124bf59a8 in android-security-14.0.0_r24 (cherry-pick of 467b18585de4); DisplayPolicy.java hunk at line 1009 plus DragDropControllerTests update; the patched code is visible in the android-security-14.0.0_r29 code context (resolver reports 5/7 lines of the patched region present).",
      "15": "same fix: platform/frameworks/base e6e8c87c3c99 in android-security-15.0.0_r12; identical DisplayPolicy.java change (hunk at line 1060) and identical test change; 5/7 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 1cfd8237b5a8 in android-security-16.0.0_r3; identical DisplayPolicy.java change (hunk at line 1071, followed by 'final String systemUiPermission =' instead of 'switch (attrs.type)') and identical test change; 5/7 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; 5/7 lines of the patched region are present at android-security-17.0.0_r1. No separate fix commit for 17 is shown in the material."
     },
     "backport_notes": [
      "Single self-contained change in DisplayPolicy.validateAddingWindowLw with no new dependencies; it relies on the method already receiving callingPid/callingUid and having mContext, both present on 14, 15 and 16. Google committed it separately per major; the context after the hunk differs on 16 (systemUiPermission), so check the context when applying. The test change relies on spyOn(mWm.mContext), which is present in the 14 test."
     ],
     "confidence": "high",
     "unknowns": [
      "Where on the add-window path the Binder calling identity is cleared and how callingPid/callingUid are obtained before validateAddingWindowLw is invoked (not shown).",
      "The exact permissions accepted by the former ActivityTaskManagerService.enforceTaskPermission(), so whether the new MANAGE_ACTIVITY_TASKS-only check narrows legitimate callers cannot be determined from the material.",
      "What privileges PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP confers at runtime (the material only shows testPrivateInterceptGlobalDragDropFlagBehaviour, which exercises drag-start delivery to invisible windows and drop handling).",
      "Which 2 of the 7 patched-region lines are reported missing at the latest security tags."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48577": {
     "cve": "CVE-2025-48577",
     "summary": "KeyguardViewMediator now re-checks the target user id at several points in the biometric-auth and keyguardDone/keyguardGoingAway dismissal chain. If the current user changed in between, it resets the keyguard instead of letting a pending dismissal started before the user switch complete.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardViewMediator)",
     "weakness": "Race condition / time-of-check to time-of-use on the current user id across asynchronously scheduled keyguard dismissal steps (missing re-validation of user)",
     "root_cause": "In packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java, the dismissal pipeline checked the target user only at the entry points: keyguardDone and keyguardDonePending compared targetUserId with KeyguardUpdateMonitor.getCurrentUser(). After that, it tracked the request with a boolean mKeyguardDonePending that held no user, and passed a single shared Runnable, mHideAnimationFinishedRunnable, to startPreHideAnimation. readyForKeyguardDone and tryKeyguardDone also carried no user. Per the commit message, if the user changed while these deferred steps were in flight, the prior keyguard could be dismissed. In addition, mUpdateCallback.onBiometricAuthenticated did not check that the authenticated userId was the current user. The mUiBgExecutor body of mKeyguardGoingAwayRunnable also called ActivityTaskManager.getService().keyguardGoingAway() without re-checking that mGoingAwayRequestedForUserId still matched the current user.",
     "fix_analysis": [
      "KeyguardViewMediator.java:mUpdateCallback.onBiometricAuthenticated: a new early return with a warning log when userId != KeyguardUpdateMonitor.getCurrentUser(). Biometric success for a non-current user is no longer reported to DevicePolicyManager.reportSuccessfulBiometricAttempt.",
      "KeyguardViewMediator.java: the boolean mKeyguardDonePending is replaced by int mKeyguardDonePendingForUser, with the sentinel NO_KEYGUARD_DONE_PENDING (-10000). keyguardDonePending stores targetUserId. readyForKeyguardDone reads the stored user, clears it and calls tryKeyguardDone(user). hideWithAnimation, resetKeyguardDonePendingLocked and dump are updated to use the new field.",
      "KeyguardViewMediator.java:tryKeyguardDone now takes an int userId. Its completion check uses mKeyguardDonePendingForUser == NO_KEYGUARD_DONE_PENDING. When it starts the pre-hide animation, it passes new OnHideAnimationFinished(userId).",
      "KeyguardViewMediator.java: the shared mHideAnimationFinishedRunnable is removed and replaced by the inner class OnHideAnimationFinished(int userId). Its run() clears mHideAnimationRunning, then compares the captured mUserId with KeyguardUpdateMonitor.getCurrentUser(). On a mismatch it logs, calls mShadeController.get().instantCollapseShade() and resetStateLocked(), and returns without calling tryKeyguardDone. keyguardDonePending also passes new OnHideAnimationFinished(targetUserId) to startPreHideAnimation.",
      "KeyguardViewMediator.java:mKeyguardGoingAwayRunnable: inside the mUiBgExecutor lambda, before ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag), it re-reads KeyguardUpdateMonitor.getCurrentUser(). If that differs from mGoingAwayRequestedForUserId, it logs, calls mUpdateMonitor.setKeyguardGoingAway(false) and mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false), and returns.",
      "KeyguardViewMediator.java: in the branch where mHiding is set to false and mLockPatternUtils.isSecure(currentUserId) leads to doKeyguardLocked(null), the fix adds mShadeController.get().instantCollapseShade() and setShowingLocked(true, true /* force */), forcing the keyguard back to showing.",
      "KeyguardViewMediatorTest.java: adds four tests: biometricAuthSuccessForCurrentUserDoesProceed, biometricAuthSuccessForDifferentUserDoesNotProceed, testDonePendingExecutesOnHideAnimationFinished and testDonePendingExecutesOnHideAnimationFinishedButTerminatesWhenUserHasChanged."
     ],
     "completeness": [
      "Covered, all in KeyguardViewMediator.java: the biometric success callback, the keyguardDonePending -> pre-hide animation -> OnHideAnimationFinished -> tryKeyguardDone chain, readyForKeyguardDone, the background keyguardGoingAway call, and the secure-user recovery branch shown in the hunk near line 2964.",
      "The Android 14 commit does not include the extra hardening in the 15/16 commits. It has no mDismissToken for the 500 ms delayed dismiss() posted after a user switch, and handleBeforeUserSwitching does not remove the HIDE message or that delayed dismiss runnable. The material does not show whether that path matters on 14.",
      "The user checks in OnHideAnimationFinished and in the goingAway runnable compare against KeyguardUpdateMonitor.getCurrentUser() at run time. They rely on that static value already reflecting the switch when the deferred step runs.",
      "keyguardDone and keyguardDonePending keep their existing entry checks. tryKeyguardDone itself does not re-check the user; it relies on its callers and on OnHideAnimationFinished."
     ],
     "behavior_changes": [
      "Biometric success for a user other than the current user is ignored (Log.w) and is no longer reported to DevicePolicyManager.reportSuccessfulBiometricAttempt.",
      "If the user changes between keyguardDonePending and the end of the pre-hide animation, the shade is collapsed and the keyguard state is reset instead of the keyguard being dismissed.",
      "If the user changes before the background keyguardGoingAway call runs, the call is skipped and the going-away flags are cleared.",
      "In the modified secure-user branch where mHiding is reset, the shade is collapsed and setShowingLocked(true, force) is applied.",
      "Several DEBUG-gated logs become unconditional Log.d logs that include user ids. The dumpsys field is renamed to mKeyguardDonePendingForUser."
     ],
     "preconditions": [
      "A multi-user device where a user switch happens while a keyguard dismissal for the previous user is in progress. The dismissal can come from biometric success, from keyguardDonePending/keyguardDone (called from KeyguardSecurityContainerController.finish, per index search), or from a keyguardGoingAway request.",
      "The deferred steps (the pre-hide animation completion runnable, the mUiBgExecutor runnable, readyForKeyguardDone) must run after the current user has already changed."
     ],
     "per_major": {
      "14": "Fixed by 2acf4a1801c1 in android-security-14.0.0_r24. This is the analysed commit; it uses KeyguardUpdateMonitor.getCurrentUser(). It lacks the mDismissToken / removeMessages(HIDE) additions of 15/16. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "Adapted fix 3b6cc0893907 in android-security-15.0.0_r12. Same logic, using mSelectedUserInteractor.getSelectedUserId(). It adds mDismissToken for the delayed dismiss, removes the HIDE message and the token callbacks in handleBeforeUserSwitching, and calls mKeyguardInteractor.showKeyguard() in the recovery branch. At android-security-15.0.0_r17, 8/8 lines are present.",
      "16": "Adapted fix 78760f098fab in android-security-16.0.0_r3. Same as 15, except the recovery branch is gated on !KeyguardWmStateRefactor.isEnabled() and setShowingLocked takes a reason string. The goingAway check comes before the ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS branch. At android-security-16.0.0_r8, 8/8 lines are present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "One commit per branch. The user lookup differs: KeyguardUpdateMonitor.getCurrentUser() on 14, mSelectedUserInteractor.getSelectedUserId() on 15/16. On 14, keyguardDonePending has an extra primaryAuth parameter. When porting to a 14-based tree, consider also taking the 15/16 mDismissToken and removeMessages(HIDE) additions in handleBeforeUserSwitching if the delayed dismiss() post exists there."
     ],
     "confidence": "high",
     "unknowns": [
      "Which line of the patched region differs at android-security-14.0.0_r29 and android-security-17.0.0_r1 (the resolver reports 7/8 lines).",
      "Whether the Android 14 tree has the 500 ms delayed dismiss() post that 15/16 additionally guard with mDismissToken.",
      "The enclosing method of the recovery branch modified near line 2964 is not shown in the 14 diff hunk (the 16 log string suggests handleStartKeyguardExitAnimation).",
      "What onBiometricAuthenticated does after reportSuccessfulBiometricAttempt (that part of the method is not shown)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48578": {
     "cve": "CVE-2025-48578",
     "summary": "Makes MediaProvider restore the caller identity it had cleared before an exception propagates: in MediaProvider#updateInternal when ContentUris.parseId throws NumberFormatException, and (on 15/16) in MediaProvider#getResultForGetMediaUri when an unsupported document authority causes an IllegalArgumentException.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (content provider update path and getMediaUri call path)",
     "weakness": "Improper cleanup on exception path: the calling identity is cleared (clearLocalCallingIdentity / clearCallingIdentity) and not restored when an exception is thrown. The bulletin classifies this as EoP.",
     "root_cause": "In MediaProvider#updateInternal the code calls clearLocalCallingIdentity() and then evaluates MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)) outside of any try/finally. If the URI's id segment is not numeric, ContentUris.parseId throws NumberFormatException and the exception escapes. The cleared identity token is never passed back to restoreLocalCallingIdentity. The commit message describes the same defect in MediaProvider#getResultForGetMediaUri: after the calling identity is cleared, an IllegalArgumentException is thrown for an unsupported documentUri authority without calling restoreCallingIdentity(token) first.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:updateInternal: the genericUri computation is wrapped in try/catch (NumberFormatException e). On catch, restoreLocalCallingIdentity(token) is called and the exception is rethrown unchanged. The existing try-with-resources around queryForSingleItem is untouched.",
      "src/com/android/providers/media/MediaProvider.java:getResultForGetMediaUri (in the Android 15 and 16 commits only): restoreCallingIdentity(token) is inserted immediately before throw new IllegalArgumentException(\"Provider for this Uri is not supported.\") in the authority check against MediaDocumentsProvider.AUTHORITY and DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY.",
      "The Android 14 commit 12fbdbc6beda is a cherry-pick (its message lists a chain of upstream cherry-picks under one Change-Id). Although its message describes both methods, its diff contains only the updateInternal hunk (1 file, 9 insertions, 2 deletions)."
     ],
     "completeness": [
      "The updateInternal change covers only NumberFormatException from ContentUris.parseId. Any other unchecked exception thrown between clearLocalCallingIdentity() and the try-with-resources block would still leave the identity unrestored, but the diff shows no other statement in that gap.",
      "The getResultForGetMediaUri change covers only the unsupported-authority throw. It restores the identity just before that one throw, rather than wrapping the whole cleared region in a try/finally.",
      "On Android 14 the fix commit contains only the updateInternal hunk. The getResultForGetMediaUri hunk mentioned in the commit message is absent from the 14 diff, and the material does not show whether that code exists on 14.",
      "The code context at android-security-14.0.0_r29 shows handleRedactedFileLookup clearing the identity at line 2223. It then throws IllegalStateException on VolumeNotFoundException (lines 2227-2231), before the try/finally that restores the identity. The fix does not touch this path (see potential_variants)."
     ],
     "behavior_changes": [
      "Returned results and exception types do not change: NumberFormatException and IllegalArgumentException still reach the caller as before. The only difference is that the calling identity is restored first.",
      "No new validation, error codes or logging are added."
     ],
     "preconditions": [
      "updateInternal path: a ContentResolver update request that reaches the block in MediaProvider#updateInternal that calls clearLocalCallingIdentity() before building genericUri (the block that queries sPlacementColumns), with a URI whose id segment makes ContentUris.parseId throw NumberFormatException.",
      "getResultForGetMediaUri path (15/16 hunk): a getMediaUri call whose documentUri authority is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY.",
      "The material does not show which earlier checks in updateInternal gate entry into the patched block."
     ],
     "per_major": {
      "14": "adapted fix: commit 12fbdbc6beda in android-security-14.0.0_r25 carries only the updateInternal NumberFormatException hunk; the getResultForGetMediaUri hunk is not in the 14 diff. 6/6 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix with both hunks: commit 2a8f3f27275c in android-security-15.0.0_r13 (restoreCallingIdentity before the IllegalArgumentException in getResultForGetMediaUri, plus the updateInternal try/catch). 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix with both hunks: commit 7ea0a039ec32 in android-security-16.0.0_r4, identical content to the 15 change at different line offsets. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. 6/6 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "For a 14-based tree, apply the updateInternal hunk as in 12fbdbc6beda. Then check whether your MediaProvider#getResultForGetMediaUri has the authority check against MediaDocumentsProvider.AUTHORITY / EXTERNAL_STORAGE_PROVIDER_AUTHORITY after the identity is cleared. If it does, add restoreCallingIdentity(token) before the throw, as in the 15/16 commits. The two hunks use different APIs: restoreLocalCallingIdentity (LocalCallingIdentity token) in updateInternal and restoreCallingIdentity(token) in getResultForGetMediaUri.",
      "MediaProvider is a mainline (APEX) module, so the fix may also ship through a module update and not only through the platform security tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the Android 14 commit omits the getResultForGetMediaUri hunk: either the matching code or clearCallingIdentity call is absent on 14, or the hunk was dropped.",
      "The material does not show what the unrestored identity does to later requests (how the thread-local identity is reused). The bulletin only classifies the issue as High EoP.",
      "Which conditions earlier in updateInternal lead to the patched block (the surrounding function body is not in the material).",
      "Why the Android 17 tag reports 6/6 rather than 8/8 lines (getResultForGetMediaUri may have been refactored); not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48579": {
     "cve": "CVE-2025-48579",
     "summary": "MediaProvider now restores the cleared calling identity before rethrowing when ContentUris.parseId throws NumberFormatException in updateInternal (and, in the 15/16 commits, before throwing IllegalArgumentException for an unsupported authority in getResultForGetMediaUri), so these exceptions no longer leave MediaProvider's own identity in effect.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (update and getMediaUri call paths)",
     "weakness": "Improper cleanup on thrown exception: the calling identity is cleared but not restored on an exceptional exit, so the elevated identity stays in effect",
     "root_cause": "In MediaProvider#updateInternal, the code calls clearLocalCallingIdentity() and then computes genericUri with MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)). This call is not inside any try/finally. If the URI's ID segment is not a valid long, ContentUris.parseId throws NumberFormatException and the method exits without calling restoreLocalCallingIdentity(token), so the cleared (provider) local calling identity stays in effect. Per the commit message and the 15/16 diffs, MediaProvider#getResultForGetMediaUri had the same flaw. When the document URI authority is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, it throws IllegalArgumentException(\"Provider for this Uri is not supported.\") without calling restoreCallingIdentity(token).",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:updateInternal: the genericUri computation is wrapped in try { ... } catch (NumberFormatException e) { restoreLocalCallingIdentity(token); throw e; }. The exception still reaches the caller, but the LocalCallingIdentity token is restored first. The later try-with-resources around queryForSingleItem is unchanged.",
      "src/com/android/providers/media/MediaProvider.java:getResultForGetMediaUri (Android 15 and 16 commits only): restoreCallingIdentity(token) is inserted immediately before throw new IllegalArgumentException(\"Provider for this Uri is not supported.\") in the authority check, so the calling identity is restored on that early-exit path."
     ],
     "completeness": [
      "Covers exactly the two exceptional exits named in the commit message: NumberFormatException from ContentUris.parseId in updateInternal, and the unsupported-authority IllegalArgumentException in getResultForGetMediaUri.",
      "The fix is targeted (catch-and-restore, or restore-before-throw). It does not wrap everything after clearLocalCallingIdentity in a try/finally, so the shown change does not cover other exception types thrown between clearLocalCallingIdentity() and the existing try block in updateInternal.",
      "The Android 14 commit contains only the updateInternal hunk. The getResultForGetMediaUri hunk in the 15 and 16 commits is missing from the 14 diff, even though the 14 commit message describes that bug. The material does not show the Android 14 state of getResultForGetMediaUri.",
      "The surrounding Android 14 code shown (handleRedactedFileLookup) has a similar clear-then-throw path that this fix does not touch (see potential_variants)."
     ],
     "behavior_changes": [
      "No new error codes or results: the caller still gets the same NumberFormatException or IllegalArgumentException. The only difference is that the calling identity is restored before the exception leaves the method."
     ],
     "preconditions": [
      "updateInternal path: a caller invokes update() on MediaProvider with a URI that reaches the shown block (identity cleared) and whose ID segment cannot be parsed as a long, so ContentUris.parseId throws NumberFormatException.",
      "getResultForGetMediaUri path (15/16 diffs): a caller makes the getMediaUri call with a document URI whose authority is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY.",
      "The bulletin rates this as High EoP. The material does not show what a caller can do with the leaked identity after the exception."
     ],
     "per_major": {
      "14": "Adapted/partial fix: commit 12fbdbc6beda in android-security-14.0.0_r25 contains only the updateInternal NumberFormatException hunk (6/6 lines present at android-security-14.0.0_r29). The getResultForGetMediaUri hunk is not in the 14 commit, although the commit message describes it.",
      "15": "Same fix, both hunks: commit 2a8f3f27275c in android-security-15.0.0_r13 adds restoreCallingIdentity(token) in getResultForGetMediaUri and the try/catch in updateInternal (8/8 lines present at android-security-15.0.0_r17).",
      "16": "Same fix, both hunks: commit 7ea0a039ec32 in android-security-16.0.0_r4. The content matches the 15 commit at different line offsets (8/8 lines present at android-security-16.0.0_r8).",
      "17": "Not affected (not listed by the bulletin). The updateInternal patched region is present at android-security-17.0.0_r1 (6/6 lines)."
     },
     "backport_notes": [
      "Each branch has a single commit, reached through a chain of cherry-picks of one change (Change-Id Ia295e56530a44c437db3a159e97c12d1a239b547). On a 14-based tree, check whether getResultForGetMediaUri has the same authority-check throw after clearing identity. If it does, apply the restoreCallingIdentity(token) hunk from the 15/16 diffs, because the 14 commit omits it.",
      "The 14 hunk applies to MediaProvider.java around line 7548 (updateInternal). In 15/16 that hunk sits at 8225/8978 and the getResultForGetMediaUri hunk at 7097/7674. Both hunks are small and anchored by context."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show why the Android 14 commit omits the getResultForGetMediaUri hunk (the code or pattern may be absent on 14, or it may have been fixed separately).",
      "The material does not show which privileged operations become reachable with the leaked identity after the exception.",
      "The material does not say whether the two bug IDs (417195606, 418225717) map one-to-one onto the two hunks."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48582": {
     "cve": "CVE-2025-48582",
     "summary": "MediaProvider now records the uid of the app that created a bulk create request inside the PendingIntent extras for PermissionActivity, and PermissionActivity uses that uid, instead of Activity.getCallingPackage(), to decide whether user consent is needed and to which package URI permissions are granted.",
     "component": "platform/packages/providers/MediaProvider, PermissionActivity / bulk request PendingIntent creation (MediaProvider.java)",
     "weakness": "Incorrect identity used for authorization (permission check and URI grant keyed on the activity launcher rather than the request creator)",
     "root_cause": "MediaProvider builds a PendingIntent targeting PermissionActivity for create bulk requests (e.g. CREATE_WRITE_REQUEST_CALL) but did not record which uid created the request. PermissionActivity.resolveCallingAppInfo() relied solely on getCallingPackage(), i.e. the package that started the activity, and that package name was also passed to shouldShowActionDialog() (consent decision), to grantUriPermission() (recipient of READ/WRITE URI grants) and to Metrics. The identity used for the permission check and the grant could therefore differ from the identity that created the request. The app info was also resolved with getApplicationInfo() rather than for the caller's user.",
     "fix_analysis": [
      "apex/framework/java/android/provider/MediaStore.java: adds hidden constant EXTRA_CALLING_PACKAGE_UID = \"calling_package_uid\" (Android 14 commit; the 15/16 diffs shown do not include MediaStore.java).",
      "src/com/android/providers/media/MediaProvider.java (PendingIntent builder for PermissionActivity, hunk around line 6971 on 14 / 7645 on 15 / 8384 on 16): before intent.putExtras(extras), adds extras.putInt(EXTRA_CALLING_PACKAGE_UID, getCallingUidOrSelf()). On 14 the PendingIntent is created with FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE.",
      "src/com/android/providers/media/PermissionActivity.java:resolveCallingAppInfo(): if the intent has extras, reads EXTRA_CALLING_PACKAGE_UID, resolves the package via Objects.requireNonNull(getPackageManager().getPackagesForUid(callingUid))[0], derives the UserHandle with UserHandle.getUserHandleForUid(callingUid), and resolves the app with getApplicationInfoAsUser(callingPackage, 0, userHandle). getCallingPackage()/getUser() remain only as the fallback when extras == null.",
      "src/com/android/providers/media/PermissionActivity.java (consent-decision code): both shouldShowActionDialog(...) invocations now receive appInfo.packageName instead of getCallingPackage().",
      "src/com/android/providers/media/PermissionActivity.java: the positive-action AsyncTask now calls grantUriPermission(appInfo.packageName, uri, READ|WRITE) for CREATE_WRITE_REQUEST_CALL, and Metrics.logPermissionGranted / logPermissionDenied use appInfo.packageName."
     ],
     "completeness": [
      "All uses of getCallingPackage() for authorization and grants in PermissionActivity shown in the diff are replaced; only the fallback inside resolveCallingAppInfo() (extras == null) still uses it.",
      "When extras is non-null but lacks EXTRA_CALLING_PACKAGE_UID, extras.getInt() returns 0 and getPackagesForUid(0) is used; the resulting behavior is not shown in the material.",
      "For shared-uid packages the fix takes getPackagesForUid(uid)[0]; the consent check and grantUriPermission are performed against the first package of the uid group rather than necessarily the specific requesting package.",
      "The grant branch patched is CREATE_WRITE_REQUEST_CALL; other branches of the switch are not shown in the diff."
     ],
     "behavior_changes": [
      "Bulk request PendingIntents now carry an extra int 'calling_package_uid'.",
      "PermissionActivity resolves the requesting app via its uid and user (getApplicationInfoAsUser), so callers are resolved in their own user.",
      "If the stored uid cannot be mapped to a package (getPackagesForUid returns null), resolveCallingAppInfo throws NullPointerException (Objects.requireNonNull) instead of NameNotFoundException.",
      "Metrics now attribute grant/deny events to the resolved request creator's package."
     ],
     "preconditions": [
      "An app calls a MediaStore create bulk request API that makes MediaProvider build a PendingIntent for PermissionActivity (e.g. CREATE_WRITE_REQUEST_CALL).",
      "The resulting PendingIntent is launched so PermissionActivity runs; before the fix the identity checked and granted was whatever package started the activity (getCallingPackage()).",
      "The exact conditions under which the launcher identity differed from the request creator are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 45db544a1f1a in android-security-14.0.0_r25, touching MediaStore.java (adds EXTRA_CALLING_PACKAGE_UID), MediaProvider.java and PermissionActivity.java; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 6b652db8a6d3 in android-security-15.0.0_r13; same PermissionActivity.java and MediaProvider.java changes (surrounding context with ActivityOptions differs); the diff shown does not include MediaStore.java; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 1b7d4ce446d1 in android-security-16.0.0_r4; same two-file change as 15 at shifted line numbers; 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); patched region 8/8 present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit; on branches where MediaStore.EXTRA_CALLING_PACKAGE_UID does not yet exist (as on 14) the apex/framework MediaStore.java hunk must be included along with the MediaProvider.java and PermissionActivity.java changes."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete scenario in which the PendingIntent launcher differed from the request creator (bug 369105011 is not described in the material).",
      "Whether PermissionActivity is exported or otherwise reachable with foreign extras.",
      "Behavior of getPackagesForUid(0) when the extra is missing.",
      "Whether EXTRA_CALLING_PACKAGE_UID already exists in MediaStore.java on 15/16 or is added elsewhere.",
      "Which other create request methods (besides CREATE_WRITE_REQUEST_CALL) go through the patched builder.",
      "Content of the referenced PermissionActivityTest changes."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48602": {
     "cve": "CVE-2025-48602",
     "summary": "KeyguardViewMediator now drops any queued START_KEYGUARD_EXIT_ANIM message on user switch and, when the exit animation has been flagged as canceled, the post-traversal completion callback finishes the remote animation with the keyguard showing and forces the showing state instead of only logging and returning.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardViewMediator)",
     "weakness": "Race condition / improper state handling between keyguard exit animation completion and user switch (test name indicates the keyguard could end up hidden)",
     "root_cause": "In packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java, a user switch (setCurrentUser on Android 14) removed pending DISMISS and HIDE messages and, for a secure target user, set mIsKeyguardExitAnimationCanceled, but it did not remove a queued START_KEYGUARD_EXIT_ANIM message, so an exit animation requested before the switch could still start afterwards. In addition, in exitKeyguardAndFinishSurfaceBehindRemoteAnimation the runnable posted via DejankUtils.postAfterTraversal only logged and returned when mIsKeyguardExitAnimationCanceled was true; it neither finished the remote animation with the keyguard showing (finishSurfaceBehindRemoteAnimation(true)) nor forced the mediator showing state back to true.",
     "fix_analysis": [
      "KeyguardViewMediator.java:setCurrentUser (Android 14): adds mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM) next to the existing removal of DISMISS and HIDE, so an exit animation queued before the user switch is not started afterwards. The pre-existing branch for a secure new user still sets mIsKeyguardExitAnimationCanceled = true, calls setPendingLock(true), ActivityTaskManager.setLockScreenShown(true, mAodShowing) and doKeyguardLocked(null).",
      "KeyguardViewMediator.java: adds a @VisibleForTesting postAfterTraversal(Runnable) wrapper around DejankUtils.postAfterTraversal so the deferred callback can be captured by tests; exitKeyguardAndFinishSurfaceBehindRemoteAnimation now calls this wrapper.",
      "KeyguardViewMediator.java:exitKeyguardAndFinishSurfaceBehindRemoteAnimation: inside the posted runnable, the mIsKeyguardExitAnimationCanceled check is moved after the (!mPM.isInteractive() && !mPendingLock) check and its action is changed from a bare return to finishSurfaceBehindRemoteAnimation(true /* showKeyguard */) followed by setShowingLocked(true /* showing */, true /* force */) and then return; onKeyguardExitFinished is not reached.",
      "KeyguardViewMediatorTest.java: adds testGoingAwayFollowedByBeforeUserSwitchWithDelayedExitAnimationDoesNotHideKeyguard, which starts an exit animation for an insecure user, requests keyguardDonePending/readyForKeyguardDone, switches to a secure user, then runs the captured post-traversal runnable and verifies notifyFinishedKeyguardExitAnimation(true) and isShowingAndNotOccluded(); the test constructor overrides postAfterTraversal to capture the runnable. An existing test gains keyguardDonePending/readyForKeyguardDone calls, and testRegisterDumpable is relaxed to startsWith on the dumpable name."
     ],
     "completeness": [
      "Covers two points: the START_KEYGUARD_EXIT_ANIM handler message queued before the switch (dropped), and the post-traversal completion callback of an exit animation already in flight (now finishes the remote animation with keyguard showing and forces the mediator showing state).",
      "On Android 14, mIsKeyguardExitAnimationCanceled is set only in the mLockPatternUtils.isSecure(newUserId) branch of setCurrentUser (pre-existing code); switching to a non-secure user takes resetStateLocked() and does not set the flag. On Android 15/16 the fix sets the flag unconditionally in onBeforeUserSwitching.",
      "The canceled check is performed only inside the deferred runnable; on Android 14 the code that runs before posting (e.g. InteractionJankMonitor end) is unchanged.",
      "Where and when mIsKeyguardExitAnimationCanceled is reset to false is not shown in the material, so whether a stale flag can affect a later legitimate unlock cannot be judged from the diff.",
      "Other handler messages (DISMISS/HIDE) were already removed on user switch before this commit and are not modified."
     ],
     "behavior_changes": [
      "A user switch now also discards any pending START_KEYGUARD_EXIT_ANIM message, so an exit animation requested before the switch will not start.",
      "When the exit animation has been canceled, the deferred completion callback now calls finishSurfaceBehindRemoteAnimation(true) (in the test observable as notifyFinishedKeyguardExitAnimation(true)) and forces setShowingLocked(true, true), instead of only logging.",
      "The canceled check now runs after the non-interactive/no-pending-lock check inside the posted runnable, so that error path takes precedence.",
      "New package-private @VisibleForTesting method postAfterTraversal(Runnable) on KeyguardViewMediator; no change to DejankUtils itself."
     ],
     "preconditions": [
      "A keyguard exit (going away) animation has been started, as driven by startKeyguardExitAnimation from WindowManager.",
      "A user switch to another user (the test uses a secure target user) is processed by KeyguardViewMediator.setCurrentUser (Android 14) while the exit animation is in progress, before the post-traversal completion callback runs.",
      "How a user switch can be triggered at that moment in practice is not shown in the material."
     ],
     "per_major": {
      "14": "Fixed by 1c3c01a414a0 in android-security-14.0.0_r24 (cherry-pick of f17b66e7). Adapted fix: removeMessages(START_KEYGUARD_EXIT_ANIM) added to setCurrentUser (the flag mIsKeyguardExitAnimationCanceled was already set there for secure users); canceled branch inside the posted runnable now finishes the remote animation with showKeyguard=true and forces setShowingLocked(true, true). At android-security-14.0.0_r29, 8/8 lines of the patched region are present.",
      "15": "Fixed by 67769a49e360 in android-security-15.0.0_r12. Adapted: removeMessages(START_KEYGUARD_EXIT_ANIM) and an unconditional mIsKeyguardExitAnimationCanceled = true are added to the onBeforeUserSwitching handler (synchronized block) rather than setCurrentUser; same postAfterTraversal wrapper and canceled-branch change (2-arg setShowingLocked). At android-security-15.0.0_r17, 8/8 lines present.",
      "16": "Fixed by c69e16c3efda in android-security-16.0.0_r3. Adapted: same onBeforeUserSwitching change as 15; additionally the pre-existing early canceled check located before the post is removed and replaced by the check inside the runnable; uses 3-arg setShowingLocked(showing, force, reason) and onKeyguardExitFinished(reason). At android-security-16.0.0_r8, 8/8 lines present.",
      "17": "Not affected per the bulletin (Android 17 not listed). At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single self-contained commit per branch. The user-switch hook differs by release: setCurrentUser on 14 (flag already set in the isSecure branch) versus onBeforeUserSwitching/handleBeforeUserSwitching on 15/16 (flag set unconditionally); setShowingLocked takes a reason argument on 16. The test subclasses KeyguardViewMediator in the test constructor to override postAfterTraversal, so the constructor argument list must match the branch."
     ],
     "confidence": "high",
     "unknowns": [
      "Where mIsKeyguardExitAnimationCanceled is reset to false (not in the diff or context).",
      "Body of finishSurfaceBehindRemoteAnimation and of the non-interactive error branch inside the posted runnable (not shown).",
      "How the user switch is initiated in the reported scenario and which caller invokes setCurrentUser on Android 14.",
      "What was visible to the user in the unfixed race (not shown in the material).",
      "Why Android 17 is listed as not affected while 7/8 patched lines are present at android-security-17.0.0_r1."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48609": {
     "cve": "CVE-2025-48609",
     "summary": "MmsProvider.deleteDataRows now verifies that the file path read from a part/drm row's _data column canonically resolves under the provider's \"parts\" directory before calling File.delete(), reusing the same check openFile already performed.",
     "component": "platform/packages/providers/TelephonyProvider, MmsProvider (mms content provider, part/drm row deletion)",
     "weakness": "Missing path validation before file deletion (path traversal / arbitrary file deletion using a database-stored path)",
     "root_cause": "In src/com/android/providers/telephony/MmsProvider.java, deleteDataRows(db, table, selection, selectionArgs) queried the _data column of the part (TABLE_PART) or drm (TABLE_DRM) table and, for every non-null value, executed new File(path).delete() with no check on where the path pointed. The _data value was treated as trusted even though openFile already contained a comment noting that callers may have inserted or updated the _data column with disallowed paths (including via SQL injection) and guarded against it with a canonical-path prefix check. The delete path had no such guard, so a row whose _data pointed outside the parts directory caused the provider to delete that file when the row was deleted (via deleteParts/deleteTempDrmData, or indirectly via deleteMessages which calls deleteParts for each pdu). The bulletin classifies this as High severity DoS.",
     "fix_analysis": [
      "MmsProvider.java: new static helper isFilePathCanonical(Context, File) returns file.getCanonicalPath().startsWith(context.getDir(PARTS_DIR_NAME, 0).getCanonicalPath()); on IOException it logs and returns false (fail closed).",
      "MmsProvider.java:deleteDataRows now takes a Context; for each _data value it skips null, builds File, calls isFilePathCanonical, and if false logs 'deleteFile: path ... does not start with ...' and continues without deleting; only paths under the parts directory reach file.delete(). The DB row is still deleted afterwards via db.delete(table, selection, selectionArgs) as before.",
      "MmsProvider.java:deleteParts and deleteTempDrmData gain a Context parameter and forward it to deleteDataRows; delete() captures Context context = getContext() once and passes it to deleteMessages, deleteParts and deleteTempDrmData; deleteMessages' per-pdu loop passes context to deleteParts.",
      "MmsProvider.java:openFile replaced its inline getCanonicalPath().startsWith(...) comparison with the shared isFilePathCanonical(getContext(), filePath); semantics unchanged.",
      "tests/src/com/android/providers/telephony/MmsProviderTest.java: adds a temp testDir/parts setup and two tests, testDeleteDataRows_pathStartsWithPartsDirectory_canDeleteFile and testDeleteDataRows_invalidPath_cannotDeleteFile, exercising isFilePathCanonical directly."
     ],
     "completeness": [
      "Covers the _data-driven file deletions that go through deleteDataRows as shown in the diff: deleteParts (called from delete() for the part table and from the deleteMessages per-pdu cascade) and deleteTempDrmData (drm table). The material does not show the full file, so other deletion sites cannot be ruled out.",
      "The check is a plain string prefix comparison (startsWith) on canonical paths without a trailing separator, the same pattern openFile already used; a directory whose name merely begins with the parts directory name would pass. The material does not show whether such a sibling directory exists.",
      "The fix only hardens the consumer of _data. It does not change how _data gets written (insert/update paths are not in the diff); the commit message itself says it may be redundant with separate SQL injection protection changes that are not included in this material.",
      "The same PARTS_DIR_NAME prefix is applied to drm-table rows; the material does not show where drm _data files are normally stored, so whether legitimate drm temp files are always under the parts directory is not established from the diff."
     ],
     "behavior_changes": [
      "Deleting a part or drm row whose _data does not canonically resolve under context.getDir(\"parts\", 0) no longer removes the file; an error is logged ('deleteFile: path ... does not start with ...') and the database row is still deleted, so such files may be orphaned on disk.",
      "If canonicalization throws IOException, the file is not deleted (previously the delete was attempted on the raw path).",
      "openFile behavior is unchanged; it now calls the shared helper.",
      "New public static @VisibleForTesting method isFilePathCanonical on MmsProvider."
     ],
     "preconditions": [
      "A row in the mms part or drm table whose _data column holds a path outside the provider's parts directory; how such a value gets written is not shown in the material (the openFile comment refers to insert/update of _data and SQL injection as the concern).",
      "A caller able to invoke ContentProvider.delete on MmsProvider such that the part or drm table is targeted, or a pdu delete that cascades through deleteMessages to deleteParts (the URI mapping and permission requirements for delete are not shown in the material)."
     ],
     "per_major": {
      "14": "same fix: commit 1552b67718fc in android-security-14.0.0_r25 (cherry-pick of fce8998a1770, Change-Id Ic2072a12bf93be7c17a86b372ae7e7057d7eb237); at android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "same fix: commit e40b30ca3fa3 in android-security-15.0.0_r13; identical MmsProvider change at different line offsets (test file differs slightly: no mUserManager field, context field placed separately); 7/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 37babc702849 in android-security-16.0.0_r4; identical MmsProvider change at different line offsets; 7/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region is present at android-security-17.0.0_r1 (7/8 lines)."
     },
     "backport_notes": [
      "Single self-contained commit; the only dependency is the pre-existing PARTS_DIR_NAME constant and openFile's existing getDir(PARTS_DIR_NAME, 0) usage. When porting, thread the Context parameter through deleteParts/deleteTempDrmData/deleteDataRows and every caller (delete() and the deleteMessages loop), since these are static methods without access to getContext()."
     ],
     "confidence": "high",
     "unknowns": [
      "How an out-of-directory _data value can be introduced (insert/update validation and the referenced 'recent sql injection protection changes' are not in the material).",
      "Permission/URI access requirements and URI-to-table mapping for reaching MmsProvider.delete on part, drm and pdu data.",
      "Where drm-table _data files normally reside and therefore whether the new check can skip legitimate drm temp file cleanup.",
      "Which single line of the 8-line patched region is absent in the latest security tags (the 7/8 figure)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48619": {
     "cve": "CVE-2025-48619",
     "summary": "ContentProvider's binder Transport now sanitizes the file open mode string in openFile and openAssetFile, silently removing the truncate ('t') and append ('a') flags when the write ('w') flag is absent, before both the permission check and the call into the provider.",
     "component": "platform/frameworks/base, core/java/android/content/ContentProvider.java (ContentProvider.Transport, content provider file access)",
     "weakness": "Improper validation of the file open mode: file-modifying mode flags (truncate, append) were accepted and forwarded without requiring the write flag.",
     "root_cause": "In ContentProvider.Transport.openFile and Transport.openAssetFile, the mode String supplied by the caller was passed unchanged to enforceFilePermission(attributionSource, uri, mode) and then to mInterface.openFile / mInterface.openAssetFile. The new code comment lists the supported modes as r, w, wt, wa, rw, rwt, but nothing normalized other combinations. A mode containing 't' (truncate) or 'a' (append) without 'w' therefore reached the provider implementation as a read request that carried bits able to modify the file. The commit message describes this as preventing unintended changes. Presumably the permission check treated such a mode as a read, but the body of enforceFilePermission is not shown. The bulletin rates the issue as High severity EoP.",
     "fix_analysis": [
      "core/java/android/content/ContentProvider.java: a new private helper Transport.validateFileMode(String mode) is added. If mode is non-null and contains no 'w', it removes every 't' (mode.replace(\"t\", \"\")) and every 'a' (mode.replace(\"a\", \"\")) and returns the result. Modes containing 'w' and null modes are returned unchanged.",
      "core/java/android/content/ContentProvider.java: Transport.openFile computes final String updatedMode = validateFileMode(mode) after validateIncomingUri/maybeGetUriWithoutUserId. It then passes updatedMode to enforceFilePermission and to mInterface.openFile, so the permission check and the provider see the same sanitized mode.",
      "core/java/android/content/ContentProvider.java: Transport.openAssetFile gets the same change, passing updatedMode to enforceFilePermission and mInterface.openAssetFile.",
      "The code comment in validateFileMode explains the design choice: unsupported modes are not rejected with a SecurityException, to avoid app compatibility issues. Instead, bits that allow modifying files are silently dropped when the write bit is absent. The commit message references atest CtsContentProviderTestCases."
     ],
     "completeness": [
      "Covers both Transport entry points changed by the diff that accept a String mode: openFile and openAssetFile. Both now feed the sanitized mode to the permission check and the provider.",
      "Only the 't' and 'a' characters are stripped. Other unexpected characters in the mode string still pass through unchanged, and the comment says a strict allow-list was deliberately not implemented.",
      "The sanitization is added only in Transport. The material does not show whether other call paths to a provider's openFile/openAssetFile exist or need it.",
      "Transport.update, which is visible in the context, takes no mode String and is unaffected. openTypedAssetFile is not shown in the material, so its handling is unknown."
     ],
     "behavior_changes": [
      "Mode strings without 'w' lose their 't' and 'a' characters before reaching the provider. For example, mode \"rt\" or \"ra\" is delivered to the provider as \"r\". No exception is thrown and no error is returned to the caller.",
      "enforceFilePermission now runs on the sanitized mode instead of the raw string from the caller.",
      "Modes that contain 'w' (w, wt, wa, rw, rwt) and null modes are unchanged."
     ],
     "preconditions": [
      "A caller reaches a ContentProvider through Transport.openFile or Transport.openAssetFile with a mode string containing 't' or 'a' but no 'w'.",
      "The caller passes enforceFilePermission for the given mode. How that check maps the mode to read or write permission is not shown in the material.",
      "The target provider's openFile/openAssetFile implementation honors the mode flags it receives. This depends on the provider and is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base f54544f81d7d in android-security-14.0.0_r24 (cherry-pick of f8099b069367df749a4c101b5d7f9020d83cb660, Change-Id I1a8993d99d1f381e1122b304d223a5c10e4578ce). At android-security-14.0.0_r29, 7/8 lines of the patched region are present, and the code context shows validateFileMode wired into openFile and openAssetFile.",
      "15": "same fix: platform/frameworks/base e623c490905c in android-security-15.0.0_r12. The diff is identical except that the context lines use TRACE_TAG_ACTIVITY_MANAGER instead of TRACE_TAG_DATABASE. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base 31e77d73c5e6 in android-security-16.0.0_r3, with a diff identical to 15. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "not affected: Android 17 is not listed by the bulletin. At android-security-17.0.0_r1, 6/8 lines of the patched region are present. The material does not show the exact code there."
     },
     "backport_notes": [
      "Single self-contained change to the Transport inner class of ContentProvider.java: add validateFileMode and pass its result to enforceFilePermission and to mInterface.openFile/openAssetFile. Across 14, 15 and 16 the only difference is the trace tag constant in the context lines (TRACE_TAG_DATABASE on 14, TRACE_TAG_ACTIVITY_MANAGER on 15 and 16)."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of enforceFilePermission is not in the material, so how it maps the mode string to read or write permission checks is inferred, not shown.",
      "Whether Transport.openTypedAssetFile or any other path accepts a mode-like parameter is not shown.",
      "Whether in-process or other non-Transport call paths into a provider's openFile/openAssetFile need the same sanitization is not shown.",
      "The exact state of the patched region on android-security-17.0.0_r1 (6/8 lines) is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48630": {
     "cve": "CVE-2025-48630",
     "summary": "SkiaRenderEngine::drawLayersInternal now clips the canvas to the layer bounds before drawing blur regions when no rounded-corner clip is present, so a client-supplied blur region larger than the layer can no longer draw blurred content outside the layer's bounds.",
     "component": "platform/frameworks/native, libs/renderengine (SkiaRenderEngine blur rendering)",
     "weakness": "Missing clip / bounds restriction on client-controlled geometry (information disclosure per bulletin type ID)",
     "root_cause": "In libs/renderengine/skia/SkiaRenderEngine.cpp, drawLayersInternal() set a canvas clip for blur drawing only when roundRectClip was non-empty (canvas->clipRRect(roundRectClip, true)). When the layer had no rounded corners, no layer-bounds clip was applied. Only the existing device clip limited drawing. As a result, the later blur draws for layer.blurRegions, whose geometry comes from the client per the code comment, were not limited to the layer's bounds. Per the commit message, a crafted blur region on a 1-2 pixel layer could make the blur \"escape\" the layer bounds and fill the entire screen. The blurred image is generated from blurInput, a snapshot of the content drawn so far.",
     "fix_analysis": [
      "libs/renderengine/skia/SkiaRenderEngine.cpp : SkiaRenderEngine::drawLayersInternal(): adds an else branch to the existing `if (!roundRectClip.isEmpty())` block. When there is no rounded-corner clip, the new branch calls `canvas->clipRRect(bounds, true)`, clipping to the layer bounds returned by getBoundsAndClip(). The clip sits inside the existing SkAutoCanvasRestore acr(canvas, true) scope, so it applies to the background blur draw and to the loop over layer.blurRegions. It is restored before any shadow drawing.",
      "Single commit with 4 added lines. The hunk content is identical on 14, 15 and 16. The commit message lists two internal Cherrypick-From commits."
     ],
     "completeness": [
      "Covers both the background blur (drawBlurRegion with bounds) and the per-region blur loop over layer.blurRegions, because the clip is set once in the enclosing SkAutoCanvasRestore scope before both.",
      "Layers with rounded corners are still constrained by the pre-existing roundRectClip branch. The fix only changes the no-rounded-corner path, which is now clipped to `bounds`.",
      "The fix does not validate or reject oversized blur regions where the client input arrives. It only constrains the rendering output. The diff modifies no other rendering paths."
     ],
     "behavior_changes": [
      "Blur region drawing is now always clipped to the layer's bounds or to its rounded-corner clip. Blur regions that extend beyond the layer no longer render outside it.",
      "The diff adds no new error codes or logging. The change is purely a rendering restriction."
     ],
     "preconditions": [
      "mBlurFilter must be non-null and the layer must pass layerHasBlur(layer, ctModifiesAlpha) in the Skia RenderEngine.",
      "The layer must have no rounded-corner clip (roundRectClip empty) and must supply blur regions (layer.blurRegions) that extend past its bounds. The commit message describes a 1-2 pixel layer with a crafted blur region. The commit was tested with a PoC app.",
      "Per the code comment, blur region geometry is client-provided ('a client sending a bigger blur region')."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/native 6906658b06e3 in android-security-14.0.0_r25; 4/4 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/native 4deb93b73906 in android-security-15.0.0_r13 (identical hunk, different line offset); 4/4 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/native c81cf361489e in android-security-16.0.0_r4 (identical hunk, different line offset); 4/4 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (4/4 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The hunk adds a 4-line else branch to the existing `if (!roundRectClip.isEmpty())` block in drawLayersInternal and has the same content on 14, 15 and 16, differing only in line offset. Forks with a customized blur path should make sure the clip is set inside the SkAutoCanvasRestore scope, before both the background blur draw and the blurRegions loop."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how blur region geometry reaches the layer (the SurfaceFlinger/SurfaceControl input path) or whether any validation exists on that input side.",
      "The two internal Cherrypick-From commits are not shown individually; only the resulting 4-line change is visible."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48634": {
     "cve": "CVE-2025-48634",
     "summary": "Adds a permission-based sanitizer (sanitizePrivateFlags) to WindowManagerService relayout so that a client cannot newly set PRIVATE_FLAG_TRUSTED_OVERLAY / PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY without INTERNAL_SYSTEM_WINDOW, or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP without MANAGE_ACTIVITY_TASKS.",
     "component": "platform/frameworks/base, WindowManagerService (services/core/java/com/android/server/wm), window relayout",
     "weakness": "Missing permission check: privileged LayoutParams.privateFlags supplied by the client were accepted during relayout without verifying caller permissions.",
     "root_cause": "In WindowManagerService's relayout path (the block that calls displayPolicy.adjustWindowParamsLw(win, attrs) and then sanitizeFlagSlippery / sanitizeSpyWindow), the caller-supplied attrs.privateFlags were taken as-is, while attrs.flags (FLAG_SLIPPERY), attrs.inputFeatures (spy window) and the systemUiVisibility disable bits (hasStatusBarPermission) were checked against caller permissions. Therefore a client that already owns a window could, on a subsequent relayout, add the private flags PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP to its window without holding INTERNAL_SYSTEM_WINDOW or MANAGE_ACTIVITY_TASKS. The bulletin rates this High / EoP.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowManagerService.java, relayout block (`if (attrs != null)` after displayPolicy.adjustWindowParamsLw): new line `attrs.privateFlags = sanitizePrivateFlags(attrs.privateFlags, win.mAttrs.privateFlags, win.getName(), uid, pid);` inserted before the existing sanitizeFlagSlippery call, using the Binder calling uid/pid captured at the top of the method.",
      "WindowManagerService.java: new private helper sanitizePrivateFlags(newPrivateFlags, oldPrivateFlags, windowName, callingUid, callingPid). It computes addedPrivateFlags = ~oldPrivateFlags & newPrivateFlags (only flags being newly turned on). If PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_TRUSTED_OVERLAY is being added and the caller lacks INTERNAL_SYSTEM_WINDOW, both bits are cleared from the result and a Slog.w is emitted. If PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP is being added and the caller lacks MANAGE_ACTIVITY_TASKS, that bit is cleared with a Slog.w. The sanitized value is returned and written back to attrs.privateFlags.",
      "WindowManagerService.java: new helpers hasFlags(flags, mask) and hasPermission(permission, callingPid, callingUid), the latter wrapping mContext.checkPermission(...) == PackageManager.PERMISSION_GRANTED.",
      "services/tests/wmtests/.../WindowManagerServiceTests.java: testRelayoutFlagChanges gains internalSystemWindowGranted / manageActivityTasksGranted parameters that stub mWm.mContext.checkPermission for the two permissions (with anyInt() for pid/uid), plus six tests (permissionDenied/permissionGranted for each of the three flags) asserting the flag is stripped or kept and that mDwpcHelper.keepActivityOnWindowFlagsChanged is not invoked when nothing changed."
     ],
     "completeness": [
      "Covers only the relayout path and only when attrs != null; the window-add path is not in the material, so whether these flags are checked at addWindow is unknown from this diff.",
      "Only three private flags are gated (TRUSTED_OVERLAY, IS_ROUNDED_CORNERS_OVERLAY, INTERCEPT_GLOBAL_DRAG_AND_DROP); all other privateFlags bits remain unchecked by this sanitizer at relayout.",
      "Only newly added bits are evaluated (~old & new); bits already present in win.mAttrs.privateFlags are never re-validated.",
      "Argument-order observation from the diff: hasPermission is declared as (permission, callingPid, callingUid) and forwards positionally to mContext.checkPermission(permission, callingPid, callingUid), but both call sites in sanitizePrivateFlags invoke it as hasPermission(permission, callingUid, callingPid). As written, the value passed as the caller uid reaches the pid parameter of Context.checkPermission and vice versa. Context.checkPermission(String, int pid, int uid) evaluates the permission against the uid argument (general platform knowledge), so the check as written is evaluated against the caller's pid number rather than its uid. The unit tests stub checkPermission with anyInt(), anyInt() and therefore cannot detect this. The real-world effect is not shown in the material and is listed under unknowns.",
      "The two overlay bits are cleared together: a caller lacking INTERNAL_SYSTEM_WINDOW that adds only one of them loses both, including one that may have been present before."
     ],
     "behavior_changes": [
      "During relayout, unprivileged attempts to turn on PRIVATE_FLAG_TRUSTED_OVERLAY / PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY / PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP are silently dropped (no exception), with a Slog.w line naming the window and the missing permission.",
      "Per the new tests, when a flag is stripped and nothing else changes on a non-first relayout, mDwpcHelper.keepActivityOnWindowFlagsChanged is not called.",
      "Additional mContext.checkPermission calls can occur per relayout when the relevant bits are newly requested."
     ],
     "preconditions": [
      "Caller owns an already-added window (windowForClientLocked returns a WindowState for its session/client) and calls relayout with a non-null LayoutParams.",
      "The requested attrs.privateFlags contain one of the three gated bits that was not already set in win.mAttrs.privateFlags.",
      "Pre-fix, the relayout block shown did not check permissions for these bits; post-fix, INTERNAL_SYSTEM_WINDOW (overlay bits) or MANAGE_ACTIVITY_TASKS (drag-and-drop interception) is required for the bit to survive."
     ],
     "per_major": {
      "14": "Same fix: commit c88804906997 in android-security-14.0.0_r24 (cherry-pick of ec6a92be6c93). Context line after the insertion is sanitizeSpyWindow(attrs.inputFeatures, ...). 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix: commit a54ba90ae134 in android-security-15.0.0_r12; identical sanitizePrivateFlags body. Only the surrounding context differs (sanitizeInputFeatures(..., win.isTrustedOverlay()) instead of sanitizeSpyWindow). 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Same fix: commit d550a457e65c in android-security-16.0.0_r3; identical body and 15-style context. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "Not listed as affected by the bulletin. At android-security-17.0.0_r1, 7/8 lines of the patched region are present; which line differs, and whether the sanitizer is fully present, is not shown in the material."
     },
     "backport_notes": [
      "The WMS hunk is self-contained: insert the sanitizePrivateFlags call directly after displayPolicy.adjustWindowParamsLw(win, attrs) in the relayout block and add the three helpers next to the existing sanitize* methods; the diff adds no imports to WMS, so PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY and PRIVATE_FLAG_TRUSTED_OVERLAY must already be statically imported there (the drag-and-drop flag is referenced as LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP). The 14 test hunk switches createWindow(...) to newWindowBuilder(...).build(); on a fork whose WindowTestsBase lacks newWindowBuilder keep createWindow.",
      "When porting, verify the hasPermission argument order against Context.checkPermission(String, int pid, int uid): the call sites pass (uid, pid) into a helper declared (pid, uid). The 17 branch differs by one line from the 14/15/16 patched region; compare that line before copying the 14 version verbatim."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the add-window path (not in the material) already validates these private flags, or whether relayout was the only unguarded entry.",
      "The runtime consequence of the swapped uid/pid arguments to mContext.checkPermission for real callers (both unprivileged apps and legitimate system callers), and whether the one differing line on Android 17 relates to it.",
      "Where privateFlagChanges is computed relative to the new sanitizer call (that code is not in the material).",
      "The concrete privilege gained by an unprivileged window carrying each of the three flags; the bulletin only states High / EoP."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48635": {
     "cve": "CVE-2025-48635",
     "summary": "TaskFragmentOrganizerController now refuses to register a TaskFragment organizer from a non-positive pid and only hands the real activity token to an organizer when both the activity's pid and uid match the organizer's, preventing an activity token from being leaked to another process.",
     "component": "platform/frameworks/base, WindowManager / TaskFragmentOrganizerController (activity embedding)",
     "weakness": "Insufficient identity check (activity ownership decided by pid alone, with an unvalidated pid recorded at registration)",
     "root_cause": "The organizer registration path in TaskFragmentOrganizerController.java (hunk near line 430) stored new TaskFragmentOrganizerState(organizer, pid, uid) without validating the pid. Per the commit message, a malicious app could register the organizer via a one-way binder call to disguise itself as running on pid 0. prepareActivityReparentedToTask decided whether to pass the real activity.token or a temporary \"TemporaryActivityToken\" Binder by comparing only activity.getPid() with mOrganizerPid. As a result, the real token of an activity not belonging to the organizer process could be sent in the TYPE_ACTIVITY_REPARENTED_TO_TASK change.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java, organizer registration path (before mTaskFragmentOrganizerState.put): adds `if (pid <= 0) throw new IllegalStateException(\"Cannot register from invalid pid: \" + pid)`.",
      "services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java : TaskFragmentOrganizerState.prepareActivityReparentedToTask: the condition for passing the real token changes from `activity.getPid() == mOrganizerPid` to `activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid`. Otherwise the existing fallback creates a temporary Binder token stored in mTemporaryActivityTokens and removed after TEMPORARY_ACTIVITY_TOKEN_TIMEOUT_MS.",
      "Single commit 36e65fce2d51 in android-security-14.0.0_r25 (lists three Cherrypick-From changes), 6 insertions and 1 deletion in one file."
     ],
     "completeness": [
      "The fix changes both the registration path (rejects pid <= 0) and the real-token decision in prepareActivityReparentedToTask (adds a uid match).",
      "The material does not show whether mOrganizerPid is used elsewhere as an ownership test, or whether other organizer controllers record pids the same way.",
      "The earlier task-level checks (task.effectiveUid != mOrganizerUid, isAllowedToEmbedActivity, isAllowedToEmbedActivityInTrustedMode) are unchanged."
     ],
     "behavior_changes": [
      "Organizer registration now throws IllegalStateException(\"Cannot register from invalid pid: <pid>\") when pid <= 0; previously such registrations succeeded.",
      "An activity whose pid matches the organizer pid but whose uid differs now gets a temporary token (with the existing timeout cleanup) instead of the real activity.token."
     ],
     "preconditions": [
      "Per the commit message, a malicious app registers the TaskFragment organizer via a one-way binder call so that it appears to run on pid 0.",
      "An activity is reparented into a Task whose effectiveUid equals the organizer uid and which passes isAllowedToEmbedActivity / isAllowedToEmbedActivityInTrustedMode.",
      "The permission requirements for registering an organizer are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 36e65fce2d51 in android-security-14.0.0_r25; at android-security-14.0.0_r29 7/8 lines of the patched region are present.",
      "15": "announced-not-published: bulletin lists 15 as affected but no public commit resolved; at android-security-15.0.0_r17 6/8 lines of the patched region are present, fix state not confirmable from the material.",
      "16": "not affected per the bulletin (not listed); at android-security-16.0.0_r8 6/8 lines of the patched region are present.",
      "17": "not affected per the bulletin (not listed); at android-security-17.0.0_r1 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-file change: add the `pid <= 0` guard before mTaskFragmentOrganizerState.put(...) in the registration path, and the `&& activity.getUid() == mOrganizerUid` clause in prepareActivityReparentedToTask. Both use fields and methods already present in the Android 14 code shown.",
      "When checking a branch, verify both hunks separately rather than relying on the line-presence count."
     ],
     "confidence": "medium",
     "unknowns": [
      "How the pid passed to the registration path is obtained is not shown in the diff context.",
      "Which activities match the organizer pid 0 in practice is not shown.",
      "Which of the three Cherrypick-From changes correspond to which hunk.",
      "Whether the 6/8 lines present on the Android 15/16/17 branches indicate a partial fix, a differently worded fix, or context drift.",
      "Why the bulletin affected-versions list includes 16-qpr2-next while Android 16 is not listed per the per-major facts."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48641": {
     "cve": "CVE-2025-48641",
     "summary": "In the ST NFC AIDL HAL (platform/hardware/st/nfc, aidl/Nfc.h), the static eventCallback and dataCallback now copy the static shared_ptr Nfc::mCallback into a local std::shared_ptr under a new static pthread mutex sCallbackLock and call sendEvent/sendData on that local strong reference, instead of null-checking and dereferencing the shared static directly.",
     "component": "platform/hardware/st/nfc, NFC AIDL HAL (aidl/Nfc.h, struct Nfc : BnNfc, static callbacks eventCallback/dataCallback)",
     "weakness": "Use-after-free caused by a race (unsynchronized access to a shared std::shared_ptr from an asynchronous callback thread)",
     "root_cause": "Nfc::eventCallback(uint8_t, uint8_t) and Nfc::dataCallback(uint16_t, uint8_t*) are static functions. They checked the static member shared_ptr 'mCallback' for nullptr and then called mCallback->sendEvent(...) / mCallback->sendData(...) directly on the shared static. According to the commit message, an asynchronous thread running eventCallback called a method on mCallback right after Nfc::open on the main thread had destroyed the underlying object. Because of the race, the simple null check was not enough.",
     "fix_analysis": [
      "aidl/Nfc.h (file scope): adds 'static pthread_mutex_t sCallbackLock = PTHREAD_MUTEX_INITIALIZER;' before struct Nfc.",
      "aidl/Nfc.h:Nfc::eventCallback: declares 'std::shared_ptr<INfcClientCallback> localCallback', locks sCallbackLock, copies mCallback into localCallback and unlocks. It then null-checks localCallback and calls localCallback->sendEvent(mEvent, mStatus). The local copy holds a strong reference, which keeps the callback object alive while sendEvent runs.",
      "aidl/Nfc.h:Nfc::dataCallback: same pattern. The locked copy into localCallback is taken before the std::vector<uint8_t> data is built, and the call becomes localCallback->sendData(data), guarded by 'if (localCallback != nullptr)'.",
      "The commit changes only aidl/Nfc.h (diffstat: 1 file changed, 14 insertions, 4 deletions)."
     ],
     "completeness": [
      "Covers both static reader paths in aidl/Nfc.h that dereference mCallback: eventCallback (sendEvent) and dataCallback (sendData). The strong local reference keeps the callee object alive for the duration of the call.",
      "The commit message says the read and the write of mCallback are protected by the lock, but the diff changes only the readers. The writer side (where mCallback is assigned, e.g. Nfc::open/close) is not modified by this commit and is not shown in the material. Whether the write is done under sCallbackLock cannot be confirmed."
     ],
     "behavior_changes": [
      "Each eventCallback/dataCallback call now briefly takes and releases a pthread mutex before using the client callback.",
      "The INfcClientCallback object stays referenced for the duration of sendEvent/sendData even if mCallback is changed concurrently.",
      "No API, error code or return value changes are visible in the diff."
     ],
     "preconditions": [
      "The device uses the ST NFC AIDL HAL from platform/hardware/st/nfc (struct Nfc in aidl/Nfc.h).",
      "Events or data must be delivered through Nfc::eventCallback or Nfc::dataCallback on an asynchronous thread.",
      "A concurrent Nfc::open (per the commit message) must destroy the INfcClientCallback object while the callback thread is using it. The INfc AIDL interface is normally used by the NFC system service via binder (general platform knowledge)."
     ],
     "per_major": {
      "14": "same fix: commit 502f1802c4e1 in android-security-14.0.0_r25 (cherry-pick of fc619c3348188ff0020cdeb7d4c4728a0246fe1a, Change-Id I8e9e83be7f939bbfc183cb3879808ceacf931450). At android-security-14.0.0_r29, 7 of the 8 lines of the patched region are present.",
      "15": "same fix: commit ffe16073e613 in android-security-15.0.0_r13, identical diff (same blob ids 2ac623b..af9140d as Android 14). At android-security-15.0.0_r17, 7 of the 8 lines of the patched region are present.",
      "16": "same fix: commit c6da9eeb710c in android-security-16.0.0_r4 (blobs 707bd65..fd23e24). The hunks are the same; a dump() override declared before eventCallback shifts the offsets by one line. At android-security-16.0.0_r8, 7 of the 8 lines of the patched region are present.",
      "17": "not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 7 of the 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-commit, header-only change to aidl/Nfc.h. Android 14 and 15 share the pre-fix blob 2ac623b; Android 16 differs only by the dump() declaration. When backporting, also check where mCallback is written (Nfc::open/close) to decide whether those writes should take sCallbackLock. This commit does not touch them, and the material does not show them."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the writer side (Nfc::open / Nfc::close) assigns mCallback under sCallbackLock. The material shows only the header change and the claim in the commit message.",
      "If the writer does not take the same mutex, whether a residual data race remains on the shared_ptr object itself.",
      "Which translation units include aidl/Nfc.h and use sCallbackLock. This matters because sCallbackLock is a static defined in a header, so each including translation unit gets its own copy.",
      "Which thread or component in the NFC HAL stack calls eventCallback/dataCallback, and how the callbacks are registered.",
      "Why Android 17 is not listed, and how the fix reached android-security-17.0.0_r1."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48642": {
     "cve": "CVE-2025-48642",
     "summary": "Changes pvmfw's aarch64 data-cache maintenance from \"dc cvau\" (clean to Point of Unification) to \"dc cvac\" (clean to Point of Coherency) in the exit path jump_to_payload and in the flush_region helper, so that zeroed memory that could hold secrets reaches main memory.",
     "component": "platform/packages/modules/Virtualization, pvmfw (protected VM firmware) / vmbase, aarch64 cache maintenance",
     "weakness": "Incomplete clearing of sensitive data before transfer of control (cache maintenance performed to the wrong point: PoU instead of PoC), leading to information disclosure",
     "root_cause": "In pvmfw/src/entry.rs::jump_to_payload, pvmfw zeroes .data/.bss (skipping the BCC) and the stack. It then flushes the d-cache over those regions with \"dc cvau\" before writing sctlr_el1 (disabling caches and the page table, per the code comment) and jumping to the payload. pvmfw/src/helpers.rs::flush_region, which flush and flushed_zeroize build on, also cleaned lines with \"dc cvau\". \"dc cvau\" only cleans to the Point of Unification. Per the commit message, some SoCs have a unified cache before the Point of Coherency, and there a clean to PoU might not flush the data to main memory. As a result, zeroing of memory that could hold secrets might not reach main memory.",
     "fix_analysis": [
      "pvmfw/src/entry.rs::jump_to_payload: the two inline-asm loops that clean the d-cache over .data & .bss (including BCC) and over the stack region change from \"0: dc cvau, {cache_line}\" to \"0: dc cvac, {cache_line}\". Each line is now cleaned to the Point of Coherency before sctlr_el1 is rewritten and the payload is entered.",
      "pvmfw/src/helpers.rs::flush_region: the per-line asm!(\"dc cvau, {x}\") becomes asm!(\"dc cvac, {x}\"). This also changes flush() and flushed_zeroize(), which are built on top of it.",
      "The loop structure, the line-size computation (min_dcache_line_size from ctr_el0 DminLine) and the zeroing sequences are unchanged. Only the cache maintenance target point changes."
     ],
     "completeness": [
      "Covers every \"dc cvau\" instance in the two patched files: both loops in jump_to_payload and the single instruction in flush_region (and so flush/flushed_zeroize).",
      "On Android 14 the commit touches only pvmfw/. The commit title says \"vmbase,pvmfw\", and the Android 15 and 16 versions also patch a vmbase flush_region (vmbase/src/memory/util.rs on 15, libs/libvmbase/src/arch.rs on 16). The material does not show whether the Android 14 vmbase crate has its own \"dc cvau\" flush_region (see potential_variants).",
      "After the fix, the doc comments on helpers.rs::flush and flushed_zeroize still say \"to the point of unification\". These are stale comments with no functional effect.",
      "No invalidate or other cache maintenance was added; only the clean target changed."
     ],
     "behavior_changes": [
      "Cache cleans in the pvmfw exit path and flush helpers now target the Point of Coherency instead of the Point of Unification. There is no API, error-code or interface change."
     ],
     "preconditions": [
      "The device runs pvmfw (protected VM firmware) on aarch64 and pvmfw hands off to the guest payload through jump_to_payload.",
      "The SoC cache topology has a unified cache before the Point of Coherency, per the commit message.",
      "Callers of flush()/flushed_zeroize() in pvmfw: unknown from the material."
     ],
     "per_major": {
      "14": "adapted fix (ed2818f8301e in android-security-14.0.0_r25): patches pvmfw/src/entry.rs::jump_to_payload (2 sites) and pvmfw/src/helpers.rs::flush_region. At android-security-14.0.0_r29 the code shows \"dc cvac\" at all three sites. The resolver reports 6/8 lines of the patched region present.",
      "15": "adapted fix (fa7ca326d451 in android-security-15.0.0_r13): the same two sites in pvmfw/src/entry.rs::jump_to_payload plus vmbase/src/memory/util.rs::flush_region. Resolved; 6/8 lines reported present at android-security-15.0.0_r17.",
      "16": "adapted fix (f0271f36388e in android-security-16.0.0_r4): three sites in guest/pvmfw/src/arch/aarch64/payload.rs::jump_to_payload (.data/.bss, stack, and an additional EH stack loop) plus libs/libvmbase/src/arch.rs::flush_region (crate::dc!(\"cvau\") -> crate::dc!(\"cvac\")). Resolved; 6/8 lines reported present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin. The patched Android 14 files (pvmfw/src/entry.rs, pvmfw/src/helpers.rs) are absent at android-security-17.0.0_r1; the material does not show where or whether the equivalent code exists there."
     },
     "backport_notes": [
      "Single commit with a mechanical change: replace every \"dc cvau\" with \"dc cvac\" in pvmfw's jump_to_payload and in the file holding flush_region on the target branch (pvmfw/src/helpers.rs on 14, vmbase/src/memory/util.rs on 15, libs/libvmbase/src/arch.rs on 16). Grep the whole Virtualization tree for \"cvau\", because the flush helpers live in different files across releases.",
      "No Fix-in-fork section was provided, so no per-file fork verdict can be reported."
     ],
     "confidence": "high",
     "unknowns": [
      "Which SoCs have the unified-cache-before-PoC topology; the material only references b/434562039.",
      "Exactly what data could be disclosed and to whom, beyond the code comment that the zeroed regions could hold secrets.",
      "Whether the Android 14 vmbase crate (vmbase/src/memory/util.rs) contains its own \"dc cvau\" flush_region at android-security-14.0.0_r25/r29; the Android 14 commit does not touch it.",
      "Why the resolver reports 6/8 lines of the patched region on each branch.",
      "The Android 17 layout of the pvmfw/vmbase code."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48644": {
     "cve": "CVE-2025-48644",
     "summary": "Caps the cumulative size of resolved InputMethodInfo metadata attributes at MAX_METADATA_SIZE_BYTES via a TypedArrayWrapper/MetadataReadBytesTracker in the InputMethodInfo constructor, and moves the IInputMethodManager list-returning IPCs (getInputMethodList, getEnabledInputMethodList, getEnabledInputMethodSubtypeList) to blob-backed InputMethodInfoSafeList/InputMethodSubtypeSafeList parcelables to avoid oversized Binder transactions.",
     "component": "platform/frameworks/base, input method framework (android.view.inputmethod.InputMethodInfo, com.android.internal.inputmethod, InputMethodManagerService)",
     "weakness": "Uncontrolled resource consumption: missing size limit on metadata strings resolved at parse time, plus unbounded Binder reply payloads",
     "root_cause": "The InputMethodInfo constructor parses an IME's <input-method> XML metadata with res.obtainAttributes() and TypedArray.getString()/getBoolean()/getInt()/getResourceId(). The only size checks were a validation of the raw XML file size (the InputStream.skip() loop) and a 1000-char COMPONENT_NAME_MAX_LENGTH check on si.name and settingsActivity. String attributes may be resource references (e.g. @string/...) whose resolved value is much larger than the XML file. Per the commit messages, this can lead to OOM during parsing or to large Binder transactions (TransactionTooLargeException) when List<InputMethodInfo> / List<InputMethodSubtype> are returned over IPC. Subtype string attributes had no length limit in the old code.",
     "fix_analysis": [
      "Commit 558ee85e1d4a (core/java/android/view/inputmethod/InputMethodInfo.java, constructor): all TypedArray reads for the <input-method> tag and for each <subtype> tag now go through a new nested class TypedArrayWrapper (createForMethod / createForSubtype). All of them share one MetadataReadBytesTracker for the whole IME. getString() charges ret.length() * Character.BYTES (0 for null). getInt()/getResourceId() charge Integer.BYTES and getBoolean() charges 1 byte, but only when hasValue(index) is true. MetadataReadBytesTracker.onReadBytes() throws XmlPullParserException once the cumulative total exceeds MAX_METADATA_SIZE_BYTES (200*1024 bytes per the test comment). The wrapper is AutoCloseable: close() calls TypedArray.recycle(), replacing the manual sa.recycle()/a.recycle().",
      "Same commit: TypedArrayWrapper.getString() also enforces a per-attribute length via getMaxLength(index). For the <input-method> tag, InputMethod_settingsActivity is capped at COMPONENT_NAME_MAX_LENGTH (on 15/16 also InputMethod_languageSettingsActivity). All other method attributes and all subtype strings use Integer.MAX_VALUE. The si.name > COMPONENT_NAME_MAX_LENGTH check is moved out of the TypedArray block to just before the 'Validate file size using InputStream.skip()' loop. The subtype loop is restructured (continue on non-START_TAG, subtype built inside try-with-resources, then isAuxIme/subtypes.add after the block).",
      "Commit b9455ddd2a42: adds core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.{java,aidl}, a Parcelable that marshals the List<InputMethodInfo> into a byte[] and writes it with Parcel.writeBlob()/readBlob(). IInputMethodManager.aidl changes getInputMethodList() and getEnabledInputMethodList() to return InputMethodInfoSafeList. InputMethodManagerService wraps the results with InputMethodInfoSafeList.create()/empty(), and IInputMethodManagerGlobalInvoker unwraps them with InputMethodInfoSafeList.extractFrom(). The commit message states that the upstream flag guard was removed for this backport.",
      "Commit bc52aa800354: adds AbstractSafeList.java (shared marshall()/unmarshall()/extractFrom(), with a null-from check) and InputMethodSubtypeSafeList.{java,aidl}. InputMethodInfoSafeList is refactored to extend AbstractSafeList (empty() removed; create(null) yields an empty list). IInputMethodManager.aidl getEnabledInputMethodSubtypeList() now returns InputMethodSubtypeSafeList. InputMethodManagerService.getEnabledInputMethodSubtypeList() wraps the result with InputMethodSubtypeSafeList.create(), and IInputMethodManagerGlobalInvoker unwraps it with extractFrom(). In IMMS, the empty returns of getInputMethodList/getEnabledInputMethodList switch from empty() to create(null).",
      "Tests: InputMethodInfoTest gains TypedArrayWrapper unit tests (long settingsActivity rejected, 103 x 1000-char reads exceed the tracker, close() recycles). The new InputMethodInfoSafeListTest, AbstractSafeListTest and InputMethodSubtypeSafeListTest cover marshalling round trips."
     ],
     "completeness": [
      "Covered: every attribute read in the constructor, for both <input-method> and <subtype> tags, goes through a single per-IME MetadataReadBytesTracker. The sum of resolved string lengths (plus small fixed costs for ints, booleans and resource ids that are present) is therefore bounded at MAX_METADATA_SIZE_BYTES regardless of the number of subtypes. The raw XML file-size check is retained.",
      "Per-attribute string limits are narrow. In the Android 14 version, getMaxLength() applies COMPONENT_NAME_MAX_LENGTH only to InputMethod_settingsActivity. InputMethod_stylusHandwritingSettingsActivity and all subtype strings return Integer.MAX_VALUE (explicit TODO(b/456008595) and 'No limits for strings in subtype for now' comments). Those strings are bounded only by the cumulative tracker and by the resource limit DEFAULT_MAX_STRING_ATTR_LENGTH mentioned in the code comment.",
      "The class javadoc names STRING_ATTRIBUTES_MAX_CHAR_LENGTH as the limit for 'other string attributes', but the code does not use such a constant. Only the cumulative limit applies to them.",
      "getResourceId() charges Integer.BYTES only. The size of any resource referenced by id is not counted against this limit.",
      "Binder hardening covers the three list-returning IInputMethodManager methods (getInputMethodList, getEnabledInputMethodList, getEnabledInputMethodSubtypeList). getCurrentInputMethodInfoAsUser still returns a raw InputMethodInfo.",
      "In commit b9455ddd2a42, InputMethodInfoSafeList.extractFrom() dereferences from.mBuffer without a null check despite the @Nullable parameter. Commit bc52aa800354 adds the null check in AbstractSafeList.extractFrom(). All three commits should be applied together."
     ],
     "behavior_changes": [
      "An IME whose read metadata attributes cumulatively exceed MAX_METADATA_SIZE_BYTES (200 KB per the test comment) now fails InputMethodInfo construction with XmlPullParserException ('The input method service has metadata exceeds the ... byte limit').",
      "The error message for an oversized settingsActivity changes from 'Activity name exceeds maximum of 1000 characters' to 'String resources in input method exceed the length limit of 1000 characters'. An oversized si.name is now rejected before the file-size validation with 'Input method name exceeds 1000 characters'.",
      "Absent int/boolean/resource-id attributes (hasValue false) are not charged. Null strings charge 0 bytes.",
      "IInputMethodManager.getInputMethodList / getEnabledInputMethodList / getEnabledInputMethodSubtypeList now return InputMethodInfoSafeList / InputMethodSubtypeSafeList (blob-backed) instead of List<...>. This is an AIDL signature change to the internal interface. Client-side extractFrom() returns a list of copies and clears the container, so a second extractFrom() on the same object returns an empty list.",
      "The upstream flag guard for InputMethodInfoSafeList was removed in the backport, so the new transport is unconditional on this branch."
     ],
     "preconditions": [
      "An installed package declaring an input method service whose <input-method> metadata attributes resolve to large strings or include many subtypes. Which process parses the metadata, and when, is not shown in the material.",
      "For the Binder-size aspect: a process calling the InputMethodManager list APIs, which reach IInputMethodManagerGlobalInvoker.getInputMethodList / getEnabledInputMethodList / getEnabledInputMethodSubtypeList (per the diff). INTERACT_ACROSS_USERS_FULL is enforced only when the requested userId differs from the calling user (per the IMMS code and the AIDL annotations)."
     ],
     "per_major": {
      "14": "Fixed in android-security-14.0.0_r25 by three cherry-picks: 558ee85e1d4a (parse hardening), b9455ddd2a42 (InputMethodInfoSafeList, upstream flag guard removed) and bc52aa800354 (InputMethodSubtypeSafeList/AbstractSafeList). Adapted: no languageSettingsActivity, isVirtualDeviceOnly or supportsConnectionlessStylusHandwriting attributes; getMaxLength() caps only InputMethod_settingsActivity; the test lives in core/tests/coretests. At android-security-14.0.0_r29, 7/8 lines of the patched region are present, and the SafeList classes and imports are visible in the code context.",
      "15": "Adapted fix in android-security-15.0.0_r13 (903125b03826): same TypedArrayWrapper/MetadataReadBytesTracker, plus languageSettingsActivity under Flags.imeSwitcherRevamp() and isVirtualDeviceOnly / supportsConnectionlessStylusHandwriting reads inside the wrapper. getMaxLength() caps settingsActivity and languageSettingsActivity. The test is in core/tests/InputMethodCoreTests. 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Adapted fix in android-security-16.0.0_r4 (2bca2265ff3e): identical to 15 except the flag is Flags.imeSwitcherRevampApi(). 7/8 lines of the patched region are present at android-security-16.0.0_r8. SafeList commits for 15/16 are not shown in the material.",
      "17": "Not affected: not listed by the bulletin. 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply in order: 558ee85e1d4a (self-contained; uses the existing MAX_METADATA_SIZE_BYTES constant and the file-size validation block), then b9455ddd2a42, then bc52aa800354. bc52aa800354 depends on b9455ddd2a42: it refactors InputMethodInfoSafeList onto AbstractSafeList and removes empty(). The two SafeList commits change IInputMethodManager.aidl return types, so IInputMethodManagerGlobalInvoker, InputMethodManagerService and any other IInputMethodManager implementation in the target tree must be updated in the same step. Per index search, a ZeroJankProxy wrapper implementing IInputMethodManager methods exists; check whether the target branch has it, since the shown diffs do not touch it.",
      "For branches with the IME switcher revamp flag, keep the languageSettingsActivity read inside the TypedArrayWrapper block and add it to getMaxLength(), as in the 15/16 variants. On 14 that attribute is not read."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact value and definition site of MAX_METADATA_SIZE_BYTES are not in the diff (the test comment states 200 * 1024 bytes).",
      "Which process parses IME metadata into InputMethodInfo, and from which call paths.",
      "Whether the two SafeList commits were also applied on Android 15 and 16 (only the parse-hardening commit is shown for those branches).",
      "Whether other IInputMethodManager implementations (e.g. ZeroJankProxy) exist on the security-14 branch and were adjusted; the shown diffs do not include them.",
      "Which of the four linked bugs corresponds to which symptom (OOM vs TransactionTooLargeException) is not stated."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48645": {
     "cve": "CVE-2025-48645",
     "summary": "Wraps the pm.getText() call in DeviceAdminInfo.loadDescription in a try/catch that converts an OutOfMemoryError into a NotFoundException, so an oversized device-admin description resource no longer propagates an Error to the caller.",
     "component": "platform/frameworks/base, device admin (core/java/android/app/admin/DeviceAdminInfo.java)",
     "weakness": "Unhandled exceptional condition (uncaught OutOfMemoryError while loading an app-controlled text resource)",
     "root_cause": "DeviceAdminInfo.loadDescription(PackageManager) called pm.getText(mActivityInfo.packageName, mActivityInfo.descriptionRes, mActivityInfo.applicationInfo) directly. The resource comes from the package that declares the device admin receiver. If loading it threw OutOfMemoryError, the method did not handle it and the Error propagated to the caller. The commit message describes this as a DoS caused by a maliciously large string injected into the Receiver's label, while the code changed is the description load. The bulletin rates the issue EoP, High.",
     "fix_analysis": [
      "core/java/android/app/admin/DeviceAdminInfo.java:loadDescription: the existing pm.getText(...) call is now wrapped in try { ... } catch (OutOfMemoryError e) { throw new NotFoundException(); }. The method already declared throws NotFoundException, so callers that handle a missing description now handle this case through the same path.",
      "No other logic changes: the descriptionRes != 0 check and the trailing throw new NotFoundException() are unchanged. One file, 5 insertions and 1 deletion."
     ],
     "completeness": [
      "Covers only DeviceAdminInfo.loadDescription. It does not limit the size of the loaded text. It only catches the OutOfMemoryError after the load has been attempted.",
      "DeviceAdminInfo.loadLabel, which delegates to mActivityInfo.loadLabel(pm), is not touched by this commit.",
      "At android-security-14.0.0_r29 the file also contains a @hide loadDescriptionSafe(PackageManager) that trims the description to 1024 characters via ComponentInfo#loadDescription. That method is not part of this diff, and the material does not show whether it belongs to this CVE's fix set.",
      "Only OutOfMemoryError is caught. Other Errors or RuntimeExceptions from pm.getText are not handled here."
     ],
     "behavior_changes": [
      "If loading the description resource throws OutOfMemoryError, loadDescription now throws NotFoundException instead of letting the OutOfMemoryError propagate. Callers see the same exception type as for a missing description.",
      "No API signature change: the method already declared throws NotFoundException."
     ],
     "preconditions": [
      "An installed package declares a device admin receiver with a non-zero description resource (mActivityInfo.descriptionRes != 0) that has very large text content.",
      "Some component builds a DeviceAdminInfo for that receiver and calls loadDescription(pm). Per index search, Settings creates DeviceAdminInfo objects in DeviceAdminListPreferenceController.createDeviceAdminInfo, and DeviceAdminListItemTest mocks loadDescription, which suggests that DeviceAdminListItem uses it."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base ecf88469d44f in android-security-14.0.0_r25; 5/5 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 42b92b39e52c in android-security-15.0.0_r13 (identical hunk, different line offset); 5/5 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base cee45869c491 in android-security-16.0.0_r4 (identical hunk); 5/5 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; the patched region (5/5 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The hunk is identical across 14, 15 and 16; only the line offset differs. It applies cleanly wherever loadDescription still calls pm.getText directly. If a tree has a loadDescriptionSafe helper, check whether callers were moved to it, since that is not part of this commit."
     ],
     "confidence": "high",
     "unknowns": [
      "Which process the failure occurs in, and whether that process is privileged. The commit only describes a DoS from a large string, while the bulletin rates the issue EoP.",
      "Why the commit message refers to the Receiver's label when the change is to the description load.",
      "Whether the second Cherrypick-From commit (8102beb663e3...) corresponds to the loadDescriptionSafe addition seen at r29 or to something else. Only the try/catch hunk is shown.",
      "Test coverage: the commit says Test: manual and includes no automated test."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48646": {
     "cve": "CVE-2025-48646",
     "summary": "In ActivityStarter's FLAG_ACTIVITY_FORWARD_RESULT trampoline handling, the fix no longer always copies sourceRecord.launchedFromPackage into callingPackage. It now does so only after PackageManagerInternal.getPackageUid shows the package belongs to the same app as callingUid (UserHandle.isSameApp).",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (services/core/java/com/android/server/wm/ActivityStarter.java)",
     "weakness": "Missing ownership validation of a package name used as caller identity (launchedFromPackage spoofing)",
     "root_cause": "In ActivityStarter.java, an activity can be started with FLAG_ACTIVITY_FORWARD_RESULT when sourceRecord.launchedFromUid == callingUid. In that case the code treated the source as a trampoline and overwrote callingPackage and callingFeatureId with sourceRecord.launchedFromPackage / launchedFromFeatureId. The removed comment said this was safe because \"these two packages come from the same uid\". However, the added test shows that launchedFromPackage and launchedFromUid on an ActivityRecord need not match: an activity started through startNextMatchingActivity() can carry the launchedFromPackage of a different (privileged) app while its launchedFromUid is the caller's own uid. Because the copy had no check, the newly started activity could record a launchedFromPackage that the calling uid does not own.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java (FLAG_ACTIVITY_FORWARD_RESULT block, inside the sourceRecord.launchedFromUid == callingUid check): the code reads sourceRecord.launchedFromPackage into a local variable. If it is non-null, the code gets PackageManagerInternal via mService.getPackageManagerInternalLocked() and calls getPackageUid(launchedFromPackage, 0 /* flags */, UserHandle.getUserId(callingUid)).",
      "callingPackage = launchedFromPackage and callingFeatureId = sourceRecord.launchedFromFeatureId are applied only if UserHandle.isSameApp(packageUid, callingUid) is true. Otherwise callingPackage and callingFeatureId keep the immediate caller's values. The fix also removes the 'this is safe' comment and adds a reference to b/457742426.",
      "services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java: adds testLaunchedFromPackage_nextMatchingActivity_forwardResult. The test builds P1 -> M1 -> M2, where M2 has launchedFromPackage set to the privileged package but launchedFromUid set to the malicious uid. It mocks mMockPackageManager.getPackageUid for both packages, starts P2 from M2 with FLAG_ACTIVITY_FORWARD_RESULT, and asserts that P2.launchedFromPackage and P2.launchedFromUid are the immediate caller's (malicious) values."
     ],
     "completeness": [
      "The fix covers the FLAG_ACTIVITY_FORWARD_RESULT branch in ActivityStarter, which rewrites callingPackage and callingFeatureId from sourceRecord. This is the only consumer shown in the material.",
      "The override is accepted only when UserHandle.isSameApp(packageUid, callingUid) holds.",
      "This commit does not change how the mismatched ActivityRecord is created in the first place (the test says launchedFromPackage is set to a different app than launchedFromUid via startNextMatchingActivity()). The fix hardens the consumer, not the producer. The material does not show whether other consumers of launchedFromPackage trust it without a uid check."
     ],
     "behavior_changes": [
      "callingPackage and callingFeatureId are now replaced by the source's launchedFromPackage and launchedFromFeatureId only when that package resolves to the same app as callingUid. Otherwise the started activity records the immediate caller's package and feature id.",
      "When sourceRecord.launchedFromPackage is null, callingPackage is no longer overwritten with null (before the fix it was).",
      "This start path now makes a PackageManagerInternal.getPackageUid lookup."
     ],
     "preconditions": [
      "An activity start with Intent.FLAG_ACTIVITY_FORWARD_RESULT, a non-null sourceRecord (resultTo token), and requestCode < 0. If requestCode >= 0, START_FORWARD_AND_REQUEST_CONFLICT is returned first.",
      "sourceRecord.launchedFromUid == callingUid, while sourceRecord.launchedFromPackage names a package that callingUid does not own. The added test describes getting such a record via startNextMatchingActivity() from an activity that another (privileged) app launched.",
      "The bulletin rates the issue High / EoP."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base ad0b8a7b63ac in android-security-14.0.0_r25 (hunk at ActivityStarter.java ~line 990). Per the per-major facts, 5/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 04c368b5f527 in android-security-15.0.0_r13, identical hunk at ~line 1030. The test is inserted after the bubble test instead. Per the per-major facts, 5/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c148b4fae634 in android-security-16.0.0_r4, identical hunk at ~line 1142. Per the per-major facts, 5/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. Per the per-major facts, 5/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single self-contained commit. The code hunk is identical across 14, 15 and 16; only the line offsets differ. It uses mService.getPackageManagerInternalLocked(), PackageManagerInternal.getPackageUid and UserHandle.isSameApp, as the diff on each of these branches shows.",
      "The test relies on ActivityBuilder.setLaunchedFromPackage/setLaunchedFromUid and on mMockPackageManager in wmtests. Check that these helpers exist on the target branch."
     ],
     "confidence": "high",
     "unknowns": [
      "How launchedFromPackage ends up mismatched with launchedFromUid on the creating path (startNextMatchingActivity) is described only in the test comment. That code is not in the material.",
      "Whether other consumers of ActivityRecord.launchedFromPackage trust it without a uid check is not shown.",
      "What getPackageUid returns for a package it cannot resolve, and so exactly how the check behaves in that case, is not shown.",
      "Why only 5/8 lines of the patched region are counted as present at the latest tags is not explained in the material.",
      "The material does not say why Android 17 is not listed by the bulletin."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64783": {
     "cve": "CVE-2025-64783",
     "summary": "Google fixed this High-severity information disclosure in external/dng_sdk by updating the whole bundled Adobe DNG SDK to version 1.7.1 build 2410 (61 files, about 2700 insertions) rather than with an isolated point fix.",
     "component": "platform/external/dng_sdk, DNG/TIFF raw image parsing and rendering library (source/dng_*.cpp)",
     "weakness": "unknown (the bulletin type is ID, information disclosure; the diff is a full SDK version bump and does not isolate a single flaw)",
     "root_cause": "The material does not identify the specific defect. The fix commit \"Update to DNG SDK 1.7.1 2410\" (Bug 465781139) replaces the prior SDK snapshot with Adobe's 1.7.1/2410 release. Hardening visible in the shown portion of the diff:\n- source/dng_jxl.cpp dng_jxl_io_buffer::ReadChunk now throws ThrowEndOfFile when no bytes remain and limits each read to the remaining stream length.\n- source/dng_ifd.cpp dng_ifd::ParseTag gains a new tcGainMapMetadata_ISO_21496_1 handler with a 4 MiB tag-count cap.\n- source/dng_gain_map.cpp dng_opcode_GainMap::ProcessArea rewrites its row and column stepping as count-based loops, with rowPitch and colPitch clamped to the overlap size.\n- source/dng_fingerprint.h makes dng_fingerprint::data private behind Data()/MutableData().\nThe material does not show which of these changes (or which of the roughly 3000 unshown diff lines) corresponds to the CVE.",
     "fix_analysis": [
      "Single commit per branch (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8, cherry-picked from internal commits 403d0a45 and defc6b20). It replaces the SDK sources with 1.7.1 build 2410; source/dng_flags.h now defines kDNGSDK_VersionString \"1.7.1\" and kDNGSDK_BuildVersion 2410.",
      "source/dng_jxl.cpp dng_jxl_io_buffer::ReadChunk computes bytes_remaining_in_stream from stream.Length() - stream.Position(). It throws ThrowEndOfFile(\"dng_jxl_io_buffer\") when that is zero and limits next_read_size to min(fChunkSize, remaining). When fIndex + next_read_size would exceed fMaxBufferSize, it memmoves the unconsumed bytes to the buffer start and resets fIndex. dng_jxl.cpp also gains new streaming adapters: jxl_data_writer, jxl_data_writer2 (with a 1 GiB kMaxSize guard) and jxl_image_chunk_reader (which uses SafeUint32Mult for buffer sizing).",
      "source/dng_ifd.cpp dng_ifd::ParseTag has a new tcGainMapMetadata_ISO_21496_1 case. It checks tagType == ttUndefined, rejects tagCount > 4*1024*1024 with ThrowBadFormat, allocates tagCount bytes via host.Allocate and reads them with stream.Get. dng_ifd::IsValidDNG now accepts sfPreviewGainMap alongside sfGainMap. dng_ifd::IsBaselineJPEG now also returns true for ccJPEG + piRGB with 3 or 4 samples.",
      "source/dng_gain_map.cpp dng_opcode_GainMap::ProcessArea clamps rowPitch and colPitch to overlap.H() and overlap.W(). rows and cols become ceiling-divided counts, and the loops iterate rowIdx/colIdx, advancing row and col by the clamped pitch. This replaces the previous pitch-stepped loops (row < overlap.b with row += fAreaSpec.RowPitch(), and col < cols with col += colPitch).",
      "source/dng_fingerprint.h and .cpp:\n- dng_fingerprint::data becomes private (friends: dng_fingerprint_less_than and dng_md5_printer), and an explicit copy constructor and copy assignment are added.\n- dng_md5_printer::Process(const void*, uint32) is renamed ProcessPtr. New overloads Process(const dng_fingerprint&) and Process(const dng_string&) are added, plus Process_bool and Process_size.\n- The dng_md5_printer constructor becomes protected. New concrete classes are dng_md5_printer_le_stream (always little-endian) and dng_md5_direct_printer.\n- Digest call sites across the SDK are converted to the typed APIs and to Put_* calls.",
      "source/dng_exceptions.h and .cpp: dng_exception now derives from std::exception and provides a what() string. With qDNGVerboseExceptions (default 1 in dng_flags.h), the message and sub_message are carried into the exception. DNG_NO_RETURN uses [[noreturn]] under C++11.",
      "source/dng_abort_sniffer.cpp: the dng_sniffer_task destructor is moved out of line, made virtual, and wraps fSniffer->EndTask() in try/catch(...).",
      "source/dng_info.cpp: the dng_info constructor initializes fTIFFBlockOriginalOffset to 0 instead of kDNGStreamInvalidOffset. ParseDNGPrivateData adds a RICOH branch that calls ParseMakerNoteIFD on the private block.",
      "source/dng_image_writer.cpp and .h: WriteTIFF, WriteTIFFWithProfile, WriteDNG and WriteDNGWithMetadata gain a gainMapMetadataBlock parameter and gainMapAltProfileData/gainMapAltProfileSize parameters. The TIFF gain map is written only when both the image and the metadata block are present. The PGTM duplication logic now uses profile_tag_set::ProfileDidWritePGTMToMainIFD."
     ],
     "completeness": [
      "The fix is a version bump, so it carries every upstream change between the previous snapshot and 1.7.1/2410, not just the CVE fix. The material does not allow judging the specific vulnerable path.",
      "The shown diff is truncated (3031 further lines not displayed). This includes the full changes to dng_stream.cpp (+189), dng_pixel_buffer.cpp (+60), dng_linearization_info.cpp (+89), dng_misc_opcodes.cpp (128 changed lines), dng_negative.cpp (96) and dng_read_image.cpp (23), so parser-side bounds changes in those files cannot be assessed here.",
      "The r29 context still shows an Android GOOGLE MODIFICATION block (the plane-clamping loop in dng_misc_opcodes.cpp dng_opcode_MapTable::ProcessArea).",
      "The r29 context for Android 14 shows kDNGSDK_BuildVersion 2502, so the branch was later moved beyond build 2410. This commit alone brings it to 2410."
     ],
     "behavior_changes": [
      "dng_exception is now a std::exception subclass. Throw_dng_error includes message/sub_message in what() when qDNGVerboseExceptions is 1 (its default in dng_flags.h).",
      "JPEG XL input: dng_jxl_io_buffer::ReadChunk throws an end-of-file error when no bytes remain in the stream.",
      "The new tag tcGainMapMetadata_ISO_21496_1 is parsed. A tagCount above 4 MiB raises a bad-format error.",
      "NewSubFileType sfPreviewGainMap is now accepted as a valid gain map IFD. IsBaselineJPEG now treats JPEG RGB with 3 or 4 samples per pixel as baseline.",
      "DNGPrivateData blocks with a RICOH prefix are now parsed as Pentax maker notes.",
      "Several fingerprint and digest computations now use dng_md5_printer_le_stream and typed Put_* calls.",
      "dng_info::fTIFFBlockOriginalOffset is now initialized to 0 rather than kDNGStreamInvalidOffset.",
      "API changes:\n- dng_fingerprint::data is private (use Data()/MutableData()).\n- dng_md5_printer::Process(void*, len) is renamed ProcessPtr.\n- dng_md5_printer can no longer be instantiated directly.\n- WriteTIFF/WriteDNG signatures gain gain-map metadata parameters."
     ],
     "preconditions": [
      "A DNG/TIFF file (or a JPEG XL-compressed DNG) must be parsed or rendered through external/dng_sdk. The material does not show the exact tag or code path for this CVE.",
      "On Android, dng_sdk is used by Skia's SkRawCodec for raw image decoding (general platform knowledge). None of the index matches shown (DngCreator TiffWriter, pHash fuzzer, Settings tests) is a dng_sdk caller."
     ],
     "per_major": {
      "14": "Same fix: platform/external/dng_sdk d0bfe8eeee73 (Update to DNG SDK 1.7.1 2410) in android-security-14.0.0_r25. At android-security-14.0.0_r29 the patched region is fully present (8/8 lines), and the tree has since moved to build 2502.",
      "15": "Same fix: 45da10961287 in android-security-15.0.0_r13. The shown portion of the diff is identical to Android 14. Present at android-security-15.0.0_r17 (8/8).",
      "16": "Same fix: 6b5cf2a88ebd in android-security-16.0.0_r4. Present at android-security-16.0.0_r8 (8/8).",
      "17": "Not listed by the bulletin (not affected per the bulletin). The patched code is present at android-security-17.0.0_r1 (8/8)."
     },
     "backport_notes": [
      "This is a whole-SDK upgrade, not a targeted patch. Backporting means taking the full 1.7.1/2410 tree while keeping Android-specific code (for example, the GOOGLE MODIFICATION blocks in dng_misc_opcodes.cpp). The API renames (dng_fingerprint::data to Data()/MutableData(), dng_md5_printer::Process to ProcessPtr, new WriteTIFF/WriteDNG parameters) will break any out-of-tree callers, which must be fixed in the same change.",
      "The same Change-Id was applied to 14, 15 and 16, and no per-branch adaptation is visible in the shown diffs, so the expected path is to cherry-pick the whole commit."
     ],
     "confidence": "low",
     "unknowns": [
      "Which specific code change in the SDK update corresponds to CVE-2025-64783, and the exact trigger (tag, compression type or opcode) for the information disclosure.",
      "The pre-fix body of dng_jxl_io_buffer::ReadChunk, which the diff does not show.",
      "Content of the roughly 3000 truncated diff lines, including the changes to dng_stream.cpp, dng_pixel_buffer.cpp, dng_linearization_info.cpp and dng_misc_opcodes.cpp.",
      "Why Android 17 is not listed as affected while its tree contains the same code."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64784": {
     "cve": "CVE-2025-64784",
     "summary": "Google fixed this High-severity information-disclosure issue in platform/external/dng_sdk by replacing the vendored Adobe DNG SDK with upstream version 1.7.1 build 2410 (61 files, +2721/-694). This is a wholesale update with many parser, stream and fingerprinting changes rather than a single targeted check.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK: DNG/TIFF/JXL parsing, image reading, opcodes, fingerprinting)",
     "weakness": "unknown (bulletin type is ID; the diff is a bulk SDK version bump and the material does not isolate the specific flaw)",
     "root_cause": "Not determinable from the material. The fix commit \"Update to DNG SDK 1.7.1 2410\" (Bug 465781139) imports a full upstream SDK release, and the material does not identify which changed code path is behind the information-disclosure report. Changes visible in the shown diff:\n- dng_ifd.cpp ParseTag adds a bounded (4 MiB) reader for the new tcGainMapMetadata_ISO_21496_1 tag.\n- dng_gain_map.cpp dng_opcode_GainMap::ProcessArea reworks row/column stepping: rowPitch and colPitch are clamped to the overlap size, and iteration uses explicit row/column counts.\n- dng_fingerprint's raw data[] array becomes private, with Data()/MutableData() accessors, and typed Get/Put(dng_fingerprint) calls replace raw 16-byte pointer reads/writes.\n\nThe post-fix dng_jxl.cpp dng_jxl_io_buffer::ReadChunk checks the bytes remaining in the stream, throws ThrowEndOfFile when none remain, and clamps the read size. The old body is truncated in the diff, so it is unknown whether this logic is new. Post-fix context also shows:\n- DNG_REQUIRE bounds checks in dng_linearize_plane::Process and dng_pixel_buffer::SetConstant;\n- scale/radius/overflow checks in dng_resample_weights::Initialize;\n- a dng_stream::Get overload with a maxOverRead parameter.\nThe diff hunks for those files are not shown, so it is unknown whether this commit introduced them.",
     "fix_analysis": [
      "Single commit d0bfe8eeee73 (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8, cherry-picked from two internal commits). It sets source/dng_flags.h to kDNGSDK_VersionString \"1.7.1\" / kDNGSDK_BuildVersion 2410 and updates 61 source files to that upstream release.",
      "source/dng_jxl.cpp dng_jxl_io_buffer::ReadChunk, as it reads after the fix: computes bytes_remaining_in_stream; throws ThrowEndOfFile(\"dng_jxl_io_buffer\") when it is zero; limits next_read_size to min(fChunkSize, remaining stream bytes); memmoves leftover data to the buffer start when the next chunk would exceed fMaxBufferSize; then reads with stream.Get. The diff does not show whether the old ReadChunk already did this, because its body is truncated and the hunk appears to be a realignment around large added blocks.",
      "source/dng_ifd.cpp dng_ifd::ParseTag: new case tcGainMapMetadata_ISO_21496_1. It requires ttUndefined, rejects tagCount > 4*1024*1024 with ThrowBadFormat, allocates tagCount bytes via host.Allocate and reads them into fGainMapMetadata. dng_ifd::IsValidDNG now accepts sfPreviewGainMap alongside sfGainMap.",
      "source/dng_gain_map.cpp dng_opcode_GainMap::ProcessArea: rowPitch = Min(fAreaSpec.RowPitch(), overlap.H()) and colPitch = Min(fAreaSpec.ColPitch(), overlap.W()); rows and cols are computed as ceil(overlap/pitch); the row and column loops iterate by index counts and advance by the clamped pitch. This replaces loops that stepped by the unclamped fAreaSpec.RowPitch() and by colPitch against overlap.W().",
      "source/dng_fingerprint.h/.cpp changes:\n- dng_fingerprint::data is made private (friends: dng_fingerprint_less_than, dng_md5_printer), with an explicit copy constructor/assignment and Data()/MutableData().\n- dng_md5_printer::Process(const void*, uint32) is renamed to virtual ProcessPtr. New methods: Process(const dng_fingerprint&), Process(const dng_string&), Process_bool, Process_size.\n- The dng_md5_printer constructor is made protected. New classes: dng_md5_printer_le_stream (always little-endian) and dng_md5_direct_printer.\n- dng_md5_printer_stream::ProcessPtr flushes before hashing, so interleaved Put and Process calls are serialized correctly.\nCallers across the SDK are converted to typed fingerprint Get/Put and to Put_uint32/Put_real32/Put_real64/Put_swap4/Put_swap8 instead of hashing raw in-memory bytes.",
      "source/dng_exceptions.h/.cpp: dng_exception now derives from std::exception, carries an fErrorMsg string and implements what(). With qDNGVerboseExceptions (default 1 in dng_flags.h), Throw_dng_error passes message and sub_message into the exception. DNG_NO_RETURN is placed before the return type and uses [[noreturn]] on C++11.",
      "source/dng_abort_sniffer.cpp/.h: the dng_sniffer_task destructor is made virtual and out-of-line, and wraps fSniffer->EndTask() in try/catch(...). dng_abort_sniffer gains virtual SniffSlowHint().",
      "source/dng_image_writer.cpp/.h: WriteTIFF, WriteTIFFWithProfile, WriteDNG and WriteDNGWithMetadata gain gainMapMetadataBlock and gainMapAltProfileData/Size parameters. The TIFF gain-map IFD is only written when both the image and the metadata block are present. The PGTM duplicate-write logic now keys off profile_tag_set::ProfileDidWritePGTMToMainIFD().",
      "Other visible changes:\n- dng_file_stream(int fd, ...) delegates to the mode-string constructor.\n- dng_info::fTIFFBlockOriginalOffset is initialised to 0 instead of kDNGStreamInvalidOffset.\n- dng_info::ParseDNGPrivateData gains a RICOH branch that calls ParseMakerNoteIFD.\n- dng_ifd::IsBaselineJPEG treats ccJPEG + piRGB with 3 or 4 samples as baseline.\n- dng_host::MakeJXLEncodeSettings uses host-attached JXLEncodeSettings when present.\n- dng_flags.h adds sanitizer detection macros and DNG_FALLTHROUGH.\n- The dng_area_task range struct gains fAllocator.\nThe remaining ~3000 diff lines are truncated in the material."
     ],
     "completeness": [
      "The fix is a full upstream release import, so it covers every code path changed between the previously vendored SDK and 1.7.1 build 2410. The material cannot show which single change closes the reported issue, so completeness against the CVE's trigger cannot be judged from the code.",
      "At android-security-14.0.0_r29, dng_flags.h shows kDNGSDK_BuildVersion 2502, so a later SDK update was applied on top of this one. The build 2410 state described by this diff is not the final state of the branch.",
      "The diffs for most of dng_jxl.cpp (about 1200 changed lines) and for dng_stream.cpp (189 lines, including the Get overload with maxOverRead seen in context) are truncated. It is not visible whether all stream-read sites have equivalent end-of-stream handling.",
      "A Google-specific modification (the BEGIN/END GOOGLE MODIFICATION plane-bounds loop in dng_misc_opcodes.cpp dng_opcode_MapTable::ProcessArea) is present in the post-fix tree, so Android's local patch was kept. The truncated dng_misc_opcodes.cpp diff does not show whether this commit touched it."
     ],
     "behavior_changes": [
      "dng_exception is now a std::exception, with what() returning \"dng_error: <code>[: message (sub_message)]\". qDNGVerboseExceptions defaults to 1, so messages are kept in thrown exceptions.",
      "The new tag tcGainMapMetadata_ISO_21496_1 is parsed. A tag larger than 4 MiB throws dng_error_bad_format (\"Gain map metadata block too large\").",
      "NewSubFileType sfPreviewGainMap is now accepted as a valid gain-map IFD.",
      "JPEG-compressed RGB IFDs with 3 or 4 samples are treated as baseline JPEG for reading.",
      "RICOH-prefixed DNGPrivateData is parsed as a Pentax maker note.",
      "Public API changes for in-tree users:\n- dng_fingerprint::data is private; use Data()/MutableData().\n- dng_md5_printer::Process(ptr, len) is now ProcessPtr.\n- dng_md5_printer cannot be instantiated directly; use dng_md5_direct_printer or the stream variants.\n- The WriteTIFF/WriteDNG family gain extra trailing parameters, which shifts the position of useHalfFloat in WriteTIFF/WriteTIFFWithProfile.",
      "Several fingerprints that previously hashed native in-memory byte order (masked RGB tables, hue/sat map interpolation, RGB-to-RGB table data, HDR hint) now hash little-endian serialized values. This can change computed digests on big-endian hosts.",
      "The dng_sniffer_task destructor swallows exceptions from EndTask(). dng_info::fTIFFBlockOriginalOffset starts at 0."
     ],
     "preconditions": [
      "A process linking libdng_sdk parses an attacker-controlled DNG/TIFF file (or a JXL stream embedded in one). The material does not identify the specific tag, opcode or compression path that triggers the reported issue.",
      "For the JXL path: the file uses JPEG XL compressed data and the SDK is built with JXL support (dng_jxl.cpp compiled in).",
      "For the gain-map changes: the file carries a GainMap opcode in an opcode list, a gain-map IFD, or a tcGainMapMetadata_ISO_21496_1 tag."
     ],
     "per_major": {
      "14": "same fix: commit d0bfe8eeee73 \"Update to DNG SDK 1.7.1 2410\" (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8) in android-security-14.0.0_r25. At android-security-14.0.0_r29 the patched region is fully present (8/8 lines), and dng_flags.h already reports a later build (2502), so a further SDK update was applied on top.",
      "15": "same fix: commit 45da10961287 with the same subject, linked by change_id, in android-security-15.0.0_r13. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 6b5cf2a88ebd (the ref the Android 14 commit is linked from) in android-security-16.0.0_r4. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; android-security-17.0.0_r1 already contains the patched region (8/8 lines present)."
     },
     "backport_notes": [
      "This is a wholesale vendored-SDK bump, not a minimal patch, applied via the same change to 14, 15 and 16. Backporting means importing the full 61-file update while keeping Android's local GOOGLE MODIFICATION blocks (e.g. dng_misc_opcodes.cpp) and the qAndroid path in dng_file_stream.cpp. The 14 r29 tree also carries a later build (2502).",
      "Expect API breakage for any in-tree consumer that touches dng_fingerprint::data directly, calls dng_md5_printer::Process(ptr, len), instantiates dng_md5_printer, or calls WriteTIFF/WriteDNG with positional arguments beyond gainMapImage. A Soong build of all dependents is the quickest check (general platform knowledge)."
     ],
     "confidence": "low",
     "unknowns": [
      "Which specific change in the 1.7.1 build 2410 update addresses the reported information disclosure, and what file construct triggers it (the diff is a bulk version bump with roughly 3000 lines truncated).",
      "Whether the dng_jxl_io_buffer::ReadChunk end-of-stream check and read clamp are new in this commit or were already present before it (the old body is truncated in the diff).",
      "Whether the DNG_REQUIRE bounds checks visible in post-fix context (dng_linearize_plane::Process, dng_pixel_buffer::SetConstant), the checks in dng_resample_weights::Initialize and the dng_stream::Get maxOverRead overload were introduced by this commit or by the later build 2502 update.",
      "Which Android processes link libdng_sdk and expose the parser to untrusted input (the index search returned no conclusive consumer)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64893": {
     "cve": "CVE-2025-64893",
     "summary": "The fix syncs platform/external/dng_sdk to Adobe DNG SDK 1.7.1 build 2410 (61 files, +2721/-694). Among many unrelated changes it adds a size cap on the new GainMapMetadata tag, rewrites the dng_opcode_GainMap::ProcessArea loop, adds new JXL chunked reader/writer classes with checked sizing, and encapsulates dng_fingerprint and dng_md5_printer. The material does not isolate which change closes the bulletin's information-disclosure bug.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK: DNG/TIFF parsing, JXL codec glue, opcode processing, fingerprinting)",
     "weakness": "unknown (bulletin type is ID; the diff is a version bump with several hardening changes, none singled out as the fix)",
     "root_cause": "Not identifiable from the material. The commit message says only \"Update to DNG SDK 1.7.1 2410\" (Bug 465781139), and the diff is cut off inside dng_jxl.cpp (3031 more lines not shown). Pre-fix code that the update visibly replaces includes: dng_ifd::ParseTag had no handler, and so no size limit, for tcGainMapMetadata_ISO_21496_1; dng_opcode_GainMap::ProcessArea stepped rows by fAreaSpec.RowPitch() without clamping it to the overlap height; AddDigest routines hashed raw host memory through dng_md5_printer::Process; dng_fingerprint exposed its raw data[] array publicly. The material does not show which of these, or which of the unseen changes in other files, corresponds to the reported disclosure.",
     "fix_analysis": [
      "source/dng_jxl.cpp: adds the new classes jxl_data_writer, jxl_data_writer2 and jxl_image_chunk_reader, plus jxl_buffer_chunk_reader (its body is cut off). jxl_image_chunk_reader::get_color_channel_data_at computes fRowStep and bytesNeeded with SafeUint32Mult (dng_safe_arithmetic.h is now included) before fHost.Allocate. jxl_data_writer2 caps requested buffer sizes at kMaxSize (1073741824 bytes) and only writes finalized ranges within that bound. The ReadChunk body appears as added lines, but the hunk is cut off, so the material does not show whether its checks are new.",
      "source/dng_ifd.cpp: dng_ifd::ParseTag adds a tcGainMapMetadata_ISO_21496_1 case. It requires ttUndefined, rejects tagCount > 4 MiB with ThrowBadFormat(\"Gain map metadata block too large\"), then allocates exactly tagCount bytes and reads them with stream.Get into the new dng_ifd::fGainMapMetadata. dng_ifd::IsValidDNG now accepts sfPreviewGainMap alongside sfGainMap.",
      "source/dng_gain_map.cpp: dng_opcode_GainMap::ProcessArea now clamps rowPitch to overlap.H() and colPitch to overlap.W(). It precomputes rows and cols as ceiling divisions and loops by index (rowIdx/colIdx) with explicit row += rowPitch and col += colPitch. This replaces the row loop that stepped by the unclamped fAreaSpec.RowPitch().",
      "source/dng_fingerprint.h/.cpp: dng_fingerprint::data becomes private (friends: dng_md5_printer and dng_fingerprint_less_than), with an explicit copy constructor, operator= and Data()/MutableData() accessors. dng_md5_printer::Process(const void*, uint32) is renamed to the virtual ProcessPtr. New methods: Process(const dng_fingerprint&), Process(const dng_string&), Process_bool, Process_size. dng_md5_printer_stream now inherits dng_md5_printer publicly, and its ProcessPtr calls Flush() before hashing. New classes dng_md5_printer_le_stream and dng_md5_direct_printer are added. Visible call sites (dng_big_table.cpp, dng_bmff.cpp, dng_camera_profile.cpp, dng_exif.cpp, dng_hue_sat_map.cpp, dng_image_writer.cpp, dng_jpeg_image.cpp, dng_ifd.cpp) switch to Get/Put(fingerprint) and to Put_uint32/Put_real32/Put_real64/Put_swap4/Put_swap8 instead of hashing raw memory.",
      "source/dng_exceptions.h/.cpp, dng_errors.h, dng_flags.h: dng_exception now derives from std::exception and carries a message string (qDNGVerboseExceptions defaults to 1). DNG_NO_RETURN uses [[noreturn]]. Sanitizer detection macros and DNG_FALLTHROUGH are added. The SDK version constants are set to 1.7.1 / 2410.",
      "source/dng_abort_sniffer.cpp/.h: the dng_sniffer_task destructor moves out of line, becomes virtual, and wraps fSniffer->EndTask() in try/catch(...).",
      "The diff hunks for the remaining files in the stat (dng_stream.cpp, dng_misc_opcodes.cpp, dng_negative.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_read_image.cpp, dng_resample.cpp, dng_shared.cpp, dng_string.cpp, dng_preview.cpp, dng_update_meta.cpp, dng_xmp*.cpp and others) are not shown and cannot be analysed."
     ],
     "completeness": [
      "The fix is a monolithic SDK sync, not a targeted patch. The material does not identify which code path produced the information disclosure, so coverage of the specific bug cannot be judged from the code.",
      "Visible hardening covers: the new GainMapMetadata tag (4 MiB cap, exact-size allocation), the GainMap opcode row/column loop (pitch clamped to the overlap), checked sizing in the new JXL chunk reader and writer, and digest computation (fields are serialized through the stream Put routines instead of hashed as raw struct memory).",
      "About 3031 lines of the diff are not shown (the rest of dng_jxl.cpp, plus dng_stream.cpp, dng_linearization_info.cpp, dng_misc_opcodes.cpp, dng_pixel_buffer.cpp, dng_negative.cpp, dng_read_image.cpp and others), so whatever checks they add or omit is unknown.",
      "The code context at android-security-14.0.0_r29 shows kDNGSDK_BuildVersion 2502 and a 2025 copyright in dng_stream.cpp, so the branch has since taken a further SDK update. Checks seen in that context (DNG_REQUIRE in dng_pixel_buffer::SetConstant and dng_linearize_plane::Process, the finite/zero checks in dng_resample_weights::Initialize) cannot be attributed to this commit."
     ],
     "behavior_changes": [
      "A GainMapMetadata (ISO 21496-1) tag larger than 4 MiB is rejected with dng_error_bad_format. Before the update, ParseTag had no case for this tag.",
      "dng_exception derives from std::exception, and what() returns \"dng_error: <code>[: message (sub_message)]\" because qDNGVerboseExceptions defaults to 1. Throw_dng_error now passes message and sub_message into the exception.",
      "dng_ifd::IsValidDNG accepts NewSubFileType sfPreviewGainMap (33). dng_ifd::IsBaselineJPEG returns true for ccJPEG + piRGB with 3 or 4 samples per pixel.",
      "dng_info::ParseDNGPrivateData now parses RICOH-prefixed DNGPrivateData as a Pentax maker note IFD. The default of dng_info::fTIFFBlockOriginalOffset changes from kDNGStreamInvalidOffset to 0.",
      "dng_host::MakeJXLEncodeSettings uses settings attached to the host for the lossless use cases.",
      "dng_image_writer: WriteTIFF and WriteTIFFWithProfile gain a gainMapMetadataBlock parameter, inserted before useHalfFloat, followed by the gain map alternate ICC profile data and size. WriteDNG and WriteDNGWithMetadata gain the same three parameters, appended after gainMapLossyCompressed. A TIFF gain map is written only when both the image and the metadata block are supplied. Whether the PGTM tag is written from the negative now depends on whether the main profile actually wrote it.",
      "Digest inputs for masked RGB tables and hue/sat map interpolation are now serialized through stream Put routines. The material does not show whether the resulting values change on little-endian hosts.",
      "Source-incompatible API changes for dependents: dng_fingerprint::data is private (use Data()/MutableData()); dng_md5_printer::Process(ptr, len) is now ProcessPtr; the dng_md5_printer constructor is protected; the dng_sniffer_task destructor is virtual."
     ],
     "preconditions": [
      "dng_sdk parses or renders a DNG/TIFF file (possibly with embedded JXL) controlled by an attacker, through dng_info, dng_ifd, dng_jxl or opcode processing. The material does not show the exact triggering construct.",
      "Per index search, no confirmed in-platform consumer of dng_sdk classes was returned (the results were DngCreator's TiffWriter, a pHash fuzzer and unrelated code), so the material does not show the platform entry point."
     ],
     "per_major": {
      "14": "Same fix: commit d0bfe8eeee73 (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8, cherry-picked from internal commits 403d0a45 and defc6b20) in android-security-14.0.0_r25. At r29, 8/8 lines of the patched region are present, and the code context shows SDK build 2502.",
      "15": "Same fix: commit 45da10961287 (linked by Change-Id, same commit title and matching visible diff) in android-security-15.0.0_r13. 8/8 lines present at r17.",
      "16": "Same fix: commit 6b5cf2a88ebd (linked by Change-Id, matching visible diff) in android-security-16.0.0_r4. 8/8 lines present at r8.",
      "17": "Not affected per the bulletin. The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single monolithic SDK sync, linked by Change-Id across 14, 15 and 16. The material does not show a small targeted patch that could be split out. It breaks source compatibility for out-of-tree users of dng_fingerprint::data, dng_md5_printer::Process(ptr, len) or the dng_md5_printer constructor. Positional callers of WriteTIFF/WriteTIFFWithProfile are also affected, because gainMapMetadataBlock is inserted before useHalfFloat.",
      "The 14 branch head (r29) already carries a later SDK build (2502), so compare a backport target against that newer state, not only against the 2410 diff."
     ],
     "confidence": "low",
     "unknowns": [
      "Which code path and input construct cause the information disclosure. The commit message gives no detail and the diff is a version bump.",
      "Whether the checks in dng_jxl_io_buffer::ReadChunk (EOF and buffer spill) are new in this commit or were already present. The hunk is cut off and appears to be realigned.",
      "The content of the roughly 3031 cut-off diff lines (the rest of dng_jxl.cpp, plus dng_stream.cpp, dng_linearization_info.cpp, dng_misc_opcodes.cpp, dng_pixel_buffer.cpp, dng_negative.cpp, dng_read_image.cpp, dng_resample.cpp, dng_shared.cpp, dng_string.cpp, dng_xmp*.cpp, dng_preview.cpp, dng_update_meta.cpp, dng_matrix.cpp).",
      "What each of the two internal cherry-picked commits (403d0a45641560631b0d6a9653c94a22163e3008 and defc6b209f2ea4b724c0122f9c82d5b6053f9425) contained.",
      "Whether the 15 and 16 diffs are byte-identical to the 14 diff beyond the visible portion.",
      "Which Android platform component links dng_sdk and exposes the parser to untrusted input."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0005": {
     "cve": "CVE-2026-0005",
     "summary": "Adds IActivityTaskManager.rebuildSystemLockTaskPinnedMode() (gated by enforceTaskPermission) that re-applies the recents, task-change-listener, status bar and keyguard state of app pinned mode for the existing locked task, has SystemUI's KeyguardViewMediator call it when ATMS reports LOCK_TASK_MODE_PINNED, and makes KeyguardServiceDelegate keep the keyguard enabled flag across a keyguard service disconnect, so that the device stays in app pinned mode upon a SystemUI crash.",
     "component": "platform/frameworks/base, WindowManager lock task (LockTaskController / ActivityTaskManagerService) plus keyguard (SystemUI KeyguardViewMediator, system_server KeyguardServiceDelegate)",
     "weakness": "State desynchronization after component restart: pinned lock task mode restrictions held in system_server were not re-applied after SystemUI crashed, and the keyguard enabled state was reset on keyguard service disconnect",
     "root_cause": "In app pinned mode, LockTaskController keeps mLockTaskModeState == LOCK_TASK_MODE_PINNED and the task in mLockTaskModeTasks, and the restrictions (status bar via setStatusBarState, keyguard via setKeyguardState, recents and task-change notifications) are applied when the mode is entered (the tests show each called once by startLockTaskMode). Before the fix nothing re-applied them after a SystemUI crash while system_server still considered the device pinned. In addition, KeyguardServiceDelegate.onServiceDisconnected called mKeyguardState.reset() without preserving the enabled flag; the fix's save/restore implies the reset could lose a disabled keyguard state that onServiceConnected replays via `if (!mKeyguardState.enabled) setKeyguardEnabled(...)`.",
     "fix_analysis": [
      "core/java/android/app/IActivityTaskManager.aidl: adds `void rebuildSystemLockTaskPinnedMode();` next to startSystemLockTaskMode/stopSystemLockTaskMode.",
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java:rebuildSystemLockTaskPinnedMode(): calls enforceTaskPermission(\"rebuildSystemLockTaskPinnedMode\") (same gate as startSystemLockTaskMode/stopSystemLockTaskMode), clears the calling identity, and under mGlobalLock calls getLockTaskController().rebuildSystemLockTaskPinnedMode().",
      "services/core/java/com/android/server/wm/LockTaskController.java:rebuildSystemLockTaskPinnedMode(): returns (Slog.e) if mLockTaskModeState != LOCK_TASK_MODE_PINNED; returns (Slog.i) if mLockTaskModeTasks is empty; otherwise takes the first locked task, calls mSupervisor.mRecentTasks.onLockTaskModeStateChanged(LOCK_TASK_MODE_PINNED, task.mUserId) synchronously, then on mHandler calls IStatusBarService.showPinningEnterExitToast(true), mTaskChangeNotificationController.notifyLockTaskModeChanged(state), setStatusBarState(state, userId) and setKeyguardState(state, userId), reusing the existing locked task rather than starting a new lock task.",
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java: right after the mBootSendUserPresent / sendUserPresentBroadcast() check, posts a runnable that queries getLockTaskModeState() and, if it equals LOCK_TASK_MODE_PINNED, calls rebuildSystemLockTaskPinnedMode(); RemoteException is logged.",
      "services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java:onServiceDisconnected(): saves mKeyguardState.enabled before mKeyguardState.reset() and restores it afterwards, so the flag is available to the existing `if (!mKeyguardState.enabled) mKeyguardService.setKeyguardEnabled(...)` replay in onServiceConnected.",
      "services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java: four new tests: empty task list, DONT_LOCK task and non-pinned mode are no-ops; pinned mode re-issues notifyLockTaskModeChanged, disableKeyguard, status bar disable/disable2 and RecentTasks.onLockTaskModeStateChanged a second time."
     ],
     "completeness": [
      "Covers only LOCK_TASK_MODE_PINNED: rebuildSystemLockTaskPinnedMode() returns for any other state, and KeyguardViewMediator only triggers it when getLockTaskModeState() == LOCK_TASK_MODE_PINNED. Full lock task mode (LOCK_TASK_MODE_LOCKED) is not rebuilt; whether it has the same exposure is not shown by the material.",
      "Only the first entry of mLockTaskModeTasks is used for the user id.",
      "The rebuild is triggered from one place only, the SystemUI KeyguardViewMediator call site; system_server's KeyguardServiceDelegate.onServiceDisconnected only preserves the enabled flag and re-asserts setLockScreenShown, it does not trigger the rebuild itself.",
      "The restrictions are re-applied asynchronously (SystemUI handler post, then LockTaskController mHandler post); the material does not quantify the window.",
      "The new handler runnable wraps a RemoteException in RuntimeException.",
      "The new binder method uses the same enforceTaskPermission gate as the existing system lock task entry points."
     ],
     "behavior_changes": [
      "New hidden binder method IActivityTaskManager.rebuildSystemLockTaskPinnedMode(), guarded by enforceTaskPermission.",
      "When the rebuild runs while pinned, the screen pinning 'entering' toast is shown again, status bar / keyguard / recents state is re-applied, and task stack listeners receive another lock task mode change notification.",
      "KeyguardServiceDelegate preserves the keyguard enabled flag across a keyguard service disconnect.",
      "New log lines: Slog.e when rebuild is requested outside pinned mode, Slog.i when there are no locked tasks, Log.e in SystemUI if the rebuild binder call fails."
     ],
     "preconditions": [
      "A task is in app pinned mode (mLockTaskModeState == LOCK_TASK_MODE_PINNED with a task in mLockTaskModeTasks), which LockTaskController enters when isSystemCaller is true.",
      "SystemUI crashes while pinned mode is active (per the commit title).",
      "The bulletin classifies the issue as Information Disclosure, High; the material does not state the attacker position."
     ],
     "per_major": {
      "14": "Same fix: 9ff1c081e9f7 in android-security-14.0.0_r25 (RESTRICT AUTOMERGE cherry-pick of 575845147cb4). KeyguardViewMediator uses ActivityTaskManager.getService(), LockTaskController uses mLockTaskModeTasks.get(0), tests use verifyNoInteractions(mSupervisor). Region fully present (8/8) at android-security-14.0.0_r29.",
      "15": "Adapted fix 08abd0bc0451 in android-security-15.0.0_r13: same production logic, but KeyguardViewMediator calls the mActivityTaskManagerService field (and adds an android.app.ActivityTaskManager import), LockTaskController uses mLockTaskModeTasks.getFirst(), and no-op tests use verify(mSupervisor, never()).mRecentTasks.onLockTaskModeStateChanged(...). Region fully present (8/8) at android-security-15.0.0_r17.",
      "16": "Adapted fix 92b109b60414 in android-security-16.0.0_r4, same shape as the Android 15 variant (mActivityTaskManagerService field, getFirst()). Region fully present (8/8) at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed); the patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit touching system_server (ATMS, LockTaskController, KeyguardServiceDelegate), SystemUI (KeyguardViewMediator) and the IActivityTaskManager AIDL, all in frameworks/base; they must ship together since SystemUI calls the new binder method. Android 14 uses ActivityTaskManager.getService() on the SystemUI side, 15/16 use the mActivityTaskManagerService field; pick the form matching the target.",
      "Forks with a customized KeyguardViewMediator should place the rebuild post at the same point (after the mBootSendUserPresent check); the LockTaskController helper relies on existing setStatusBarState/setKeyguardState/getStatusBarService helpers."
     ],
     "confidence": "medium",
     "unknowns": [
      "The enclosing method of the KeyguardViewMediator hunk and when it runs relative to a SystemUI restart are not shown.",
      "What KeyguardState.reset() sets `enabled` to is not shown.",
      "Whether full lock task mode (LOCK_TASK_MODE_LOCKED) loses restrictions after a SystemUI crash is not shown; the fix excludes it.",
      "Whether mLockTaskModeTasks can hold more than one task in pinned mode is not shown.",
      "The precise consequence the bulletin classifies as Information Disclosure is not described in the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0011": {
     "cve": "CVE-2026-0011",
     "summary": "Makes Settings.enableSystemPackageLPw able to reuse the disabled PackageSetting of a shared-UID system app when its update is uninstalled. addPackageLPw now accepts the package even when registerExistingAppId fails for a shared-UID package, and the shared user app ID is restored on the reused setting, so the preloaded app is no longer re-scanned and installed as a new app with a possibly different UID.",
     "component": "platform/frameworks/base, PackageManagerService / Settings (system app enable/disable, UID migration)",
     "weakness": "Logic error in package state handling: the setting of a shared-UID system app is not reused when the app is re-enabled, so the app is installed again as a new package and its UID can change.",
     "root_cause": "In services/core/java/com/android/server/pm/Settings.java, enableSystemPackageLPw rebuilds a disabled system package's setting through addPackageLPw. This happens when the system app's update is uninstalled. addPackageLPw creates a new PackageSetting and only puts it into mPackages if mAppIds.registerExistingAppId(uid, p, name) succeeds. Per the commit message, registerExistingAppId fails for shared-uid packages, so the disabled setting is not reused. Instead, the preloaded version is re-scanned and installed as a new app. When the manifest has sharedUserMaxSdkVersion, this changes the app's UID. Also, the rebuilt setting did not have its shared user app ID copied from the disabled setting.",
     "fix_analysis": [
      "Settings.java:addPackageLPw: adds a new boolean parameter, hasSharedUser. The condition that admits the new PackageSetting into mPackages becomes 'mAppIds.registerExistingAppId(uid, p, name) || hasSharedUser', so a shared-UID package is accepted even when registerExistingAppId fails.",
      "Settings.java:enableSystemPackageLPw: passes p.hasSharedUser() from the disabled setting to addPackageLPw. After a non-null return it calls ret.setSharedUserAppId(p.getSharedUserAppId()), alongside the existing setAppMetadataFilePath and setUpdatedSystemApp(false).",
      "Settings.java, settings-reading path (the addPackageLPw call that logs \"Reading package\"): passes hasSharedUser=false, which keeps the prior behavior for settings read from disk.",
      "services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt: the addPackageLPw mock gains one more nullable() argument to match the new signature."
     ],
     "completeness": [
      "Only the enableSystemPackageLPw path passes the real hasSharedUser value. The settings-reading caller explicitly passes false, so the relaxed condition only affects re-enabling a disabled system package.",
      "The new hasSharedUser bypass does not check that uid is consistent with the shared user's app ID. It relies on the disabled setting's state. Whether the setting is re-linked to the SharedUserSetting package list is not shown in this diff.",
      "The fallback path (re-scanning the preloaded version as a new app) is not changed. For shared-UID system apps, the fix avoids reaching it."
     ],
     "behavior_changes": [
      "For shared-UID system apps, enableSystemPackageLPw can now return a reused PackageSetting where it previously failed. The update-uninstall path then reuses the disabled setting, as it already does for non-shared-UID system apps, instead of re-scanning and installing the preloaded version as a new package.",
      "The restored setting carries the original sharedUserAppId from the disabled setting.",
      "addPackageLPw has a new signature (an extra boolean), which affects all callers and test mocks."
     ],
     "preconditions": [
      "A preloaded system app that uses a shared user ID is updated, and the update is then uninstalled, which triggers Settings.enableSystemPackageLPw.",
      "Per the commit message, the UID-changing outcome requires the manifest to use sharedUserMaxSdkVersion. The exact UID assignment logic is outside the diff."
     ],
     "per_major": {
      "14": "Same fix (77d8e59a1af6 in android-security-14.0.0_r25). The 17-argument addPackageLPw is extended with hasSharedUser, the settings-reading caller passes false, enableSystemPackageLPw sets sharedUserAppId, and MockSystem.kt is updated. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "Adapted fix (65e7256522b4 in android-security-15.0.0_r13). Same logic on the shorter addPackageLPw(name, realName, codePath, uid, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary) signature. The condition becomes '(uid == INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps()) || registerExistingAppId(...) || hasSharedUser'. The reading caller passes false, and setSharedUserAppId is added after setInstallSource. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "Adapted fix (51368785bd09 in android-security-16.0.0_r4), identical in content to the Android 15 variant at shifted line numbers. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "Not affected per the bulletin (not listed). At android-security-17.0.0_r1, 4/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit. On branches with the 17-argument addPackageLPw (Android 14), apply the r25 diff. On branches with the compact signature that includes isSdkLibrary, use the 15/16 variant. Update every addPackageLPw caller and the MockSystem.kt mock argument count, or mockingservicestests will not compile."
     ],
     "confidence": "medium",
     "unknowns": [
      "Why registerExistingAppId fails for shared-uid packages (AppIds internals) is not shown.",
      "The caller of enableSystemPackageLPw and the fallback re-scan path where the UID changes are not in the material.",
      "Why the bulletin rates this as EoP is not stated in the material.",
      "Which line of the patched region is missing at the latest 14/15/16 tags, and how the Android 17 code differs, is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0012": {
     "cve": "CVE-2026-0012",
     "summary": "In SystemUI ExpandableNotificationRow.setHideSensitive, the decision to skip the hide-sensitive (public/private layout) animation and apply visibility and content alphas immediately is now taken also when the row is showing the public (redacted) layout and the showing layout did not change; before the fix this depended only on the `animated` flag (Android 14/15) or used a feature flag plus layout-unchanged (Android 16).",
     "component": "platform/frameworks/base, SystemUI notification row (packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java)",
     "weakness": "Logic error in the lockscreen sensitive-notification redaction transition: the condition for skipping the public/private layout animation (and resetting content alphas) was wrong; the commit message states the prior code \"can cause 0 content alpha\". The exact information-disclosure mechanism is not shown in the material.",
     "root_cause": "setHideSensitive(hideSensitive, animated, delay, duration) computes mShowingPublic = mSensitive && hideSensitive, returns early if mShowingPublicInitialized and the value did not change, and then either applies the new state immediately (cancel animations on mPublicLayout/mPrivateLayout/mChildrenContainer, resetAllContentAlphas(), set mPublicLayout visibility, updateChildrenVisibility()) or calls animateShowingPublic(). On Android 16 before the fix, the immediate path was also taken whenever Flags.skipHideSensitiveNotifAnimation() was on and the showing layout was unchanged, regardless of whether the row was showing the public or the private layout; the commit message calls the prior code too aggressive and says it can cause 0 content alpha. On Android 14/15 before the fix the immediate path depended only on !animated, so with animated=true the unchanged-layout, not-yet-initialized case with mShowingPublic true went through animateShowingPublic(). The fix makes all branches use one skip condition: mShowingPublic && (mShowingPublic == oldShowingPublic).",
     "fix_analysis": [
      "ExpandableNotificationRow.java:setHideSensitive (Android 14 commit 334c15a0ebee, Android 15 commit 679801a99ccd): introduces `boolean isShowingLayoutNotChanged = mShowingPublic == oldShowingPublic;`, reuses it in the existing early return `if (mShowingPublicInitialized && isShowingLayoutNotChanged) return;`, adds `final boolean shouldSkipHideSensitiveAnimation = mShowingPublic && isShowingLayoutNotChanged;` and changes the branch to `if (!animated || shouldSkipHideSensitiveAnimation)` so the immediate (non-animated) path, which cancels pending animations, calls resetAllContentAlphas() and sets mPublicLayout visibility from mShowingPublic, is also taken in that case instead of animateShowingPublic().",
      "ExpandableNotificationRow.java:setHideSensitive (Android 16 commit ef2a8e203164): the skip condition already existed as `Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged`; the fix replaces the flag check with `mShowingPublic`, so the animation is skipped only when the row is showing the public layout and the layout did not change.",
      "Because of the early return, the new skip condition can only be true on a call where mShowingPublicInitialized is false while mShowingPublic is already true and unchanged."
     ],
     "completeness": [
      "The change is confined to the branch selection inside setHideSensitive; animateShowingPublic(), resetAllContentAlphas() and updateChildrenVisibility() are not modified.",
      "On Android 16 the fix narrows an over-broad flag-gated skip; on Android 14/15 there was no skip at all, so the backport adds the narrowed skip rather than removing a broad one. Whether the 14/15 code exhibited the same alpha problem via animateShowingPublic() is not shown in the material.",
      "The code context at android-security-14.0.0_r29 shows the fixed lines (isShowingLayoutNotChanged, shouldSkipHideSensitiveAnimation, `!animated || shouldSkipHideSensitiveAnimation`); the per-major check reports 6/7 lines of the patched region present.",
      "The Flags.skipHideSensitiveNotifAnimation() flag is no longer consulted at this site on Android 16 after the fix; the material does not show whether the flag declaration itself was removed."
     ],
     "behavior_changes": [
      "Android 14/15: when setHideSensitive is called with animated=true on a row whose mShowingPublic is true and unchanged (only possible when mShowingPublicInitialized is false), the transition is now applied immediately (animations cancelled, content alphas reset, mPublicLayout set VISIBLE) instead of running animateShowingPublic().",
      "Android 16: when the row is showing the private layout (mShowingPublic false) and the layout is unchanged, the hide-sensitive animation is no longer skipped even if Flags.skipHideSensitiveNotifAnimation() is enabled; the skip at this site is now independent of that flag.",
      "No new error paths or API changes; the change is SystemUI-internal UI state handling."
     ],
     "preconditions": [
      "A notification row in SystemUI with mSensitive true (sensitive notification) subject to hideSensitive handling, row visibility not GONE.",
      "setHideSensitive(...) invoked with animated=true (otherwise the immediate path was already taken).",
      "mShowingPublicInitialized false while mShowingPublic is already equal to the newly computed value (the only way past the early return with an unchanged layout); on Android 16 before the fix, additionally Flags.skipHideSensitiveNotifAnimation() enabled for the over-broad skip path.",
      "Exact user-visible trigger is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit 334c15a0ebee in android-security-14.0.0_r25 (cherry-pick of e93d4b015c28); the branch had no skip condition, so the fix adds `shouldSkipHideSensitiveAnimation = mShowingPublic && isShowingLayoutNotChanged` to the `!animated` test. At android-security-14.0.0_r29 the fixed code is visible (lines 2804-2812); the per-major check reports 6/7 lines of the patched region present.",
      "15": "adapted fix: commit 679801a99ccd in android-security-15.0.0_r13, same hunk as Android 14 with an extra pre-existing `float oldAlpha = getContentView().getAlpha();` line in context. 7/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "same fix (one-line form): commit ef2a8e203164 in android-security-16.0.0_r4 replaces `Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged` with `mShowingPublic && isShowingLayoutNotChanged`. 4/5 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); 6/7 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "For trees based on Android 14/15 (no flag-gated skip), apply the two-part hunk: introduce isShowingLayoutNotChanged and the shouldSkipHideSensitiveAnimation OR-term on the `!animated` branch; for trees with the Flags.skipHideSensitiveNotifAnimation() form, only the one-line condition swap is needed. Single commit, no dependencies shown."
     ],
     "confidence": "medium",
     "unknowns": [
      "How the wrong skip condition leads to information disclosure (bulletin type ID): the material only states it 'can cause 0 content alpha'.",
      "Which callers set mShowingPublicInitialized back to false while leaving mShowingPublic true, making the new skip path reachable.",
      "Body of animateShowingPublic() and resetAllContentAlphas(), and whether the Flags.skipHideSensitiveNotifAnimation() declaration was removed on Android 16.",
      "Which line of the patched region the per-major check counts as missing on each branch.",
      "Why Android 17 is not listed by the bulletin."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0013": {
     "cve": "CVE-2026-0013",
     "summary": "PickActivity.setupLayout now calls setSelector(null) on the copied \"more apps\" intent so a caller-supplied selector can no longer redirect the activity launch DocumentsUI performs on the caller's behalf.",
     "component": "platform/packages/apps/DocumentsUI, picker (PickActivity / RootsFragment \"more apps\" entries)",
     "weakness": "Intent redirection: incomplete sanitization of a caller-controlled intent that is re-launched with the app's own identity (selector not cleared)",
     "root_cause": "In src/com/android/documentsui/picker/PickActivity.java, setupLayout(Intent intent) builds `moreApps = new Intent(intent)` from the incoming intent to offer other apps that can handle the request. It then clears only the explicit component (setComponent(null)) and the package (setPackage(null)), and leaves the intent's selector in place. The copied intent is passed to RootsFragment.show(..., /* intent= */ moreApps) and to CrossProfileUtils.getCrossProfileResolveInfo(getPackageManager(), moreApps). An Intent selector replaces the main intent for resolution purposes (general platform knowledge). So, as the commit message states, a selector carried on the original intent could make the launch of moreApps resolve to an arbitrary activity started with DocumentsUI's permissions.",
     "fix_analysis": [
      "src/com/android/documentsui/picker/PickActivity.java:setupLayout adds `moreApps.setSelector(null);` immediately after `moreApps.setComponent(null); moreApps.setPackage(null);`. This happens before the copy is used for cross-profile resolution and before it is passed to RootsFragment.show.",
      "The fix is a single commit with a two-line change (one comment, one statement). No other files or logic are touched. Only the `moreApps` copy is changed; the original `intent` object is not modified."
     ],
     "completeness": [
      "The fix covers the one copy site in setupLayout. Both consumers of moreApps (CrossProfileUtils.getCrossProfileResolveInfo and RootsFragment.show) now receive an intent with the component, package and selector cleared.",
      "The selector is the only field added to the sanitized set. Other copied fields of the incoming intent (flags, extras, clip data, data URI, categories) still pass through unchanged. The material does not show whether any of those fields matter for security in the later launch.",
      "The material shows no other place in DocumentsUI that re-dispatches the incoming intent, so coverage beyond this function cannot be judged from the provided code."
     ],
     "behavior_changes": [
      "Any selector on the intent that started PickActivity is now ignored when the list of alternative apps is built and when one of them is launched. Resolution now uses only the main intent fields.",
      "The diff adds no new error, result code or UI change."
     ],
     "preconditions": [
      "A caller starts DocumentsUI's PickActivity with an intent that carries a selector. For setupLayout to call RootsFragment.show with moreApps, the action must be one of ACTION_GET_CONTENT, ACTION_OPEN, ACTION_CREATE, ACTION_OPEN_TREE or ACTION_PICK_COPY_DESTINATION.",
      "App entries built from moreApps are only included when mState.action == ACTION_GET_CONTENT (the `includeApps` argument), and the launch happens through RootsFragment. The material does not show the exact launch call or whether a user tap is needed.",
      "The cross-profile branch also resolves moreApps through CrossProfileUtils.getCrossProfileResolveInfo when mState.supportsCrossProfile() is true."
     ],
     "per_major": {
      "14": "Same fix: a48245c5bfa7 in android-security-14.0.0_r25 (cherry-pick of 32d6a7338dc3, Change-Id I3e2eeaab8990a20fe639a165630ed1773e47fb3c). All 6/6 lines of the patched region are present at android-security-14.0.0_r29. The context differs slightly: 14 calls the method mState.supportsCrossProfile().",
      "15": "Same fix: 15f898d5082a in android-security-15.0.0_r13, with an identical two-line insertion. The surrounding code uses the mState.supportsCrossProfile field and private-space logic. All 6/6 lines are present at android-security-15.0.0_r17.",
      "16": "Same fix: 9f2d3f09f8fd in android-security-16.0.0_r4, with an identical insertion and the same context as 15. All 6/6 lines are present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). At android-security-17.0.0_r1 only 4/6 lines of the patched region are present. The material does not show how the code differs there."
     },
     "backport_notes": [
      "The fix is a single-statement insertion after `moreApps.setPackage(null);` in PickActivity.setupLayout. It sits before the cross-profile code that differs between branches (a method call on 14, a field plus private-space checks on 15/16). DocumentsUI is also shipped as a mainline module (general platform knowledge), so check whether a given build gets the fix through a module update."
     ],
     "confidence": "high",
     "unknowns": [
      "Where exactly RootsFragment starts the moreApps intent, and whether user interaction (tapping an app entry) is required. The launch code is not in the material.",
      "Which apps can start PickActivity (manifest, export status, permissions).",
      "Whether any legitimate callers set a selector and are affected by it being dropped.",
      "Why Android 17 is not affected: whether the copy site was restructured or the selector was already cleared there.",
      "Whether other DocumentsUI entry points copy and re-launch the incoming intent in a similar way."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0014": {
     "cve": "CVE-2026-0014",
     "summary": "Adds an early `Process.isSdkSandboxUid(uid)` check to the attribution-tag validation helper in AppOpsService, so that for a non-null packageName the helper returns false for SDK sandbox UIDs instead of treating them as trusted/system.",
     "component": "platform/frameworks/base, services/core AppOps (AppOpsService)",
     "weakness": "Improper trust/privilege classification of caller UID (missing check for SDK sandbox UIDs in attribution tag validation)",
     "root_cause": "In services/core/java/com/android/server/appop/AppOpsService.java, the boolean helper used for attribution tag validation (its name is not shown in the hunk header) returns true for a null packageName. It then returns true when `UserHandle.getAppId(uid)` is in the system range (`appId > 0 && appId < Process.FIRST_APPLICATION_UID`), and otherwise continues with further logic not shown in the material. It had no handling for SDK sandbox UIDs. Per the commit message, sandboxed UIDs could therefore be considered \"system\" for the purposes of attribution tag validation. The bulletin classifies the impact as DoS (High). The fix's test, AppOpsMemoryUsageTest, suggests the consequence concerns AppOps memory usage, but the material does not describe the mechanism.",
     "fix_analysis": [
      "services/core/java/com/android/server/appop/AppOpsService.java, attribution-tag validation helper (not named in the material, around line 3959 on Android 14): the fix inserts `if (Process.isSdkSandboxUid(uid)) { return false; }` after the existing `if (packageName == null) return true;` and before the `appId < Process.FIRST_APPLICATION_UID` system check.",
      "Effect: for any SDK sandbox UID with a non-null packageName, the helper now returns false right away (untrusted). Neither the system-appId branch nor the remaining validation logic can classify a sandbox UID as trusted.",
      "Single commit, single hunk, three added lines. The change is identical in all three fixed branches (Android 14 7320b5316d76, Android 15 531f3ac25912, Android 16 b51a58ecec96)."
     ],
     "completeness": [
      "The fix covers only this one helper in AppOpsService. Sandbox UIDs are rejected after the null-packageName check and before the system-appId check.",
      "The `packageName == null` early return true still comes before the new check, so a sandbox UID with a null packageName still returns true. The material does not show whether that path is reachable.",
      "The rest of the helper's body (after the system-appId check) is not shown, so the material does not tell which downstream condition originally caused sandbox UIDs to be treated as system.",
      "This change touches no other AppOps trust/validation paths. The material does not show whether sibling helpers perform a similar uid-range-based trust check."
     ],
     "behavior_changes": [
      "Attribution tag validation now returns false for all SDK sandbox UIDs with a non-null packageName, where previously it could return true.",
      "The diff adds no exceptions, error codes or API changes; only the boolean result for sandbox callers changes.",
      "The change is unconditional (Flag: EXEMPT CVE_FIX), not behind a feature flag."
     ],
     "preconditions": [
      "An AppOps operation that reaches the attribution-tag validation helper in AppOpsService with a caller UID in the SDK sandbox UID range and a non-null packageName.",
      "The exact entry points (which AppOps APIs call this helper) are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: 7320b5316d76 in android-security-14.0.0_r25 (cherry-pick of internal 1bc6b146 / 8714d11f); 3/3 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: 531f3ac25912 in android-security-15.0.0_r13, identical three-line hunk at a different line offset (~4718); 3/3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: b51a58ecec96 in android-security-16.0.0_r4, identical three-line hunk (~5043); 3/3 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (3/3 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "A plain three-line insertion between the `packageName == null` return and the `appId < Process.FIRST_APPLICATION_UID` check in AppOpsService.java. It is identical across 14/15/16 apart from line offsets. `Process.isSdkSandboxUid` depends on the SDK sandbox UID range introduced with the SDK Runtime, so it is not available on branches before Android 13 (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The name of the patched helper function and its callers (the hunk header only shows the class; the supplied code context is a different region of the file).",
      "The code path after the system-appId check that originally led to sandbox UIDs being treated as system.",
      "The concrete DoS mechanism (the test name AppOpsMemoryUsageTest hints at memory usage, but the material does not state it).",
      "Whether the null-packageName early return can be reached by a sandbox UID."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0015": {
     "cve": "CVE-2026-0015",
     "summary": "AppOpsService now only accepts proxied attribution tags from trusted proxies (flags without OP_FLAG_UNTRUSTED_PROXIED). It also checks that the proxy package really belongs to the proxy uid before treating it as system, stops the start dry run and finish paths from creating per-tag state, and finds the AttributedOp to finish by clientId and trust flag.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop)",
     "weakness": "Missing trust check on proxy-supplied attribution tags, allowing unbounded per-tag state creation in system_server (DoS per bulletin)",
     "root_cause": "In AppOpsService.verifyAndGetBypass, a tag not declared in the proxied package was re-checked against the proxy package's manifest (isAttributionInPackage(proxyPkg, attributionTag)), whether or not the proxy was trusted. In the ROOT_UID early return and in a second early return that yields RestrictionBypass.UNRESTRICTED, the tag counted as valid whenever isPackageNullOrSystem(proxyPackageName, proxyUid) held. That check did not verify that the named package belongs to proxyUid. A tag judged valid is used as a key when creating state: in the start path (getOpsLocked with edit=true, including dry run) and in the finish path (getOpLocked, which used edit=true before the fix). The in-code comment describes the concern as abusive apps clogging the appops system with unlimited attribution tags via proxy calls.",
     "fix_analysis": [
      "AppOpsService.java:verifyAndGetBypass (full overload): new boolean isProxyTrusted parameter. The proxy-manifest re-check becomes isProxyTrusted && isAttributionInPackage(proxyPkg, attributionTag). The ROOT_UID early return and the UNRESTRICTED early return set isAttributionTagValid = isProxyTrusted && isPackageNullOrSystem(proxyPackageName, proxyUid). The 3-arg overload passes true with no proxy. The proxy overload gains the isProxyTrusted parameter. checkPackage passes (..., true, true).",
      "AppOpsService.java note and start paths (method names inferred from parameter lists as noteOperationUnchecked and startOperationUnchecked): both compute proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0 and pass it to verifyAndGetBypass. Invalid tags are set to null as before.",
      "AppOpsService.java start dry run: on Android 14, startOperationUnchecked takes a dryRun parameter and calls getOpsLocked with dryRun ? null : attributionTag. On 15/16, a separate method (taking startIfModeDefault; per the commit message, startOperationDryRun) calls getOpsLocked with a null tag. In both cases the Ops object is created if needed, but nothing about the tag is cached.",
      "AppOpsService.java finish path (inferred to be finishOperationUnchecked): verifyAndGetBypass is called with isProxyTrusted=true, because trust is unknown at finish time. getOpLocked is called with edit=false, so finish no longer creates Op entries. The AttributedOp is found through the new getAttributedOpWithClientId(op, clientId, attributionTag, hasProxy) instead of op.mAttributions.get(attributionTag).",
      "AppOpsService.java:getAttributedOpWithClientId (new): with no proxy, returns op.mAttributions.get(tag). With a proxy, it looks in this order: (1) a matching in-progress event for clientId without OP_FLAG_UNTRUSTED_PROXIED under the tag; (2) if the tag is valid in the proxied package per the 3-arg verifyAndGetBypass (a SecurityException counts as invalid), an untrusted event under the tag; (3) an untrusted event under the null tag. Otherwise it returns null.",
      "AppOpsService.java:isPackageNullOrSystem: after the appId shortcut, returns false if mPackageManagerInternal.getPackageUid(packageName, MATCH_ALL, userId of uid) != uid.",
      "AttributedOp.java:hasInProgressEvent(Predicate<InProgressStartOpEvent>) (new): checks mPausedInProgressEvents when paused, otherwise mInProgressEvents, and returns whether any event matches the predicate."
     ],
     "completeness": [
      "The note, start (including dry run) and finish paths in the diff now derive or assume proxy trust and pass it into verifyAndGetBypass. Both early-return shortcuts in verifyAndGetBypass are gated on isProxyTrusted.",
      "The finish path still assumes a trusted proxy for tag validation. However, it no longer creates Op state (edit=false) and falls back to the null-tag AttributedOp for untrusted events.",
      "The fix is validation based and introduces no cap on the number of attribution tags. Tags that are still valid (declared in the proxied package or in a trusted proxy) remain bounded only by manifests.",
      "The material does not show any other path that accepts proxy-supplied tags."
     ],
     "behavior_changes": [
      "For proxy calls carrying OP_FLAG_UNTRUSTED_PROXIED, the tag is now treated as invalid and set to null when it is declared only in the proxy's manifest, or when the call falls into the ROOT_UID or UNRESTRICTED early-return branches.",
      "The finish path no longer creates an Op if none exists. A finish without a matching start logs 'Operation not found' and returns.",
      "The start dry run no longer passes the requested tag to getOpsLocked, so it does not cache state for that tag.",
      "isPackageNullOrSystem returns false when the named package's uid for that user does not match the supplied uid, even if it is a system package.",
      "A proxied finish now matches the AttributedOp by clientId and trust flag, so it can resolve to the null-tag AttributedOp when the start was recorded there."
     ],
     "preconditions": [
      "A caller reaches the note or start path with a proxy and op flags that include OP_FLAG_UNTRUSTED_PROXIED (an untrusted proxy acting for another package), or reaches the finish path with a proxy.",
      "The attribution tag is not declared in the proxied package but is declared in the proxy's manifest; or the call takes the ROOT_UID or UNRESTRICTED early return and the proxy package passes isPackageNullOrSystem."
     ],
     "per_major": {
      "14": "adapted fix: a404da3cbbc6 in android-security-14.0.0_r25 (RESTRICT AUTOMERGE cherry-pick). Dry run is handled through the dryRun parameter (dryRun ? null : attributionTag). Uses the single op.mAttributions map; getAttributedOpWithClientId has no virtualDeviceId. Present at 14.0.0_r29 (8/8 lines).",
      "15": "adapted fix: 22d9cd12ef99 in android-security-15.0.0_r13. Separate start dry-run method patched (getOpsLocked with null tag). Lookups use op.mDeviceAttributedOps keyed by getPersistentId(virtualDeviceId). Present at 15.0.0_r17 (8/8 lines).",
      "16": "adapted fix: a4523e227733 in android-security-16.0.0_r4. Same shape as 15, but keyed by getPersistentDeviceIdForOp(virtualDeviceId, op.op). Present at 16.0.0_r8 (8/8 lines).",
      "17": "not affected (not listed by the bulletin); patched region present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "Single commit, but the finish-path lookup differs by branch. Android 14 uses op.mAttributions directly; 15 and 16 use the virtual-device-aware map (getPersistentId vs getPersistentDeviceIdForOp). The dry run is a parameter on 14 and a separate method on 15/16.",
      "The new AttributedOp.hasInProgressEvent needs InProgressStartOpEvent to expose getClientId() and getFlags(). The Android 14 diff also adds a java.util.function.Consumer import that the new method does not use."
     ],
     "confidence": "medium",
     "unknowns": [
      "How the DoS shows up in practice (memory growth, persisted state size, crash) is not described beyond the in-code comment about clogging AppOps.",
      "The names of the note, start and finish methods in the Android 14 diff are inferred from parameter lists; the hunk headers show only the class.",
      "The condition for the second early-return branch in verifyAndGetBypass (the one returning RestrictionBypass.UNRESTRICTED) is not shown.",
      "Whether the note path creates per-tag state is not shown in the diff."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0020": {
     "cve": "CVE-2026-0020",
     "summary": "Package manifest parsing now trims leading/trailing whitespace from <permission>, <permission-tree> and <permission-group> names in ParsedPermissionUtils, so a declared name is stored in trimmed form rather than as written in the manifest.",
     "component": "platform/frameworks/base, package manager manifest parsing (ParsedPermissionUtils, parsePermission / parsePermissionTree / parsePermissionGroup)",
     "weakness": "Improper input validation / missing normalization of a security-relevant identifier (permission name taken verbatim from the manifest, whitespace included)",
     "root_cause": "ParsedPermissionUtils.parsePermission, parsePermissionTree and parsePermissionGroup stored the android:name string from the manifest as-is, so a name with leading or trailing whitespace stayed on the ParsedPermissionImpl / ParsedPermissionGroupImpl object unchanged. The diff does not show which consumer mishandled such names. It only shows that the parser never normalized them.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java:parsePermission (Android 14 path): after the knownCerts / restricted-flag handling and before PermissionInfo.fixProtectionLevel is applied, adds permission.setName(permission.getName().trim()). The protection-flag validation and parseAllMetaData later in the same function therefore see the trimmed name.",
      "ParsedPermissionUtils.java:parsePermissionTree: adds permission.setName(permission.getName().trim()). On Android 14 the trim comes after a block ending in '+ permission.getName());' (the 14 diff does not show that block's content) and immediately before setProtectionLevel(PROTECTION_NORMAL).setTree(true). On Android 15/16 it comes right after sa.recycle() and before the indexOf('.') name checks, so those checks run on the trimmed name.",
      "ParsedPermissionUtils.java:parsePermissionGroup: appends .setName(permissionGroup.getName().trim()) to the builder chain inside the try block (after setFlags), before the TypedArray is recycled.",
      "No new error is introduced: a padded name is trimmed silently, not rejected."
     ],
     "completeness": [
      "Covers the three declared-name paths in ParsedPermissionUtils: <permission>, <permission-tree> and <permission-group>.",
      "Only String.trim() is applied: leading/trailing whitespace is removed, while interior whitespace and other unusual characters are neither touched nor rejected.",
      "Placement of the permission-tree trim differs between majors. On 15/16 it comes before the indexOf('.') checks. On 14 it comes after the preceding check block. Because the 15/16 check tests 'index > 0', leading whitespace can change its result, so on 14 that check may run on the untrimmed name.",
      "The diff does not touch other manifest strings, such as the android:permissionGroup attribute on <permission>, <uses-permission> names or the knownCerts string. Whether those need the same treatment is not shown."
     ],
     "behavior_changes": [
      "Permission, permission-tree and permission-group names declared with leading/trailing whitespace are now stored in trimmed form.",
      "No new parse error or rejection: the names are trimmed silently."
     ],
     "preconditions": [
      "The package manager parses an APK whose AndroidManifest.xml declares a <permission>, <permission-tree> or <permission-group> whose android:name has leading or trailing whitespace."
     ],
     "per_major": {
      "14": "Adapted fix (d7a73fb8bfc2, android-security-14.0.0_r25; RESTRICT AUTOMERGE, Cherrypick-From 2c0bf3261ca5, Change-Id I673ad83d05c9825177967e4f0a960e8841610b71). Same three trim() calls, but in services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java, and the permission-tree trim is placed after the preceding check block instead of before the name checks. At android-security-14.0.0_r29 the resolver reports 7/8 lines of the patched region present (the 14 patch has 4 insertions versus 6 in 15/16). The trim in parsePermission is visible at line 147 in the r29 code.",
      "15": "Same fix (4fa32d4fef90, android-security-15.0.0_r13) in core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java. The permission-tree trim is placed before the indexOf('.') checks. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Same fix (e770e9f02341, android-security-16.0.0_r4), same content as the 15 patch at the core/java/com/android/internal/pm path with shifted line numbers. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). The resolver checked only the services/core/java path, which is absent at android-security-17.0.0_r1. On 15/16 the file lives under core/java/com/android/internal/pm/pkg/component/, so that absence alone does not show the patch state on 17."
     },
     "backport_notes": [
      "Single-commit fix. On trees based on Android 14 or earlier, apply it to services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java. Consider the 15/16 placement of the permission-tree trim (before the name checks) so those checks also run on the trimmed name.",
      "In parsePermissionGroup, keep setName(...trim()) inside the try block that holds the TypedArray, as in the fix."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which consumer of the permission or group name mishandled whitespace-padded names, and so the exact EoP mechanism.",
      "Whether other manifest-derived strings (e.g. the android:permissionGroup attribute on <permission>, <uses-permission> names) are trimmed elsewhere or need the same treatment.",
      "What the Android 14 check block before the permission-tree trim contains, why the 14 cherry-pick places the trim differently from 15/16, and what explains the 7/8 line match at android-security-14.0.0_r29.",
      "The patch state of ParsedPermissionUtils on Android 17 (only the 14 path was checked).",
      "Downstream effects of trimming (e.g. on duplicate or ownership handling), which the material does not show."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0021": {
     "cve": "CVE-2026-0021",
     "summary": "In packages/apps/Settings, AppInfoBase's caller permission check now, on devices with activity embedding (multi-pane) enabled, verifies that the SettingsActivity's initial calling package holds INTERACT_ACROSS_USERS_FULL instead of relying on getLaunchedFromUid, and the Settings$AppUsageAccessSettingsActivity intent-filter gets android:priority=\"1\".",
     "component": "platform/packages/apps/Settings, applications/AppInfoBase (app info pages) and AndroidManifest.xml",
     "weakness": "Missing/insufficient caller permission check on multi-pane (activity embedding) devices",
     "root_cause": "AppInfoBase has a method (its name is not shown in the hunk header) that, after confirming the host activity is a SettingsActivity, decides whether the caller is allowed through by using ActivityManager.getService().getLaunchedFromUid(activity.getActivityToken()). Per the commit title ('Check permission of the calling package in multi-pane devices'), this check was not adequate on devices where ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) is true. The material does not show exactly why the launched-from UID check fails in that mode. Separately, the Settings$AppUsageAccessSettingsActivity intent-filter for android.settings.USAGE_ACCESS_SETTINGS with data scheme \"package\" had no priority, while its sibling Settings$UsageAccessSettingsActivity already declares priority 1.",
     "fix_analysis": [
      "src/com/android/settings/applications/AppInfoBase.java (caller permission method, after the SettingsActivity instanceof check): adds a branch for ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity). It obtains ((SettingsActivity) activity).getInitialCallingPackage(); if that is empty, it logs and returns false. It then requires mPm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) == PERMISSION_GRANTED; otherwise it logs and returns false. On success it returns true. The pre-existing getLaunchedFromUid path is only reached when embedding is not enabled.",
      "src/com/android/settings/applications/AppInfoBase.java: adds import com.android.settings.activityembedding.ActivityEmbeddingUtils.",
      "AndroidManifest.xml: the Settings$AppUsageAccessSettingsActivity intent-filter (USAGE_ACCESS_SETTINGS, scheme package) changes from <intent-filter> to <intent-filter android:priority=\"1\">, matching the sibling Settings$UsageAccessSettingsActivity. The commit message does not explain this change. A higher intent-filter priority makes an activity rank ahead of other matching activities during resolution (general platform knowledge)."
     ],
     "completeness": [
      "The new check applies only when ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) is true. The non-embedding path still uses getLaunchedFromUid and is unchanged.",
      "On the embedding path the check is keyed on a package name from SettingsActivity.getInitialCallingPackage() and uses PackageManager.checkPermission(permission, packageName), not a UID. The material does not show how SettingsActivity derives the initial calling package.",
      "Only the AppInfoBase method is changed. The material does not show which fragments rely on it, or whether other Settings fragments perform a similar getLaunchedFromUid-based check.",
      "The manifest priority change is applied only to Settings$AppUsageAccessSettingsActivity. The surrounding context shows that the sibling Settings$UsageAccessSettingsActivity already had priority 1."
     ],
     "behavior_changes": [
      "On multi-pane devices, AppInfoBase now denies (returns false, with a Log.w) when the initial calling package cannot be determined, or when that package lacks INTERACT_ACROSS_USERS_FULL.",
      "On multi-pane devices the getLaunchedFromUid-based check is no longer consulted. The result is decided only by the calling-package permission check.",
      "The USAGE_ACCESS_SETTINGS/package-scheme filter of Settings$AppUsageAccessSettingsActivity now has priority 1, which can change which activity is chosen when several activities match that intent."
     ],
     "preconditions": [
      "A device where ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) returns true (multi-pane / activity embedding Settings layout).",
      "A SettingsActivity hosting a fragment that reaches the patched AppInfoBase check, launched by another app. Exactly which exported entry points reach it is not shown.",
      "The calling package does not hold INTERACT_ACROSS_USERS_FULL (the fixed code now rejects such callers on the embedding path)."
     ],
     "per_major": {
      "14": "same fix: commit 19171d5c31ad in android-security-14.0.0_r25 (cherry-pick of bd4d57ade079, Change-Id I91dafa77d07970fdf2628b4d9e89ca1c4b74194c); 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 298fd46325a8 in android-security-15.0.0_r13; same diff content (different line offsets); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 48af8a13dd12 in android-security-16.0.0_r4; same diff content; 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected: not listed by the bulletin; 8/8 lines of the patched region already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit, applied with the same content on 14, 15 and 16. Before applying it to a customized Settings fork, verify that the target tree has ActivityEmbeddingUtils.isEmbeddingActivityEnabled(Activity), SettingsActivity.getInitialCallingPackage(), the mPm field in AppInfoBase, and the TextUtils/Log/Manifest imports."
     ],
     "confidence": "medium",
     "unknowns": [
      "The name and full body of the patched AppInfoBase method, and how callers use its result.",
      "Why the getLaunchedFromUid-based check is insufficient on multi-pane devices.",
      "How SettingsActivity.getInitialCallingPackage() is populated, and whether it is trustworthy in all launch paths.",
      "Which fragments (for example, whether UsageAccessDetails extends AppInfoBase) and which exported activities reach the patched check.",
      "The exact reason for the manifest priority change on Settings$AppUsageAccessSettingsActivity (the commit message does not state it).",
      "Which user mPm.checkPermission evaluates for the calling package name."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0023": {
     "cve": "CVE-2026-0023",
     "summary": "In PackageInstallerService.java, the session creation path now always clears PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE from the caller-supplied params.installFlags, then adds it back only when DevicePolicyManagerInternal.isUserOrganizationManaged(userId) is true.",
     "component": "platform/frameworks/base, PackageManager / PackageInstallerService (install session creation)",
     "weakness": "Missing sanitization of a caller-controlled install flag: a flag meant to be set only by the system, in SessionParams.installFlags, was accepted from caller input.",
     "root_cause": "When creating an install session, PackageInstallerService took params.installFlags as supplied by the caller. It only ORed in INSTALL_FROM_MANAGED_USER_OR_PROFILE when dpmi.isUserOrganizationManaged(userId) was true and never cleared the bit otherwise. A caller that pre-set the flag in its SessionParams therefore had it carried into the PackageInstallerSession even when the install was not from a managed user or profile. The commit message says this could lead to unexpected behaviour.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java, session creation path (the block after `final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);`): adds `params.installFlags &= ~PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE;`, so the flag is always removed from caller input first.",
      "The existing conditional `if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) { params.installFlags |= PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE; }` is unchanged. At this point the flag is now set only from the device policy state of the target userId, not by the caller.",
      "A regression test is referenced: PackageManagerShellCommandInstallTest#testSessionCreationWithManagedUserOrProfileFlag_notFromManagedProfile. The test file is not included in the diff."
     ],
     "completeness": [
      "The sanitized params object is the one passed to the new PackageInstallerSession constructor in the same method.",
      "Only INSTALL_FROM_MANAGED_USER_OR_PROFILE is addressed. The same block already strips INSTALL_REQUEST_UPDATE_OWNERSHIP when ENFORCE_UPDATE_OWNERSHIP is denied (or for APEX), and resets forceQueryableOverride for callers that are not root or shell. No other caller-controlled flag is newly sanitized by this change.",
      "The diff is a two-line change (one comment, one statement). No other files or paths, such as the code that consumes the flag, are modified. The material does not show whether the flag can reach a session by any route other than this creation path."
     ],
     "behavior_changes": [
      "If a caller sets INSTALL_FROM_MANAGED_USER_OR_PROFILE in SessionParams.installFlags, the flag is now silently dropped. No error path is added.",
      "The flag is present on a session only when DevicePolicyManagerInternal is available and reports the target userId as organization managed."
     ],
     "preconditions": [
      "The caller can create a PackageInstaller session (reaching the session creation path in PackageInstallerService) and supplies SessionParams with INSTALL_FROM_MANAGED_USER_OR_PROFILE set in installFlags.",
      "The target userId is not organization managed, or DevicePolicyManagerInternal is unavailable. Otherwise the system would set the flag anyway."
     ],
     "per_major": {
      "14": "same fix: commit a9550d0a75d0 in android-security-14.0.0_r25 (Change-Id I21bbbf628e97244d469eb23ce0558dbf560b7618); 3/3 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 221ac26392cd in android-security-15.0.0_r13, identical two-line hunk at a different offset (hunk at line 1016); 3/3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 09055276288a in android-security-16.0.0_r4, identical two-line hunk (hunk at line 1027); 3/3 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (3/3 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained two-line hunk anchored on the `final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);` line in PackageInstallerService.java. Across 14, 15 and 16 the hunk is identical and only its offset differs."
     ],
     "confidence": "high",
     "unknowns": [
      "The name of the enclosing method is not shown in the hunk header or the provided context.",
      "The material does not show which code that consumes INSTALL_FROM_MANAGED_USER_OR_PROFILE produces the 'unexpected behaviour' named in the commit message, or what concrete privilege results. The bulletin only rates the issue High EoP.",
      "The material does not show whether the flag can enter a session through any path other than session creation.",
      "The contents of the referenced regression test (PackageManagerShellCommandInstallTest) are not included."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0024": {
     "cve": "CVE-2026-0024",
     "summary": "MediaProvider's redaction decision for files opened via ContentResolver now also considers the UID passed in MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, so media opened by a picker on behalf of an app lacking ACCESS_MEDIA_LOCATION gets location metadata redacted.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (ContentResolver open path / location redaction)",
     "weakness": "missing permission check (authorization decided on the direct caller only, ignoring the on-behalf-of UID)",
     "root_cause": "isRedactionNeededForOpenViaContentResolver(Uri redactedUri, String ownerPackageName, File file) never looked at the opts Bundle. In the code shown, its decision rests on the redacted-URI flag and on whether the direct caller (getCallingPackageOrSelf()) owns the file. The remainder of the method is not shown in the material. When a picker (SAF, per the commit message) opens a media file on behalf of another app and supplies that app's UID in MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, that UID's permissions were not considered. Per the commit message, this let an app bypass ACCESS_MEDIA_LOCATION and get unredacted location data.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java: the open helper that computes redactionNeeded (code context around line 8910 in android-security-14.0.0_r29) now passes its opts Bundle to isRedactionNeededForOpenViaContentResolver(redactedUri, ownerPackageName, file, opts).",
      "src/com/android/providers/media/MediaProvider.java:isRedactionNeededForOpenViaContentResolver gains a Bundle opts parameter. The new check comes after the existing 'redactedUri != null -> true' check and before the owner check. If opts is non-null and opts.getInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID) > 0, it builds LocalCallingIdentity.fromExternal(getContext(), mUserCache, mediaCapabilitiesUid, null, null) and returns true (redact) if that identity hasPermission(PERMISSION_IS_REDACTION_NEEDED).",
      "If the capabilities UID does not require redaction, the method falls through to the pre-existing logic, starting with the owner check. In effect, the on-behalf-of UID can now force redaction but cannot remove it."
     ],
     "completeness": [
      "Covers only opens that go through isRedactionNeededForOpenViaContentResolver with EXTRA_MEDIA_CAPABILITIES_UID present in opts. The on-behalf-of UID is honored only when the intermediary actually supplies it (the UID must be > 0).",
      "The new check sits after the redactedUri shortcut and before the owner check, so a file owned by the direct caller is now redacted when the capabilities UID requires it.",
      "The existing requireOriginal check downstream (MediaStore.getRequireOriginal(uri) together with redactionNeeded) now also fires for the capabilities-UID case and throws UnsupportedOperationException.",
      "Other redaction decision paths (e.g. FUSE-based opens) are not touched by this diff. The material does not show whether they already handle an on-behalf-of UID."
     ],
     "behavior_changes": [
      "Opens that carry MediaStore.EXTRA_MEDIA_CAPABILITIES_UID for a UID where PERMISSION_IS_REDACTION_NEEDED holds now return a redacted file, even when the direct caller owns the file.",
      "For such opens with requireOriginal set on the URI, the call now fails with UnsupportedOperationException ('Caller must hold ACCESS_MEDIA_LOCATION permission to access original').",
      "An extra LocalCallingIdentity.fromExternal lookup is performed per open when the extra is present."
     ],
     "preconditions": [
      "A media file whose contents include redactable location metadata, opened through MediaProvider via ContentResolver (not a redacted URI).",
      "An intermediary (a picker such as SAF, per the commit message) that opens the file and passes the target app's UID in MediaStore.EXTRA_MEDIA_CAPABILITIES_UID in the opts Bundle.",
      "The target UID does not hold ACCESS_MEDIA_LOCATION, while the direct caller (the picker) would otherwise receive unredacted data."
     ],
     "per_major": {
      "14": "same fix: commit 50387ce9d85a in android-security-14.0.0_r25, identical logic (surrounding code uses RedactionInfo-based ranges). At android-security-14.0.0_r29, 6/8 lines of the patched region are present, and the code context shows opts passed at the call site.",
      "15": "same fix: commit 77f9bd12a6ed in android-security-15.0.0_r13, identical hunks at different line offsets (surrounding code uses RedactionUtils.getRedactionRanges). 6/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: commit 69a25763cdb4 in android-security-16.0.0_r4, identical hunks. 6/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin. Only 4/8 lines of the patched region are present at android-security-17.0.0_r1, so the code there differs, and the material does not show how it is handled."
     },
     "backport_notes": [
      "Single self-contained commit. It needs LocalCallingIdentity.fromExternal (called as fromExternal(getContext(), mUserCache, uid, null, null)), the mUserCache field, the PERMISSION_IS_REDACTION_NEEDED constant and MediaStore.EXTRA_MEDIA_CAPABILITIES_UID to exist. The opts Bundle must also be in scope in the open helper that calls isRedactionNeededForOpenViaContentResolver. MediaProvider is a mainline module (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the enclosing open helper is not shown in the material (only the class-level hunk header).",
      "The full pre-fix logic of isRedactionNeededForOpenViaContentResolver after the owner check is not shown.",
      "Whether other open paths (e.g. FUSE) already respect the capabilities UID is not shown.",
      "Why the Android 17 region only partially matches, and how 17 handles the on-behalf-of UID, is not shown.",
      "Test coverage: the commit lists 'Test: manual' only."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0025": {
     "cve": "CVE-2026-0025",
     "summary": "Notification.java now reads EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES with getParcelableArrayFromBundle(..., Bundle.class) instead of extras.getParcelableArray(..., Parcelable.class), so the element type is restricted to Bundle and arbitrary Parcelable element types are no longer accepted.",
     "component": "platform/frameworks/base, core/java/android/app/Notification.java (MessagingStyle extras parsing, Notification.visitUris URI checking)",
     "weakness": "Insufficient type validation of untrusted parcelled data (incorrect type conversion), allowing the URI permission visitor to be bypassed",
     "root_cause": "Notification.visitUris(), Notification.hasImage() and the MessagingStyle code that restores messages from extras read EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES with extras.getParcelableArray(key, Parcelable.class). That call returns a Parcelable[] whose elements can be any Parcelable type. These arrays were then passed to MessagingStyle.Message.getMessagesFromBundleArray(), which by its name expects Bundle elements (its body is not shown). A caller-controlled array with a non-Bundle element was not rejected when it was read. The test inserts a crafted Parcelable, MyParceledListSlice, into EXTRA_MESSAGES. Per the test comment (\"to avoid visitUri check uris in EXTRA_MESSAGES value\"), this could let a notification carrying content URIs in its messages get past the visitUris-based URI permission check during enqueue, without checkGrantUriPermission being applied to those URIs. The bulletin type is information disclosure.",
     "fix_analysis": [
      "core/java/android/app/Notification.java:visitUris(): EXTRA_MESSAGES is now read as `Bundle[] messages = getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class)`. EXTRA_HISTORIC_MESSAGES is read with the same helper and Bundle.class. Its local variable keeps the Parcelable[] type. The new test expects a non-Bundle element to produce java.lang.ArrayStoreException from enqueueNotificationWithTag, with no URI grant check for the embedded URI.",
      "core/java/android/app/Notification.java:hasImage(): EXTRA_MESSAGES is read with getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class) into a Bundle[] before getMessagesFromBundleArray() is called.",
      "core/java/android/app/Notification.java, MessagingStyle code that restores from extras (enclosing method name not shown in the hunk): EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES are both read with getParcelableArrayFromBundle(..., Bundle.class) into Bundle[] before Message.getMessagesFromBundleArray() is called.",
      "services/tests/uiservicestests/.../NotificationManagerServiceTest.java: adds testNoUriGrantsForBadMessagesList. The test builds a MessagingStyle notification that references content://com.android.contacts/display_photo/1, appends a crafted non-Bundle Parcelable to EXTRA_MESSAGES, and enqueues the notification through the binder service. It expects an ArrayStoreException and no checkGrantUriPermission call for that URI."
     ],
     "completeness": [
      "The fix covers the EXTRA_MESSAGES/EXTRA_HISTORIC_MESSAGES reads in Notification.java that appear in the diff: visitUris (both keys), hasImage (EXTRA_MESSAGES) and the MessagingStyle restore code (both keys).",
      "MessagingStyle.Message.getMessagesFromBundleArray() itself is not changed, so the fix depends on callers passing a type-checked array. A caller of getMessagesFromBundleArray that still passes a Parcelable[] read with Parcelable.class would not be covered. The material shows no such caller.",
      "Readers of these extras outside Notification.java (for example SystemUI or support-library code) are not part of this change and are not shown in the material.",
      "In all three diffs, the new test method testNoUriGrantsForBadMessagesList has no @Test annotation, while the neighbouring tests do. The material does not show whether the test actually runs."
     ],
     "behavior_changes": [
      "When an EXTRA_MESSAGES or EXTRA_HISTORIC_MESSAGES array contains a non-Bundle Parcelable, enqueueNotificationWithTag now fails for that notification (the test expects ArrayStoreException) instead of accepting it.",
      "Well-formed MessagingStyle notifications built with Message.toBundle() arrays are expected to be unaffected."
     ],
     "preconditions": [
      "An app posts a notification through INotificationManager.enqueueNotificationWithTag whose extras contain EXTRA_MESSAGES or EXTRA_HISTORIC_MESSAGES as a hand-built Parcelable[] with a non-Bundle element. The message content references content URIs the app cannot access (the test uses a contacts display_photo URI).",
      "The check that is bypassed is the URI permission visitation done through Notification.visitUris during enqueue (checkGrantUriPermission on mUgmInternal in the test)."
     ],
     "per_major": {
      "14": "Same fix: commit 5e1623a880d2 in android-security-14.0.0_r25 changes visitUris, hasImage and the MessagingStyle restore code. At android-security-14.0.0_r29, 6 of 8 patched-region lines are present, and the visible visitUris code is the fixed version.",
      "15": "Same fix: commit 5ffc92d00244 in android-security-15.0.0_r13 has the same Notification.java hunks at shifted line numbers. Its test diff also adds static imports (EXTRA_MESSAGES, EXTRA_MESSAGING_PERSON, FLAG_UPDATE_CURRENT) and updates further test references. At android-security-15.0.0_r17, 6 of 8 patched-region lines are present.",
      "16": "Same fix: commit 014dea279c49 in android-security-16.0.0_r4 has the same Notification.java hunks. At android-security-16.0.0_r8, 6 of 8 patched-region lines are present.",
      "17": "Not affected per the bulletin (Android 17 is not listed). At android-security-17.0.0_r1, 6 of 8 patched-region lines are present."
     },
     "backport_notes": [
      "This is a single commit with the same Notification.java hunks on 14, 15 and 16, at different line offsets. It depends on the existing helper Notification.getParcelableArrayFromBundle(Bundle, String, Class), which all three branches use without changing it.",
      "Update every site: visitUris (two reads), hasImage, and the MessagingStyle restore code (two reads). Consider adding @Test to testNoUriGrantsForBadMessagesList so that it runs."
     ],
     "confidence": "medium",
     "unknowns": [
      "How MessagingStyle.Message.getMessagesFromBundleArray() behaved on a non-Bundle element before the fix (its body is not shown), and so exactly how the URI check was skipped.",
      "The body and visibility of getParcelableArrayFromBundle.",
      "The name of the enclosing MessagingStyle method in the third hunk.",
      "Where the leaked URI content is finally rendered or granted (for example by SystemUI).",
      "Which 2 of the 8 patched-region lines differ at the latest security tags.",
      "Whether readers of EXTRA_MESSAGES outside Notification.java exist and apply equivalent type checks."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0026": {
     "cve": "CVE-2026-0026",
     "summary": "Adds an early return in PermissionManagerServiceImpl.removePermission() so that a non-dynamic permission is no longer removed from the permission registry after the \"not allowed to modify non-dynamic permission\" condition is detected.",
     "component": "platform/frameworks/base, services/core PermissionManagerService (permission registry)",
     "weakness": "Missing early return after a failed authorization check: the non-dynamic check only logged and did not stop the operation (improper enforcement of a check)",
     "root_cause": "In services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java, removePermission() looks up the Permission and, if !bp.isDynamic(), only emits Slog.wtf(\"Not allowed to modify non-dynamic permission ...\") (a TODO says to switch this back to SecurityException). Control then fell through to mRegistry.removePermission(permName) and mPackageManagerInt.writeSettings(false). So a caller that passed enforcePermissionTree() could remove a non-dynamic permission from the registry, although the commit states that only dynamic permissions may be removed.",
     "fix_analysis": [
      "PermissionManagerServiceImpl.java:removePermission(): a single `return;` is added inside the `if (!bp.isDynamic())` block, right after the Slog.wtf call. For non-dynamic permissions the method now returns from within the synchronized(mLock) block, before mRegistry.removePermission(permName) and before mPackageManagerInt.writeSettings(false).",
      "The Slog.wtf logging is kept and the TODO to restore a SecurityException is left in place. The caller gets no error, but the removal no longer happens."
     ],
     "completeness": [
      "Covers the only fall-through in removePermission(): the bp == null branch already returned, and the new return closes the non-dynamic branch, so mRegistry.removePermission() is now reached only for dynamic permissions.",
      "The sibling addPermission() path (lines 665-670 in the context) already throws SecurityException when modifying an existing non-dynamic permission and is not changed by this fix.",
      "The fix does not restore the SecurityException noted in the TODO; a caller attempting to remove a non-dynamic permission receives no error, only a Slog.wtf is logged.",
      "The instant-app check and mRegistry.enforcePermissionTree() gating are unchanged."
     ],
     "behavior_changes": [
      "removePermission() on an existing non-dynamic permission now returns without modifying the registry and without calling writeSettings(false); previously the permission was removed and settings were written.",
      "No new exception or error code is introduced; the only observable signal is the pre-existing Slog.wtf log line."
     ],
     "preconditions": [
      "Caller reaches PermissionManagerServiceImpl.removePermission() (which takes the caller identity from Binder.getCallingUid()) and is not an instant app.",
      "mRegistry.enforcePermissionTree(permName, callingUid) must pass for the given permission name and calling UID.",
      "The named permission must exist in mRegistry and be non-dynamic (bp.isDynamic() == false)."
     ],
     "per_major": {
      "14": "same fix: commit 6c22d6c1e696 in android-security-14.0.0_r9 (cherry-pick of 528a87e90ff9, Change-Id Ie2f43663bc71a06ffadb868d2d0eea5ee78f76e5); at android-security-14.0.0_r29 all 3/3 lines of the patched region are present.",
      "15": "not affected per the bulletin; at android-security-15.0.0_r17 the patched region is present (3/3 lines).",
      "16": "not affected per the bulletin (the bulletin does list 16-qpr2-next); at android-security-16.0.0_r8 the patched region is present (3/3 lines).",
      "17": "not affected per the bulletin; the patched file services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java is absent at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-line, self-contained change: insert `return;` after the Slog.wtf call inside the `if (!bp.isDynamic())` block of removePermission(), before mRegistry.removePermission(permName).",
      "No fork comparison section was provided in the material, so no per-file fork verdict can be reported."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the non-dynamic check was previously downgraded from SecurityException to Slog.wtf (the TODO only hints at it).",
      "Where the equivalent logic lives in Android 17, since the patched file is absent at android-security-17.0.0_r1.",
      "Why the bulletin lists 16-qpr2-next as affected while the patched region is already present at android-security-16.0.0_r8.",
      "The concrete impact on the device of removing a non-dynamic permission from the registry (bulletin only rates it High EoP)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0034": {
     "cve": "CVE-2026-0034",
     "summary": "Two frameworks/base commits bound the per-user ManagedServices approval state: uninstalling a package now also removes its entries from mUserSetServices, and setPackageOrComponentEnabled refuses to add more than MAX_SERVICE_ENTRIES (100) entries per user to mApproved / mUserSetServices, returning a boolean so NotificationManagerService callers skip follow-up work when nothing changed.",
     "component": "platform/frameworks/base, services/core notification (ManagedServices, NotificationManagerService: listeners, assistants, condition providers)",
     "weakness": "Missing resource limit: unbounded growth of per-user approved and user-set service lists (no cap, and user-set list never cleaned on package removal)",
     "root_cause": "In services/core/java/com/android/server/notification/ManagedServices.java, setPackageOrComponentEnabled unconditionally added pkgOrComponent to the per-user approved ArraySet (mApproved) and to mUserSetServices, with no upper bound on how many packages/components could be approved (and therefore bound) per user. In addition, the uninstall cleanup path reached from onPackagesChanged only trimmed the approved lists; entries for the removed package stayed in mUserSetServices, so that list could only grow. Every call also triggered rebindServices and the callers' broadcasts regardless of whether anything changed.",
     "fix_analysis": [
      "Commit 1 (00fd75533614, Ibd585dd1af): ManagedServices.java, in the uninstall cleanup method invoked from onPackagesChanged (the method taking uninstalledUserId and pkg that returns `removed`, around line 1265): after trimming the approved lists, it fetches mUserSetServices.get(uninstalledUserId) and iterates it backwards, removing every entry whose getPackageName(pkgOrComponent) equals the uninstalled pkg. New test testPackageUninstall_componentNoLongerUserSetList verifies isPackageOrComponentUserSet becomes false after onPackagesChanged(true, pkg, uid) for both APPROVAL_BY_COMPONENT and APPROVAL_BY_PACKAGE.",
      "Commit 2 (cb1ce8020b43, Iddd8044997): ManagedServices.java adds `private static final int MAX_SERVICE_ENTRIES = 100`. Both overloads of setPackageOrComponentEnabled now return boolean. In the 5-arg version: when enabled, approved.add(approvedItem) happens only if approved.size() < MAX_SERVICE_ENTRIES (sets changed=true), otherwise a Slog.w 'Failed to allow ... because there are too many already' and no change; disabling always removes and sets changed=true. The mUserSetServices update is now inside `if (changed)`: with userSet=true the entry is added only if userSetServices.size() < MAX_SERVICE_ENTRIES, with userSet=false it is removed. rebindServices(false, userId) runs only if changed; the method returns changed.",
      "Commit 2, NotificationManagerService.java setNotificationPolicyAccessGrantedForUser: captures the boolean from mConditionProviders.setPackageOrComponentEnabled(pkg, userId, true, granted) and returns early (no ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED broadcast, no handleSavePolicyFile) when false.",
      "Commit 2, NotificationManagerService.java setNotificationListenerAccessGrantedForUser internals: reorders the calls so mListeners.setPackageOrComponentEnabled(listener.flattenToString(), userId, true, granted, userSet) runs first; if it returns false the method returns before calling mConditionProviders.setPackageOrComponentEnabled(..., false, granted, userSet) and before the broadcast.",
      "Commit 2, the assistant-services setPackageOrComponentEnabled override in NotificationManagerService.java (the one ensuring only one component is enabled, calling setNotificationAssistantAccessGrantedForUserInternal): now returns boolean; the 'already the current component' early exit returns false; otherwise returns super's result. The listener-services override (which sends ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED): returns false without sending the broadcast when super returned false, else sends it and returns true.",
      "Tests: ManagedServicesTest adds setPackageOrComponentEnabled_tooManyPackages_stopsAdding (101st add rejected, disabling frees a slot) and setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet (user-set list capped; uninstall via onPackagesChanged frees space). NotificationManagerServiceTest stubs all mock setPackageOrComponentEnabled variants to return true and adds testSetListenerAccessForUser_tooManyListeners_skipsFollowups."
     ],
     "completeness": [
      "The cap is enforced only in ManagedServices.setPackageOrComponentEnabled; the material does not show whether other writers of mApproved / mUserSetServices (XML read/migration paths such as migrateToXml used by the tests) apply any bound.",
      "The user-set cap is independent of the approved cap: when mUserSetServices for a user already has 100 entries, the approval itself still succeeds (changed=true) but the entry is not recorded as user-set (shown by the 'toomany' assertions in setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet).",
      "Disabling always sets changed=true (approved.remove is not checked for actual presence), so a disable for an entry that was never approved still triggers rebindServices and the callers' broadcasts.",
      "Cleanup of mUserSetServices on uninstall covers only the uninstalled user id passed to the cleanup method; it keys on package name via getPackageName, so it works for both package and component entries.",
      "The commit message lists a flag (com.android.server.notification.limit_managed_services_count) but the Android 14 diff contains no flag check; the limit is unconditional in this backport.",
      "The 14/15/16 per-major facts report 7/8 lines of the patched region present at the latest security tags, so a small drift versus the original hunk exists there; the material does not show which line differs."
     ],
     "behavior_changes": [
      "Attempting to approve a 101st package/component per user for a given ManagedServices instance is silently rejected (Slog.w only, no exception, no rebind, no broadcast, no policy file save on the condition-provider path).",
      "setPackageOrComponentEnabled (both overloads, and the assistant/listener overrides in NotificationManagerService.java) now return boolean instead of void.",
      "rebindServices is no longer called when the enable request was rejected; ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED and ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED are not broadcast in that case.",
      "In setNotificationListenerAccessGrantedForUser the listener list is updated before the condition-provider secondary list, and the condition-provider update is skipped when the listener update was rejected.",
      "The assistant override returns false (previously returned void) when the requested component is already the enabled assistant.",
      "Uninstalling a package now removes its entries from mUserSetServices, so isPackageOrComponentUserSet returns false afterwards.",
      "At most 100 entries per user are kept in mUserSetServices; additional user-set markers are dropped."
     ],
     "preconditions": [
      "Ability to reach ManagedServices.setPackageOrComponentEnabled with enabled=true repeatedly for distinct packages/components: via NotificationManagerService.setNotificationPolicyAccessGrantedForUser (guarded by checkCallerIsSystemOrShell and mAllowedManagedServicePackages.test, per the context shown) or setNotificationListenerAccessGrantedForUser (guarded by mAllowedManagedServicePackages.test; its caller check is not shown in the material).",
      "For the user-set list growth: packages that were approved as user-set and later uninstalled, since before commit 1 those entries were never removed from mUserSetServices."
     ],
     "per_major": {
      "14": "same fix: both commits (00fd75533614 then cb1ce8020b43) landed in android-security-14.0.0_r25; the listener override in 14 has no redactSensitiveNotificationsFromUntrustedListeners block, so the hunk is the simpler 14 variant. 7/8 patched lines present at r29.",
      "15": "adapted fix: a68f6e5719ce in android-security-15.0.0_r13 is the same logic; the listener setPackageOrComponentEnabled hunk inserts the early return before the redactSensitiveNotificationsFromUntrustedListeners block, and line offsets differ. 7/8 patched lines present at r17.",
      "16": "adapted fix: e363f8210456 in android-security-16.0.0_r4, same content as 15 with different line offsets. 7/8 patched lines present at r8.",
      "17": "not affected per the bulletin (not listed); at android-security-17.0.0_r1 the patched region is present (2/2 lines)."
     },
     "backport_notes": [
      "Apply the user-set cleanup commit (Ibd585dd1af) before the limit commit (Iddd8044997): the second commit's ManagedServices.java diff is based on the first's output (index 2d0ea16c0a45), and its test setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet relies on onPackagesChanged freeing mUserSetServices slots.",
      "The void-to-boolean signature change must be propagated to every subclass override of setPackageOrComponentEnabled (the assistant and listener overrides in NotificationManagerService.java) and to Mockito stubs in NotificationManagerServiceTest; an unstubbed mock returns false and would make the NMS callers return early, breaking existing tests."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the ManagedServices cleanup method modified by commit 1 (the hunk header shows only the class; the method takes uninstalledUserId/pkg and returns `removed`).",
      "Whether mApproved / mUserSetServices are persisted and how; the material shows only XML attribute constants.",
      "Whether XML load/migration paths or other writers of mApproved can exceed MAX_SERVICE_ENTRIES; not shown in the material.",
      "Which single line of the patched region differs at the latest 14/15/16 security tags (7/8 lines present).",
      "The concrete privilege impact the bulletin's EoP rating refers to; the material only shows the unbounded list growth and missing cleanup."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0035": {
     "cve": "CVE-2026-0035",
     "summary": "MediaProvider now rejects MediaStore CREATE_WRITE/DELETE/..._REQUEST calls with an IllegalArgumentException when any requested URI does not resolve to exactly one existing row at request time.",
     "component": "platform/packages/providers/MediaProvider, MediaStore createRequest (PermissionActivity request creation)",
     "weakness": "Missing input validation: request URIs were not checked for existence before a permission request was created for them",
     "root_cause": "In src/com/android/providers/media/MediaProvider.java, the code path that builds the PermissionActivity Intent for the MediaStore CREATE_*_REQUEST methods (createWriteRequest, createDeleteRequest, etc.) took the caller-supplied list of URIs and built the request without checking that each URI referred to an existing item. A caller could therefore obtain a request for a media URI that did not exist in the Files table at the time of the call. Before the fix, testCreateRequest asserted success for a non-existent id (42), which confirms the old behavior.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java (the createRequest handler; the hunk header does not show the enclosing method name): after the existing per-URI validation block and before the PermissionActivity Intent is created, a new loop goes over all `uris`. For each URI it calls queryForSingleItem(uri, new String[]{FileColumns._ID}, null, null, null) in a try-with-resources. queryForSingleItem throws FileNotFoundException if it finds zero rows or multiple rows, or if there is a read error. The handler converts that exception to IllegalArgumentException(\"Invalid Uri: \" + uri, e), which aborts the request.",
      "The loop runs under clearLocalCallingIdentity()/restoreLocalCallingIdentity(token) inside a try/finally. The existence query therefore runs with MediaProvider's own identity, and the identity is always restored.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: testCreateRequest now inserts a real audio row via sIsolatedResolver.insert (assumeTrue(uri != null)) and makes the request on that URI. The new test testCreateRequest_invalidUri_throwsException asserts IllegalArgumentException for the non-existent id 42."
     ],
     "completeness": [
      "Every URI in the list is checked. The check comes before the Intent is built with `method` as its action, which suggests it covers every CREATE_*_REQUEST method. The commit message explicitly names CREATE_WRITE/DELETE/.._REQUEST.",
      "The check runs only when the request is created. The material does not show whether PermissionActivity checks existence again when the grant is applied; this diff does not address any window between the check and the grant.",
      "queryForSingleItem also throws when it finds multiple matches, so URIs that resolve to more than one row are rejected too. This is stricter than rejecting only non-existent URIs.",
      "Rejection happens at the first invalid URI, before the Intent is created, so no partial request is built."
     ],
     "behavior_changes": [
      "MediaStore create*Request calls (createWriteRequest is shown in the tests) now throw IllegalArgumentException(\"Invalid Uri: ...\") when any supplied URI does not exist or is ambiguous. Before the fix, the old test shows that a non-null request was returned for a non-existent id.",
      "One additional database query per requested URI, run with cleared calling identity.",
      "Callers that create requests for URIs that are not yet inserted will now get an exception. The existing test testCreateRequest was changed to use a real inserted row."
     ],
     "preconditions": [
      "A caller invokes a MediaStore CREATE_*_REQUEST method with a URI list that contains at least one URI not present in the Files table.",
      "The new check is reached only after the pre-existing per-URI validation block that comes before it in the same method (the diff does not show that block's contents)."
     ],
     "per_major": {
      "14": "same fix: commit 003dd55147bf in android-security-14.0.0_r25 (cherry-pick of internal changes 268fc3fb/027c6c2c, Change-Id I00a3b15d). 8/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 56e6173dcc97 in android-security-15.0.0_r13. The MediaProvider.java hunk is identical but at a different offset (line ~7644). The test hunk differs only in context and indentation around testGrantMediaReadForPackage. 8/8 lines are present at android-security-15.0.0_r17.",
      "16": "same fix: commit 119013a3d7e8 in android-security-16.0.0_r4. The MediaProvider.java hunk is identical, at line ~8383. 8/8 lines are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix is a single self-contained hunk. It depends only on queryForSingleItem, clearLocalCallingIdentity/restoreLocalCallingIdentity and FileColumns._ID already existing in MediaProvider.java, and it applies identically across 14/15/16 apart from line offsets. MediaProvider is a mainline (APEX) module, so the fix may also arrive through a module update rather than a platform tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the enclosing method in MediaProvider.java (the hunk header shows only the class).",
      "What concrete privilege escalation a request on a non-existent URI led to (for example, how PermissionActivity behaves when the row appears later). The material does not show the grant-side code.",
      "Whether PermissionActivity runs its own existence check at grant time.",
      "Whether trash and favorite requests go through the same code path (the commit message names only write and delete explicitly)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "15": {
   "counts": {
    "announced": 38,
    "certain": 37,
    "probable": 0,
    "not_published": 1,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2025-48631",
     "osv_id": "ASB-A-444671303",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a hard 4096 px per-dimension limit in LocalImageResolver.onHeaderDecoded, so that images whose width or height exceeds it are rejected in the header callback instead of being passed on to the existing downsampling-ratio logic.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The only production change is a constant plus an early throw at the top of onHeaderDecoded, and the same hunks appear on 14, 15 and 16. The test needs the binary asset test16000x16000.gif added to core/tests/coretests/res/drawable."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b645abd5b328b39c6cdfd12d51ecf06843a232e5",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-43766",
     "osv_id": "ASB-A-288144143",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The initial fix (shown for Android 14) makes the Bluetooth stack start LE link encryption as soon as an ACL link to a device with a known LE link key is established. On Android 15 the material shows three commits: a revert of a mis-resolved backport, a reland of three regression fixes (skip encryption when the link is already encrypted, ignore some SMP completion failures for bonded peripheral-role links, set the L2CAP link state to connected earlier), and a fix that saves the peer CSRK only after LTK generation.",
      "confidence": "medium",
      "backport_notes": [
       "On 15, the reland of the regression fixes (btm_ble_sec.cc, l2c_ble.cc) and the smp_act.cc CSRK fix were applied together under bug 288144143. In btm_ble_set_encryption the is_le_device_encrypted() early return must sit inside `case BTM_BLE_SEC_ENCRYPT:`. The earlier 15 backport put it before the case label, which made it dead code, and that backport was reverted.",
       "On branches with aconfig flags, smp_proc_srk_info may already contain both orderings behind save_peer_csrk_after_ltk_gen. The Android 16 commit removes the flag and keeps only the early smp_key_distribution_by_transport call."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "5a4ff0112a76a3c279ba9e6e753d192451aa55ba",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "d33196b5d7f6a3b7c2e9110331d2a013ef0d7b1d",
       "tag": "android-security-15.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "7f8dd23dae8029689fc250a2aba9336ddbc12c1e",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "b2f37c9983f1d2c7affbd3c46172efd759669ded",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "d259085c61030959c2ab728e1e05c0690a4d97d5",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "public_before_announcement_days": 90,
     "first_commit_before_announcement_days": 454,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-32313",
     "osv_id": "ASB-A-399155883",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Two-part fix: libbinder Parcel::appendFrom now decides whether to grow the buffer based on the current write position (mDataPos + len) instead of mDataSize + len, and NotificationHistory's Parcel constructor rejects a positionInParcel value larger than the reconstructed parcel's data size before calling setDataPosition.",
      "confidence": "high",
      "backport_notes": [
       "The libbinder change is a one-line condition swap in Parcel::appendFrom. The base change is a four-line insertion in NotificationHistory(Parcel in) directly after mParcel.setDataSize(mParcel.dataPosition()). The Android 14 native hunk sits at a different line offset (498 vs 542), but the surrounding diff context is the same.",
       "On the latest 14 and 15 tags, 5/6 lines of the native patched region are present. Check directly in Parcel.cpp that the `(mDataPos + len) > mDataCapacity` condition is there."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r15",
      "date": "2026-05-04",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b1b3c20edf73196c31fb8813a769348178391b0c",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "dc54178de228c7519e3486a0dd9e6a263d625e26",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "3f5375db991b9a9ea406e58fa0910227743ecf2f",
       "tag": "android-security-15.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "9de6991552d48387cfaf312f06a045f0c7163a4c",
       "tag": "android-security-15.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48544",
     "osv_id": "ASB-A-415783046",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the MediaProvider Bundle extra INCLUDED_DEFAULT_DIRECTORIES, which AccessChecker honored when building access SQL, and replaces it with an explicit Optional<List<String>> parameter that only the internal FUSE directory-rename path populates.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, mostly mechanical: delete the constant; change the AccessChecker signatures; add the Optional parameter through getQueryBuilder -> getQueryBuilderInternal -> appendAccessCheckQuery and the FUSE rename helpers; pass Optional.empty() at every other call site. Where appendAccessCheckQuery has only one getWhereForConstrainedAccess call (as in 14), only that call needs updating. Any out-of-tree caller of getQueryBuilder or getWhereForConstrainedAccess must be updated to compile."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r10",
      "date": "2025-09-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r10",
      "date": "2025-09-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "c34a7f642548130e95dc374035d5a3564d30599f",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 180,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48567",
     "osv_id": "ASB-A-377888957",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now NFD-normalizes paths and strips Unicode default-ignorable code points (e.g. zero-width space) before the regex-based Android/data and Android/obb private-directory access checks, via a new FileUtils.normalizeAndFilterDefaultIgnorableCodepoints applied in isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse.",
      "confidence": "high",
      "backport_notes": [
       "Single combined commit per branch, depending on android.icu (UCharacter/UProperty) and java.text.Normalizer. For 14/15 take the unflagged variant; for 16 the aconfig flag must exist and be enabled in the build or the sanitizer is a no-op. If the target tree's shouldBypassFuseRestrictions differs (16 stores the result in a local), only the method name swap to the private variant needs adapting.",
       "The Android 15 test diff references @EnableFlags/Flags without visible imports or a SetFlagsRule; when backporting, either drop the annotation (as the 14 commit did) or add the flag plumbing from the 16 commit."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "2f45f53ce92cf57046461638697ca7a3be9628f3",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48568",
     "osv_id": "ASB-A-322157041",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks user switching so that, when switching to a secure user, system_server asks SystemUI to lock via lockNow with an IRemoteCallback and blocks until SystemUI signals that callback (up to 20 s). SystemUI cancels pending dismisses during the switch and rejects a keyguard exit animation that was requested for a different user.",
      "confidence": "medium",
      "backport_notes": [
       "This fix spans system_server (UserController, KeyguardServiceDelegate) and SystemUI (KeyguardViewMediator, UserTracker/UserTrackerImpl), so both sides should ship together. Inferred from the diff: an unpatched SystemUI does not read the LOCK_ON_USER_SWITCH_CALLBACK option, so the server-side 20 s wait would time out and throw. Forks that customize UserTracker implementations or fakes must also implement the new isUserSwitching property.",
       "The Android 14 port is structurally different (setCurrentUser-based, KeyguardUpdateMonitor.getCurrentUser instead of SelectedUserInteractor, extra KeyguardUnlockAnimationController changes). Use the branch-specific commit rather than cherry-picking the Android 15 change."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-opus-5-5",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bec2efb2d7145dd2ef6de3ded785b46c7caa08ac",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48574",
     "osv_id": "ASB-A-428700812",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DisplayPolicy.validateAddingWindowLw now enforces MANAGE_ACTIVITY_TASKS against the explicitly passed callingPid/callingUid when a window requests PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, instead of calling ActivityTaskManagerService.enforceTaskPermission(), which reads the Binder calling identity that has already been cleared on this path.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, two-file change; the DisplayPolicy hunk is a 1-line to 3-line replacement that applied identically on 14, 15 and 16 (only offsets and trailing context differ), assuming validateAddingWindowLw takes callingPid/callingUid and has the PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP check."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e6e8c87c3c99ac610918d59a4fa973741b071b92",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48577",
     "osv_id": "ASB-A-413380719",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "KeyguardViewMediator now records which user a pending keyguard-done belongs to and checks the current user again at four points: biometric-auth success, hide-animation completion, the keyguardGoingAway background runnable and the exit-animation mismatch path. If a user switch happened in between, it re-shows or resets the keyguard and cancels queued dismiss/hide work.",
      "confidence": "high",
      "backport_notes": [
       "This is a single-commit fix, but it cannot be applied verbatim across branches. The user lookup differs (KeyguardUpdateMonitor.getCurrentUser() on 14, mSelectedUserInteractor.getSelectedUserId() on 15/16), and the keyguardDonePending callback signature differs on 14, so the hunks must be re-targeted.",
       "The Android 14 variant leaves out the mDismissToken/HIDE cancellation hunks. Forks on 14-based trees should check whether the delayed dismiss() post exists there and consider carrying those hunks too."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3b6cc0893907772a281af013ce666cdc3491c3dc",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48578",
     "osv_id": "ASB-A-418225717",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds missing restoreCallingIdentity / restoreLocalCallingIdentity calls on two exception paths in MediaProvider (getResultForGetMediaUri and updateInternal), so a thrown IllegalArgumentException or NumberFormatException no longer leaves the caller's identity cleared.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches only MediaProvider.java. The hunks apply by context: the 'if (!authority.equals(MediaDocumentsProvider.AUTHORITY)' block and the 'final LocalCallingIdentity token = clearLocalCallingIdentity();' line in updateInternal. On a 14-based tree, check whether getResultForGetMediaUri has the unprotected throw and apply the first hunk if it does, because Google's Android 14 commit leaves that hunk out. MediaProvider is a mainline (APEX) module (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "2a8f3f27275cd33e78ced9f649fffc38773cb4e3",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48579",
     "osv_id": "ASB-A-417195606",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restores the cleared Binder/local calling identity on two exception paths in MediaProvider (unsupported authority in getResultForGetMediaUri, NumberFormatException from ContentUris.parseId in updateInternal) so the identity is not left cleared after the exception propagates.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained (two hunks, no new symbols). When backporting to a tree based on Android 14, check whether getResultForGetMediaUri exists with the same unsupported-authority throw after clearCallingIdentity(), because the 14 commit did not include that hunk and the material does not explain why.",
       "MediaProvider ships as a mainline module in addition to the platform tree, so the fix may also need to land in the module branch used by the device (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "2a8f3f27275cd33e78ced9f649fffc38773cb4e3",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48582",
     "osv_id": "ASB-A-369105011",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now stores the calling uid (getCallingUidOrSelf()) of the app that makes a create bulk request in the PermissionActivity intent extras. PermissionActivity then uses that uid, instead of getCallingPackage(), to decide whether a consent dialog is needed and which package receives the URI permissions.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. The code depends on MediaStore.EXTRA_CALLING_PACKAGE_UID existing in apex/framework/java/android/provider/MediaStore.java. Trees that lack it need the constant addition from the Android 14 commit (45db544a1f1a)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "6b652db8a6d344de658ab5118c1c1dd419fa0bce",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48602",
     "osv_id": "ASB-A-407562568",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "KeyguardViewMediator now cancels a pending keyguard exit animation when a user switch begins (drops queued START_KEYGUARD_EXIT_ANIM, sets mIsKeyguardExitAnimationCanceled) and, in the post-traversal completion of exitKeyguardAndFinishSurfaceBehindRemoteAnimation, re-shows the keyguard instead of finishing the exit when that flag is set.",
      "confidence": "high",
      "backport_notes": [
       "The production change is small and self-contained in KeyguardViewMediator; the test requires the postAfterTraversal(Runnable) seam and an overridable constructor site, and the constructor argument list differs per release (14 ends with mSystemPropertiesHelper, 15 with WindowManagerOcclusionManager, 16 adds Kosmos communal interactors), so the test hunk needs manual adaptation.",
       "When porting to a 14-based tree, check whether mIsKeyguardExitAnimationCanceled is actually set on user switch; the 14 commit only adds the START_KEYGUARD_EXIT_ANIM removal in the switch handler, unlike 15/16 which set the flag explicitly in handleBeforeUserSwitching."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "67769a49e360493572d0cc6f47b279ef8a1b0045",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48609",
     "osv_id": "ASB-A-414388731",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsProvider.deleteDataRows now checks that the canonical path in a part/drm row's _data column is under the provider's \"parts\" directory before it deletes the file from disk. The check uses a new isFilePathCanonical helper, which openFile also uses now.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It relies on PARTS_DIR_NAME and the openFile canonical-path check existing, which they do in the 14, 15 and 16 diffs. Pass the Context through deleteMessages -> deleteParts -> deleteDataRows and update every call site of the changed static helpers.",
       "The Android 14 variant's test adds a UserManager field that is not in the 15/16 diffs. When porting, check that the target test file imports UserManager, or drop that line."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "e40b30ca3fa32c36e77ac4ca8926e4305c94a369",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2025-48619",
     "osv_id": "ASB-A-414387646",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a validateFileMode() sanitizer to ContentProvider.Transport.openFile and openAssetFile. When 'w' is absent from the mode string, it strips the 't' (truncate) and 'a' (append) characters, and the sanitized mode is then used both for the permission check and for the call forwarded to the provider.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit in one file. The three hunks apply where Transport.openFile/openAssetFile call enforceFilePermission(attributionSource, uri, mode) and then mInterface.openFile/openAssetFile. Only the traceBegin tag constant differs between 14 and 15+, and it is context, not part of the change.",
       "Every major reports 7/8 lines of the patched region present. The cause is not shown. The r17 context shows both call sites but not the validateFileMode body, so check that the method and both call sites are present when backporting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e623c490905c365b8f0a2a38116f7b84539c08f9",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48630",
     "osv_id": "ASB-A-455563813",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SkiaRenderEngine::drawLayersInternal now clips the canvas to the layer bounds before drawing blur when the layer has no rounded-corner clip, so a client-supplied blur region larger than the layer can no longer draw blurred content outside the layer.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained 4-line change inside the existing SkAutoCanvasRestore block in drawLayersInternal. It needs the `bounds` SkRRect from getBoundsAndClip and the `roundRectClip` if-statement to exist. Apply it by context, not by line number: the hunk starts at different lines on each branch (833/864/907)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "4deb93b73906d31a4b2d359510e0886ebe702600",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48634",
     "osv_id": "ASB-A-406243581",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a sanitizePrivateFlags step to WindowManagerService window relayout. When these bits are newly added in the relayout attrs, it strips PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY and PRIVATE_FLAG_TRUSTED_OVERLAY from callers lacking INTERNAL_SYSTEM_WINDOW, and PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP from callers lacking MANAGE_ACTIVITY_TASKS.",
      "confidence": "high",
      "backport_notes": [
       "The production change is self-contained: two new private helpers plus one call in the relayout block. Across branches only the neighbouring line differs (sanitizeSpyWindow on 14, sanitizeInputFeatures on 15/16). The test change uses newWindowBuilder and stubs mWm.mContext.checkPermission. Google's 14 backport also added an android.content.pm.PackageManager import to the test.",
       "When porting, note the pid/uid parameter order mismatch between hasPermission's declaration and its call sites (see completeness), and decide whether to keep the upstream code verbatim or align the order. The upstream unit tests cannot tell the two apart."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a54ba90ae134a39adda1bab8aedb16e99454c810",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48635",
     "osv_id": "ASB-A-446678690",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "announced-not-published",
     "detail": "platform/frameworks/base",
     "coverage": {
      "n_ingested_tags": 18,
      "first_ingested_tag": "android-security-15.0.0_r1",
      "latest_ingested_tag": "android-security-15.0.0_r18",
      "window_base_tag": "android-15.0.0_r1",
      "window_start": "2024-09-03",
      "partial_tags": [],
      "projects": [
       {
        "project": "platform/frameworks/base",
        "fetched_ok_in_tags": 18,
        "ingested_tags": 18,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48641",
     "osv_id": "ASB-A-392699284",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the ST NFC AIDL HAL (hardware/st/nfc aidl/Nfc.h), the static callbacks Nfc::eventCallback and Nfc::dataCallback now copy the static shared_ptr Nfc::mCallback into a local shared_ptr under a new static pthread mutex sCallbackLock. They call sendEvent/sendData on that local strong reference instead of dereferencing the shared static directly.",
      "confidence": "medium",
      "backport_notes": [
       "This is a single-file, header-only change. Google applied the same diff to 14, 15 and 16; only the context differs on 16. When backporting, check that every write of mCallback in the HAL implementation takes sCallbackLock. The commit message describes read/write protection, but the diff does not show the writer side.",
       "sCallbackLock is a static declared in the header. Any writer-side locking must use the same mutex instance as the translation unit containing eventCallback/dataCallback."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/st/nfc",
       "sha": "ffe16073e613ee40acf098f8a27485c9730b533f",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/st/nfc"
     ]
    },
    {
     "cve": "CVE-2025-48642",
     "osv_id": "ASB-A-455777515",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes the data-cache cleans in pvmfw's payload hand-off (jump_to_payload) and in vmbase's flush_region from `dc cvau` (clean to Point of Unification) to `dc cvac` (clean to Point of Coherency), so that zeroed or flushed memory is written back to main memory on SoCs with a unified cache before the PoC.",
      "confidence": "high",
      "backport_notes": [
       "Each change replaces one token (`cvau` -> `cvac`) in a `dc` instruction, but the files differ between branches. flush_region is in pvmfw/src/helpers.rs on 14, in vmbase/src/memory/util.rs on the 15 security branch, and in libs/libvmbase/src/arch.rs (via the dc! macro) on 16. Per index search, the android-15.0.0_r36 tree uses guest/pvmfw/src/entry.rs and libs/libvmbase/src/arch.rs. Match by content rather than by path, and apply the change to any extra flush loop present in the target (for example the EH stack loop on 16)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Virtualization",
       "sha": "fa7ca326d451405f53d55883f34274617aa4644e",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Virtualization"
     ]
    },
    {
     "cve": "CVE-2025-48644",
     "osv_id": "ASB-A-449181366",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Two frameworks/base commits harden the input method stack against oversized IME metadata: InputMethodInfo parsing now enforces a cumulative MAX_METADATA_SIZE_BYTES budget over all resolved attribute values (not just the raw XML file size), and IInputMethodManager#getEnabledInputMethodSubtypeList now returns a blob-backed InputMethodSubtypeSafeList instead of a plain List<InputMethodSubtype> to avoid TransactionTooLargeException.",
      "confidence": "high",
      "backport_notes": [
       "The hardening commit is self-contained in core/java/android/view/inputmethod/InputMethodInfo.java (plus tests) and only needs MAX_METADATA_SIZE_BYTES and COMPONENT_NAME_MAX_LENGTH to exist. Drop attributes the target branch lacks (as the 14 backport did for languageSettingsActivity, isVirtualDeviceOnly and connectionless stylus handwriting), and keep the single readTracker shared across the method TypedArray and every subtype TypedArray.",
       "The InputMethodSubtypeSafeList commit depends on InputMethodInfoSafeList already existing (change I0a7667070fcdf17d34b248a5988c38064588718a) and changes the IInputMethodManager AIDL return type. Every implementer of IInputMethodManagerImpl.Callback (InputMethodManagerService, ZeroJankProxy) and the client unwrapping in IInputMethodManagerGlobalInvoker must therefore change together. The commit also removes InputMethodInfoSafeList.empty(), so replace any remaining callers with create(null)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "42ad31c95505ed244cf3506dac699eb07c0c621c",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "903125b038264d69dae8a4d6eb17d6caeff94fa9",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48645",
     "osv_id": "ASB-A-443062265",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DeviceAdminInfo.loadDescription now catches an OutOfMemoryError thrown while it loads a device admin receiver's description resource through PackageManager.getText, and throws the already-declared NotFoundException instead, so an oversized description string no longer escapes to the caller as an uncaught Error.",
      "confidence": "high",
      "backport_notes": [
       "Single-hunk, self-contained change with no new dependencies. It applies wherever loadDescription still has the original pm.getText call. The commit lists two Cherrypick-From sources, but the material contains only this one hunk. The trimmed loadDescriptionSafe path seen at r17 is not part of this diff."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "42b92b39e52cd74c25b76d6e2c9538f06b5576c4",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48646",
     "osv_id": "ASB-A-457742426",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ActivityStarter now only propagates sourceRecord.launchedFromPackage/launchedFromFeatureId to a FLAG_ACTIVITY_FORWARD_RESULT target after verifying, via PackageManagerInternal.getPackageUid and UserHandle.isSameApp, that the package belongs to the calling uid.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with no dependencies. The Java change is identical across 14/15/16; only the hunk offset differs. It uses mService.getPackageManagerInternalLocked(), PackageManagerInternal and UserHandle, which the fixed branches already use without adding imports. The test depends on ActivityBuilder.setLaunchedFromPackage/setLaunchedFromUid and the mMockPackageManager mock in wmtests."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "04c368b5f527463d1ed032ffdde95e28bbb44b4c",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-64783",
     "osv_id": "ASB-A-483074175",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Moves platform/external/dng_sdk up to Adobe DNG SDK 1.7.1 build 2410. The upgrade touches 61 files: parsing, stream I/O, JPEG XL coding, opcodes and fingerprinting.",
      "confidence": "low",
      "backport_notes": [
       "This is a whole-SDK drop, not a targeted patch. Backport it as a unit: the fingerprint API changes (private data, Process renamed to ProcessPtr) and the new WriteTIFF/WriteDNG parameters break any out-of-tree caller that uses fingerprint.data or the old dng_md5_printer::Process.",
       "Keep the Google-local modifications, such as the '// BEGIN GOOGLE MODIFICATION' plane loop in dng_opcode_MapTable::ProcessArea, when merging into forks."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-opus-5-5",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "45da109612871d320f98e68acc8018dad1d1b117",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2025-64784",
     "osv_id": "ASB-A-483074618",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Google fixed this by updating platform/external/dng_sdk wholesale to Adobe DNG SDK 1.7.1 build 2410. The update changes 61 files and adds stricter validation in several parsing and processing paths. The material does not single out which hunk is the security fix.",
      "confidence": "low",
      "backport_notes": [
       "This is a wholesale vendor SDK bump with API changes: fingerprint data is now private, Process is renamed to ProcessPtr, the dng_md5_printer constructor is protected, and WriteTIFF/WriteDNG take new parameters. Out-of-tree code calling dng_sdk may need adapting. Taking the full update is safer than cherry-picking hunks, since the security-relevant hunk is not identified."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-opus-5-5",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "45da109612871d320f98e68acc8018dad1d1b117",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2025-64893",
     "osv_id": "ASB-A-483075215",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Rebases platform/external/dng_sdk onto Adobe DNG SDK 1.7.1 build 2410. This is a 61-file vendor update, and the material does not point to any single hunk as the security fix.",
      "confidence": "low",
      "backport_notes": [
       "Apply this as a whole-library rebase rather than cherry-picking individual hunks. The API changes are breaking: dng_fingerprint::data is now private, dng_md5_printer::Process(void*,len) became ProcessPtr, AddDigest now takes dng_md5_printer_stream, and the WriteTIFF/WriteDNG signatures changed. Out-of-tree code using these APIs must be updated, and Google-local modifications (for example the MapTable plane check in dng_misc_opcodes.cpp) must be kept."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-opus-5-5",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "45da109612871d320f98e68acc8018dad1d1b117",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2026-0005",
     "osv_id": "ASB-A-366405211",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a permission-guarded IActivityTaskManager.rebuildSystemLockTaskPinnedMode(). It re-applies app pinning (status bar disable, keyguard disable, lock task listeners, recents) from the state LockTaskController still holds. SystemUI's KeyguardViewMediator calls it when the system reports LOCK_TASK_MODE_PINNED. The fix also keeps the keyguard enabled/disabled state across a keyguard service disconnection, so app pinned mode is kept after a SystemUI crash.",
      "confidence": "high",
      "backport_notes": [
       "The change spans the framework side (IActivityTaskManager.aidl, ActivityTaskManagerService, LockTaskController, KeyguardServiceDelegate) and SystemUI (KeyguardViewMediator), all in frameworks/base. SystemUI calls the new binder method, so the AIDL method and both sides must ship together. The Android 14 variant shows the adaptation for trees without the KeyguardViewMediator mActivityTaskManagerService field: it uses ActivityTaskManager.getService(), and mLockTaskModeTasks.get(0) instead of getFirst()."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "08abd0bc0451891406ee3162dde72189b8108268",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0011",
     "osv_id": "ASB-A-454062218",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings.enableSystemPackageLPw and addPackageLPw in PackageManager Settings now reuse the disabled PackageSetting when a shared-UID system package is re-enabled. A new hasSharedUser parameter bypasses the failing mAppIds.registerExistingAppId check, and sharedUserAppId is copied over. Before the fix, the preload was re-scanned as a brand-new app, which per the commit message could change the app's UID when the manifest has sharedUserMaxSdkVersion.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. Across versions, the only adaptation is the addPackageLPw parameter list: Android 14 uses the long explicit-parameter form without isSdkLibrary and without the Flags.disallowSdkLibsToBeApps() clause. Any OEM-added callers of addPackageLPw need the new boolean, and the number of nullable() matchers in the MockSystem.kt stub must match the new arity."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "65e7256522b4ee9a6939713a05b9948bda2707a8",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0012",
     "osv_id": "ASB-A-392614656",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In SystemUI's ExpandableNotificationRow.setHideSensitive, the fix makes the row take the immediate (non-animated) visibility switch when the public (redacted) layout should be shown and the showing-public state has not changed, instead of running the hide-sensitive animation; on Android 16 it narrows an existing flag-gated skip to that same condition.",
      "confidence": "medium",
      "backport_notes": [
       "Branches that already contain the flag-gated skip (Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged, as on 16) need only the one-line condition change. Branches without it (14/15) need the full hunk that introduces isShowingLayoutNotChanged and shouldSkipHideSensitiveAnimation, adapted to that branch's surrounding context.",
       "No fork comparison section was provided in the material, so no per-file fork verdict can be given."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "679801a99ccd31926f2d82e92b22ee9d5e5a0184",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0013",
     "osv_id": "ASB-A-447135012",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PickActivity.setupLayout now clears the Intent selector (moreApps.setSelector(null)) on the copy of the incoming intent that DocumentsUI reuses to show other apps that can handle the request, so a selector supplied by the caller can no longer be used to launch an arbitrary activity with DocumentsUI's permissions.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a self-contained two-line insert right after moreApps.setPackage(null) in PickActivity.setupLayout, with no dependencies. On forks where the cross-profile block differs (as on 14), the insert can be placed using only the lines new Intent(intent), setComponent(null) and setPackage(null). Intent.setSelector exists since API 15, so no SDK gating is needed (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/DocumentsUI",
       "sha": "15f898d5082a2e0f9bf7d35d4e4e3f4fadcb3a90",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2026-0014",
     "osv_id": "ASB-A-443742082",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early `Process.isSdkSandboxUid(uid)` check to the AppOpsService helper that decides whether a UID is trusted for attribution tag validation, so SDK sandbox UIDs are always treated as untrusted.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a self-contained 3-line insertion that applied identically on 14, 15 and 16; only the line offsets differ. It depends on Process.isSdkSandboxUid, which has existed since the SDK sandbox arrived in Android 13 (general platform knowledge). To place it, anchor on the `if (packageName == null) { return true; }` block followed by `int appId = UserHandle.getAppId(uid);`."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "531f3ac25912ec22f45d25d1a100bbe297c4b468",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0015",
     "osv_id": "ASB-A-445917646",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppOpsService now accepts an attribution tag taken from the proxy's manifest (or via the system-proxy shortcut) only when the proxy is trusted, meaning the op flags lack OP_FLAG_UNTRUSTED_PROXIED. It also checks that a proxy package name actually resolves to the proxy uid before treating it as system, stops the dry-run start path from caching attribution tag information, and makes the finish path find the right AttributedOp by in-progress clientId instead of creating entries.",
      "confidence": "high",
      "backport_notes": [
       "Single commit across two files. Three spots need adapting to the branch: (1) the AttributedOp map lookup (op.mAttributions on 14, mDeviceAttributedOps with getPersistentId on 15, getPersistentDeviceIdForOp(virtualDeviceId, op.op) on 16); (2) whether the dry run is a separate start variant or a dryRun boolean (14 uses dryRun ? null : attributionTag); (3) the verifyAndGetBypass overload arity, since all callers of the 5/6-arg forms must pass isProxyTrusted.",
       "AttributedOp.hasInProgressEvent relies on InProgressStartOpEvent exposing getClientId() and getFlags(), and on mPausedInProgressEvents and isPaused(). Confirm these exist on the target branch."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "22d9cd12ef9997934b269423c856dc78c33c3ac1",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0020",
     "osv_id": "ASB-A-453649815",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix trims leading and trailing whitespace from the names of <permission>, <permission-tree> and <permission-group> manifest elements during package parsing in ParsedPermissionUtils, so the stored name is the trimmed form rather than the literal manifest string.",
      "confidence": "medium",
      "backport_notes": [
       "Standalone one-file change. File location differs: services/core/java/com/android/server/pm/pkg/component/ on 14 versus core/java/com/android/internal/pm/pkg/component/ on 15+. On 15/16, the parsePermissionTree trim is placed before the dot-count logic, unlike on 14."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "4fa32d4fef904e568d92f288f6029eb7f917d8b7",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0021",
     "osv_id": "ASB-A-430047417",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On devices with Settings activity embedding (multi-pane) enabled, AppInfoBase now checks INTERACT_ACROSS_USERS_FULL against the initial calling package reported by SettingsActivity before, and instead of, the getLaunchedFromUid-based path. The Settings$AppUsageAccessSettingsActivity intent filter also gets android:priority=\"1\".",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. It depends on ActivityEmbeddingUtils.isEmbeddingActivityEnabled(Activity), SettingsActivity.getInitialCallingPackage() and the mPm field in AppInfoBase already existing. Google applied the identical diff to 14, 15 and 16."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "298fd46325a8c3d46d46940524627a38165327da",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-0023",
     "osv_id": "ASB-A-459461121",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PackageInstallerService now explicitly clears PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE from caller-supplied SessionParams.installFlags before re-deriving it from DevicePolicyManagerInternal.isUserOrganizationManaged(userId) during install session creation.",
      "confidence": "high",
      "backport_notes": [
       "Simple single-hunk backport: insert the clear line directly after `final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);` and before the isUserOrganizationManaged conditional in PackageInstallerService.java."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "221ac26392cd0c8f5c9a8aa872068660506102ab",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0024",
     "osv_id": "ASB-A-326211886",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now passes the open() options Bundle into isRedactionNeededForOpenViaContentResolver. When the Bundle contains MediaStore.EXTRA_MEDIA_CAPABILITIES_UID (> 0) and that UID's identity reports PERMISSION_IS_REDACTION_NEEDED, the content is redacted. This closes an ACCESS_MEDIA_LOCATION bypass through a SAF picker.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk pair in MediaProvider.java. The target branch must already have LocalCallingIdentity.fromExternal (called as fromExternal(getContext(), mUserCache, uid, null, null)), the PERMISSION_IS_REDACTION_NEEDED constant and MediaStore.EXTRA_MEDIA_CAPABILITIES_UID."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "77f9bd12a6eddcb32d4ac3e09ea7f119e814d2f4",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-0025",
     "osv_id": "ASB-A-433746973",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Notification.java now reads the EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES extras through getParcelableArrayFromBundle(..., Bundle.class) instead of extras.getParcelableArray(..., Parcelable.class). Per the new test, an array that contains a non-Bundle element now causes an ArrayStoreException on enqueue, and no URI grant check happens for the embedded URIs.",
      "confidence": "high",
      "backport_notes": [
       "The production change touches only Notification.java and depends on the existing Notification.getParcelableArrayFromBundle helper, which the diff uses but does not add. Apart from line offsets, the Notification.java hunks are identical across 14, 15 and 16."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5ffc92d00244447bd738d45ba4ac135b194a8c94",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0034",
     "osv_id": "ASB-A-428701593",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Caps the per-user number of approved managed-service entries (notification listeners, condition providers, assistants) at 100 in ManagedServices.setPackageOrComponentEnabled, makes that method return whether the change was applied, and has NotificationManagerService skip follow-up actions (secondary approvals, broadcasts, policy save) when it was not.",
      "confidence": "high",
      "backport_notes": [
       "The return type of setPackageOrComponentEnabled changes from void to boolean in ManagedServices and in every subclass override (NotificationAssistants and NotificationListeners in NotificationManagerService.java). Any other ManagedServices subclass or OEM override must be updated to return boolean. The NotificationManagerServiceTest mocks must also be stubbed to return true.",
       "The commit message names an aconfig flag (limit_managed_services_count), but the diff has no flag guard. On 14, take the 14 variant of the NotificationListeners override (no redaction block)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a68f6e5719ce49be2ca80625755df2043d65017a",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0035",
     "osv_id": "ASB-A-418773439",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now rejects MediaStore create*Request calls (CREATE_WRITE_REQUEST, CREATE_DELETE_REQUEST and siblings) with IllegalArgumentException when any requested URI does not resolve to exactly one existing item at request time (the commit message says the Files table).",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained hunk. It relies only on existing helpers (queryForSingleItem, clearLocalCallingIdentity/restoreLocalCallingIdentity, FileColumns._ID), and Google applied the identical hunk on 14, 15 and 16 with only line offsets changing. The commit lists two Cherrypick-From sources. The material does not show what the second internal commit contributed.",
       "MediaProvider is a Mainline module (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "56e6173dcc979573634540030bf715f3cab2d841",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    }
   ],
   "tags": [
    "android-security-15.0.0_r13"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "442272360"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ac82fd7cf68b4e172fee187613b20e8f6e59f025",
       "subject": "Block overriding satellite carrier config keys on user builds",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      },
      {
       "project": "platform/packages/services/Telephony",
       "sha": "6bbe10df60b39a21355ccdacb6b8a383f7af2d6d",
       "subject": "Block overriding satellite carrier config keys on user builds",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "378087531"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7aae5fe64fea855b2e70629d76c14fb74a9b2e59",
       "subject": "Prevent long press on profile notifs when locked",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "433251166"
     ],
     "commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "dc5f45bc9815dec227271fbd40c4aa0029f619a2",
       "subject": "Truncate WindowInfo.name during parcelling.",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "435737668"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "e59337837c3d5ed2e03498f6648fd01012336dbe",
       "subject": "Update RuntimePermissionsUpgradeController to accept null PermGroups",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "435737668",
      "456903810"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "90a573dcd5dfd3f1f3183422fd139cc04f3117d4",
       "subject": "V2 Merge all permissions in a shared UID for every package in that UID",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "315320090"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "d7a9ae275a32dbba3323d89a7ddfd5a1f1fc5a9b",
       "subject": "Add SystemGalleryRoleBehavior",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "315320090",
      "418085016"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "d80e327745c9826bcf5ab75459cbe84adaa3a0f0",
       "subject": "Revert^2 \"Ensure Gallery always shows as system fixed\"",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      },
      {
       "project": "platform/packages/modules/Permission",
       "sha": "9ee0a4d5e18f9e7bcb6c1db1459e714430a7803c",
       "subject": "Revert \"Ensure Gallery always shows as system fixed\"",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    },
    {
     "bug_ids": [
      "388530367"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "32d881de7c20211b3c2379569612b375c2a01f1f",
       "subject": "Fix SQL injection vulnerability in SMS/MMS providers",
       "tag": "android-security-15.0.0_r13",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r13"
     ],
     "tag": "android-security-15.0.0_r13"
    }
   ],
   "coverage": {
    "n_ingested_tags": 18,
    "first_ingested_tag": "android-security-15.0.0_r1",
    "latest_ingested_tag": "android-security-15.0.0_r18",
    "window_base_tag": "android-15.0.0_r1",
    "window_start": "2024-09-03",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {
    "CVE-2025-48631": {
     "cve": "CVE-2025-48631",
     "summary": "Adds a hard 4096 px per-dimension limit in LocalImageResolver.onHeaderDecoded, so that images whose width or height exceeds it are rejected in the header callback instead of being passed on to the existing downsampling-ratio logic.",
     "component": "platform/frameworks/base, core/java/com/android/internal/widget/LocalImageResolver (notification MessagingStyle/ConversationStyle image loading)",
     "weakness": "Missing bounds check on decoded image dimensions, leading to uncontrolled memory consumption (DoS)",
     "root_cause": "LocalImageResolver.onHeaderDecoded is the header callback that resolveImage passes to ImageDecoder.decodeDrawable. It read the image size from ImageDecoder.ImageInfo and only computed a downsampling ratio against maxWidth/maxHeight (resolveImage(Uri, Context) passes DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480). There was no upper bound on the source dimensions, so an image whose header declares very large dimensions was still decoded. Per the commit message, formats such as JPEG allow up to 65535x65535, and a notification carrying such an image caused a crashloop.",
     "fix_analysis": [
      "core/java/com/android/internal/widget/LocalImageResolver.java: adds private static final int DEFAULT_DECODE_HARD_LIMIT_PX = 4096.",
      "LocalImageResolver.onHeaderDecoded: right after info.getSize(), throws a RuntimeException with the dimensions in its message if size.getWidth() > 4096 or size.getHeight() > 4096. This happens before the originalSize/maxSize/ratio computation, inside the header-decoded callback.",
      "LocalImageResolver.resolveImage(Uri, Context) is unchanged (shown in context). It wraps any Exception from ImageDecoder.decodeDrawable in an IOException, so callers see the documented IOException.",
      "core/tests/coretests: adds res/drawable/test16000x16000.gif and LocalImageResolverTest.resolveImage_veryLargeResource_throwsException. The test expects an IOException when that resource is resolved via resolveImage(Uri, Context)."
     ],
     "completeness": [
      "The check covers every decode that goes through onHeaderDecoded. It comes before the ratio computation, so it does not depend on the maxWidth/maxHeight passed in.",
      "The limit is per dimension, not on total pixel count. An image up to 4096x4096 still passes and continues into the existing ratio logic.",
      "Only the ImageDecoder path in LocalImageResolver is guarded. The existing test resolveImage_nonBitmapResourceIcon_fallsBackToNonResizingLoad points to a fallback load path for non-bitmap resources that does not go through onHeaderDecoded. That path is not shown in the material and this fix does not change it.",
      "The limit is a hard-coded constant and is not configurable. The commit message says the test asset was made larger than needed in case the limit is raised later."
     ],
     "behavior_changes": [
      "Images with a width or height above 4096 px now fail to load through LocalImageResolver. resolveImage(Uri, Context) throws an IOException that wraps the new RuntimeException. Existing tests show the Icon-based overloads returning null on decode errors, so they are expected to return null here too, but their code is not shown.",
      "Before the fix, such images went through the ratio-based resizing logic (target size at most 480 px for resolveImage(Uri, Context)). Now they are rejected outright, so very large notification images are no longer displayed."
     ],
     "preconditions": [
      "An image is loaded through LocalImageResolver via the ImageDecoder path that installs onHeaderDecoded. Per the class comment, LocalImageResolver is used for MessagingStyle/ConversationStyle message images.",
      "The image header declares a width or height above 4096 px. Per the commit message, such an image can arrive via a notification."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 34926e40b0d8 in android-security-14.0.0_r24. The hunks are identical apart from a line offset (second hunk starts at old line 249 vs 252). 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base b645abd5b328 in android-security-15.0.0_r12 (cherry-pick of 449f35b532d5). 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base d6df825fda3a in android-security-16.0.0_r3, with a diff identical to the Android 15 one. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (Android 17 is not listed). The patched region (8/8 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. The only production change is a constant plus an early throw at the top of onHeaderDecoded, and the same hunks appear on 14, 15 and 16. The test needs the binary asset test16000x16000.gif added to core/tests/coretests/res/drawable."
     ],
     "confidence": "high",
     "unknowns": [
      "Which process crashlooped (the commit message only says a notification caused a crashloop) and the exact memory failure mode.",
      "The full set of LocalImageResolver overloads and callers that reach onHeaderDecoded, whether any of them pass NO_MAX_SIZE, and how the non-ImageDecoder fallback path handles large images. The material shows only resolveImage(Uri, Context).",
      "How the target size is applied to the decoder after the ratio computation (not shown in the material).",
      "Why 4096 was chosen as the limit, beyond the comment about device memory."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43766": {
     "cve": "CVE-2024-43766",
     "summary": "The initial fix (shown for Android 14) makes the Bluetooth stack start LE link encryption as soon as an ACL link to a device with a known LE link key is established. On Android 15 the material shows three commits: a revert of a mis-resolved backport, a reland of three regression fixes (skip encryption when the link is already encrypted, ignore some SMP completion failures for bonded peripheral-role links, set the L2CAP link state to connected earlier), and a fix that saves the peer CSRK only after LTK generation.",
     "component": "platform/packages/modules/Bluetooth, system/stack (ACL, BTM LE security, L2CAP LE, SMP)",
     "weakness": "Bulletin type: information disclosure. Inferred from the Android 14 commit 'Encrypt LE link immediately on reconnection': the LE link was not encrypted right away when a bonded device reconnected. The Android 15 commits fix regressions caused by that change.",
     "root_cause": "Per the Android 14 fix diff (system/stack/acl/btm_acl.cc, StackAclBtmAcl::btm_establish_continue), the stack did not call btm_ble_set_encryption when an LE ACL link was established to a device whose LE link key was known. The fix adds that call. According to the commit messages, the change caused regressions (bug 288144143, b#428948282). The Android 15 follow-ups address these points: (1) btm_ble_set_encryption had no early return for an already-encrypted link in the BTM_BLE_SEC_ENCRYPT case. (2) In peripheral role, an encryption request becomes an SMP security request, and the resulting SMP_COMPLT_EVT failures (the central does not handle the cross-over, or the link disconnects) were passed on to the LE callback by btm_proc_smp_cback. (3) l2cble_notify_le_connection set p_lcb->link_state = LST_CONNECTED only after calling btm_establish_continue_from_address. (4) smp_proc_srk_info saved the peer CSRK with p_cb->sec_level before smp_key_distribution_by_transport ran, so the security level was saved as zero and the MITM_AUTH flag could be cleared when the CSRK was loaded in the next session. A first backport of the regression fixes (39a69ddf3bde) was reverted for an incorrect cherry-pick resolution that led to dead code: its is_le_device_encrypted() check sat inside the switch before any case label.",
     "fix_analysis": [
      "Initial fix (shown for Android 14, 796182896347), system/stack/acl/btm_acl.cc:StackAclBtmAcl::btm_establish_continue: for a BLE transport ACL, look up the device record with btm_find_dev. If p_dev_rec->is_le_link_key_known(), call btm_ble_set_encryption(remote_addr, BTM_BLE_SEC_ENCRYPT, role_central ? HCI_ROLE_CENTRAL : HCI_ROLE_PERIPHERAL) before NotifyAclLinkUp.",
      "Android 15 b2f37c9983f1: reverts the earlier backport 39a69ddf3bde. In that backport the is_le_device_encrypted() early return sat before `case BTM_BLE_SEC_ENCRYPT:` inside the switch, where it can never run. Its btm_ble_complete_evt_ignore call sat later in the SMP_COMPLT_EVT handling, after the 'p_dev_rec is NULL' check.",
      "Android 15 7f8dd23dae80 (reland), system/stack/btm/btm_ble_sec.cc:btm_ble_set_encryption: inside `case BTM_BLE_SEC_ENCRYPT:`, return BTM_SUCCESS when p_rec->sec_rec.is_le_device_encrypted(). No start-encryption command or SMP security request is then issued for a link that is already encrypted.",
      "Same commit, btm_ble_sec.cc: new static helper btm_ble_complete_evt_ignore(p_dev_rec, p_data). It acts when all of these hold: cmplt.reason != SMP_SUCCESS; !role_central; pairing_bda differs from both bd_addr and ble.pseudo_addr; is_le_link_key_known(); and ble_keys.key_type != BTM_LE_KEY_NONE. In that case: if is_le_device_encrypted(), it logs and returns true; else if reason == SMP_CONN_TOUT, it calls l2cu_start_post_bond_timer(ble_hci_handle), logs and returns true. In btm_proc_smp_cback, at the start of `case SMP_COMPLT_EVT:` (before p_le_callback is invoked), if event == SMP_COMPLT_EVT and the helper returns true, the function returns BTM_SUCCESS.",
      "Same commit, system/stack/l2cap/l2c_ble.cc:l2cble_notify_le_connection: set p_lcb->link_state = LST_CONNECTED before calling btm_establish_continue_from_address(bda, BT_TRANSPORT_LE). l2cu_process_fixed_chnl_resp still follows.",
      "Android 15 d259085c6103, system/stack/smp/smp_act.cc:smp_proc_srk_info: move smp_key_distribution_by_transport(p_cb, NULL) from the end of the function to directly after smp_update_key_mask(...CSRK...). The pcsrk_key.sec_level taken from p_cb->sec_level and saved via btm_sec_save_le_key(BTM_LE_KEY_PCSRK) then reflects the level determined after LTK generation. On Android 16, 769a3dea8016 removes the aconfig flag save_peer_csrk_after_ltk_gen so this ordering always applies."
     ],
     "completeness": [
      "The early return for an already-encrypted link is added only to the BTM_BLE_SEC_ENCRYPT case of btm_ble_set_encryption. The other sec_act cases are not changed by this commit.",
      "btm_ble_complete_evt_ignore suppresses failures only for a local peripheral role, a bonded device and a device that is not the current pairing target. It swallows only two situations: link already encrypted, or SMP_CONN_TOUT. Other SMP failure reasons still reach p_le_callback.",
      "The ignore check is guarded by event == SMP_COMPLT_EVT, so the earlier case that falls through into SMP_COMPLT_EVT is not affected.",
      "In smp_proc_srk_info the CSRK save now runs after smp_key_distribution_by_transport. Whether p_cb and p_data state is still valid at that point cannot be verified from the material.",
      "The Android 14 encryption-on-reconnection change applies only when btm_find_dev returns a record whose LE link key is known."
     ],
     "behavior_changes": [
      "Per the Android 14 commit: at link establishment, before NotifyAclLinkUp, the stack calls btm_ble_set_encryption for LE ACL links to devices with a known LE link key. Per the code comment, in peripheral role this results in an SMP security request.",
      "btm_ble_set_encryption(BTM_BLE_SEC_ENCRYPT) returns BTM_SUCCESS without any action when the link is already encrypted.",
      "SMP_COMPLT_EVT failures for bonded, non-pairing, peripheral-role links are dropped with a log::warn if the link is already encrypted, or if the reason is SMP_CONN_TOUT. In the SMP_CONN_TOUT case, l2cu_start_post_bond_timer is also called.",
      "In l2cble_notify_le_connection, p_lcb->link_state is set to LST_CONNECTED before btm_establish_continue_from_address is called.",
      "Per the commit message, the peer CSRK is saved with the security level determined after LTK generation, so the MITM_AUTH flag is not cleared when the CSRK is loaded in the next session."
     ],
     "preconditions": [
      "An LE transport ACL connection to a device whose LE link key the local stack already holds (a bonded device reconnecting).",
      "For the SMP-failure suppression path: local device not central, device bonded (link key known, key_type != BTM_LE_KEY_NONE) and not the current btm_sec_cb.pairing_bda.",
      "For the CSRK path: LE pairing with bonding on both sides, where the peer distributes a CSRK (smp_proc_srk_info)."
     ],
     "per_major": {
      "14": "Only the initial fix is shown: 796182896347 'Encrypt LE link immediately on reconnection' in system/stack/acl/btm_acl.cc (StackAclBtmAcl::btm_establish_continue) at android-security-14.0.0_r15. 3/3 lines present at 14.0.0_r29. The material does not show whether the regression follow-ups were applied to 14.",
      "15": "At android-security-15.0.0_r12: revert b2f37c9983f1 of a mis-resolved backport, reland 7f8dd23dae80 of the regression fixes (btm_ble_sec.cc, l2c_ble.cc; backports of aosp/3424923, aosp/3420039, aosp/3394039), and d259085c6103, the CSRK ordering fix in smp_act.cc, applied without an aconfig flag. 8/8 lines present at 15.0.0_r17. The material does not show the commit carrying the initial btm_acl.cc change on 15.",
      "16": "Adapted: 769a3dea8016 at android-security-16.0.0_r3 removes the aconfig flag save_peer_csrk_after_ltk_gen (flags/pairing.aconfig) and makes the smp_proc_srk_info reordering unconditional. The flag-guarded code was already present. 5/8 lines of the patched region present at 16.0.0_r8; the reason for the partial match is not shown.",
      "17": "Not affected: Android 17 is not listed by the bulletin. 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "On 15, the reland of the regression fixes (btm_ble_sec.cc, l2c_ble.cc) and the smp_act.cc CSRK fix were applied together under bug 288144143. In btm_ble_set_encryption the is_le_device_encrypted() early return must sit inside `case BTM_BLE_SEC_ENCRYPT:`. The earlier 15 backport put it before the case label, which made it dead code, and that backport was reverted.",
      "On branches with aconfig flags, smp_proc_srk_info may already contain both orderings behind save_peer_csrk_after_ltk_gen. The Android 16 commit removes the flag and keeps only the early smp_key_distribution_by_transport call."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact trigger and what information is disclosed. The bulletin only gives type ID and severity High; the root cause is inferred from the Android 14 diff.",
      "The commit that landed the initial btm_acl.cc change on Android 15 and 16 (not in the material).",
      "Whether the regression follow-up commits were applied to Android 14.",
      "Whether btm_establish_continue_from_address leads to btm_ble_set_encryption, which would explain the l2c_ble.cc reordering.",
      "Why only 5/8 patched lines match at android-security-16.0.0_r8.",
      "Whether p_cb state and p_data remain valid after smp_key_distribution_by_transport in the reordered smp_proc_srk_info."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-32313": {
     "cve": "CVE-2025-32313",
     "summary": "Two-part fix: libbinder Parcel::appendFrom now decides whether to grow the buffer based on the current write position (mDataPos + len) instead of mDataSize + len, and NotificationHistory's Parcel constructor rejects a positionInParcel value larger than the reconstructed parcel's data size before calling setDataPosition.",
     "component": "platform/frameworks/native libbinder (libs/binder/Parcel.cpp) and platform/frameworks/base android.app.NotificationHistory (Parcelable unparceling)",
     "weakness": "missing bounds check (incorrect capacity check in Parcel::appendFrom; unvalidated position read from parcel in NotificationHistory)",
     "root_cause": "Parcel::appendFrom copies data starting at mDataPos (startPos = mDataPos, per index search), but the growth check before the copy compared mDataSize + len against mDataCapacity. When mDataPos had been moved beyond mDataSize (possible through setDataPosition, as the new unit test does), the check could pass without growing the buffer, and the copy could land past mDataCapacity. On the Java side, NotificationHistory(Parcel in) read positionInParcel from the parcel contents and passed it straight to mParcel.setDataPosition() without comparing it to the data just appended, so a position beyond the data size became the parcel's position.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::appendFrom (frameworks/native dc54178de228): the growth condition changes from `(mDataSize+len) > mDataCapacity` to `(mDataPos + len) > mDataCapacity`, so growData(len) is called whenever a write starting at the current position would exceed capacity. The new test AppendWithBadDataPos (capacity 8, position 10000, append 8 bytes) expects appendFrom to return android::BAD_VALUE.",
      "core/java/android/app/NotificationHistory.java:NotificationHistory(Parcel in) (frameworks/base b1b3c20edf73; the same change also appears as 3f5375db991b in android-security-15.0.0_r15 with identical content): after appendFrom and setDataSize(mParcel.dataPosition()), the constructor now throws IllegalStateException(\"Obtained an invalid position value(...) from Parcel.\") if positionInParcel > mParcel.dataSize(). This happens before mParcel.setDataPosition(positionInParcel) is called."
     ],
     "completeness": [
      "Parcel::appendFrom: the fix covers the destination write position. The diff does not change how the source-side offset/len is validated; that code is not fully shown in the material, so its state is unknown.",
      "NotificationHistory: only positionInParcel is checked against dataSize. This class still passes listByteLength to appendFrom without validating it. The new check (int compared with `>` only) does not reject a negative positionInParcel; the material does not show whether native setDataPosition rejects negative values.",
      "The base-side check uses `>`, so positionInParcel == dataSize is accepted (position at the end of the data)."
     ],
     "behavior_changes": [
      "Parcel::appendFrom now grows the buffer, or fails with an error (BAD_VALUE per the new unit test), when mDataPos + len exceeds capacity, including when mDataPos is beyond mDataSize. Before the fix it would continue without growing.",
      "NotificationHistory(Parcel) now throws IllegalStateException when the position value is larger than the appended data size, instead of silently setting that position.",
      "New unit test Parcel.AppendWithBadDataPos in binderParcelUnitTest.cpp."
     ],
     "preconditions": [
      "A NotificationHistory parcel with attacker-controlled contents (mHistoryCount > 0, crafted positionInParcel / listByteLength) must be unparceled by the NotificationHistory(Parcel in) constructor.",
      "For the libbinder flaw: a Parcel whose mDataPos has been moved past mDataSize must then receive appendFrom. Per index search, appendFrom is reachable from Java via android_os_Parcel_appendFrom (frameworks/base/core/jni/android_os_Parcel.cpp) and from the NDK via AParcel_appendFrom (frameworks/native/libs/binder/ndk/parcel.cpp)."
     ],
     "per_major": {
      "14": "same fix for the libbinder part: platform/frameworks/native 4e28321ba198 in android-security-14.0.0_r25. It makes the identical one-line change to Parcel::appendFrom and adds the AppendWithBadDataPos test (inserted after AppendOverObject instead of AppendPlainDataPartial). The material shows no frameworks/base NotificationHistory commit for 14. At android-security-14.0.0_r29, 5/6 lines of the patched region are present.",
      "15": "both parts: platform/frameworks/native dc54178de228 and platform/frameworks/base b1b3c20edf73 in android-security-15.0.0_r13. The base change also appears as 3f5375db991b in android-security-15.0.0_r15 with identical content. At android-security-15.0.0_r17, 5/6 lines of the native patched region are present, and the code context shows the NotificationHistory check.",
      "16": "same fix for the NotificationHistory part: platform/frameworks/base 283bef4b2546 in android-security-16.0.0_r4, identical diff. The material shows no frameworks/native commit for 16. At android-security-16.0.0_r8, 4/4 lines of the patched region are present.",
      "17": "not affected (not listed by the bulletin); at android-security-17.0.0_r1, 4/4 lines of the patched region are present."
     },
     "backport_notes": [
      "The libbinder change is a one-line condition swap in Parcel::appendFrom. The base change is a four-line insertion in NotificationHistory(Parcel in) directly after mParcel.setDataSize(mParcel.dataPosition()). The Android 14 native hunk sits at a different line offset (498 vs 542), but the surrounding diff context is the same.",
      "On the latest 14 and 15 tags, 5/6 lines of the native patched region are present. Check directly in Parcel.cpp that the `(mDataPos + len) > mDataCapacity` condition is there."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the frameworks/base NotificationHistory fix was applied to Android 14, and whether the frameworks/native appendFrom fix was applied to Android 16 (the material shows only one commit per branch for those majors).",
      "Which of the 6 patched-region lines is missing at android-security-14.0.0_r29 and android-security-15.0.0_r17.",
      "Which process or IPC path was the concrete EoP vector (the bulletin only states EoP, High).",
      "Where inside appendFrom/growData the BAD_VALUE comes from, and how native setDataPosition behaves for negative or out-of-range values (not shown in the material).",
      "How NotificationHistory reads behave after construction when the position equals dataSize."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48544": {
     "cve": "CVE-2025-48544",
     "summary": "Removes the MediaProvider Bundle extra INCLUDED_DEFAULT_DIRECTORIES, which AccessChecker honored when building access SQL, and replaces it with an explicit Optional<List<String>> parameter that only the internal FUSE directory-rename path populates.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider / AccessChecker access-control SQL generation",
     "weakness": "Improper access control: an access-control decision was driven by a Bundle extra meant to be internal-only, which per the commit message could be set by callers in the bundle",
     "root_cause": "AccessChecker.getWhereForDefaultDirectoryMatch read the string list under MediaProvider.INCLUDED_DEFAULT_DIRECTORIES (\"android:included-default-directories\") directly from the extras Bundle passed to getWhereForConstrainedAccess. It then appended \"relative_path LIKE '<dir>/%'\" clauses to the OR-list of rows the caller may access (the test shows this for FILES). The extra was meant to be set only internally: the FUSE directory rename loop set it with getIncludedDefaultDirectories() for the System Gallery case. MediaProvider guarded against outside use by calling remove(INCLUDED_DEFAULT_DIRECTORIES) in query(), insert(), delete() and update(). Because the grant came from the same Bundle that callers supply, it could be misused, as the commit message says the extra \"can be misused by setting it in the bundle\".",
     "fix_analysis": [
      "src/com/android/providers/media/AccessChecker.java:getWhereForConstrainedAccess now takes Optional<List<String>> includedDefaultDirectoriesOptional instead of a Bundle, and builds the default-directory clause only when the Optional is present. getWhereForDefaultDirectoryMatch now takes the List<String> directly and no longer reads any Bundle key. The imports of INCLUDED_DEFAULT_DIRECTORIES and android.os.Bundle are removed.",
      "src/com/android/providers/media/MediaProvider.java: the INCLUDED_DEFAULT_DIRECTORIES constant is deleted. The four remove(INCLUDED_DEFAULT_DIRECTORIES) calls in query(), insert(), delete() and update() are removed.",
      "src/com/android/providers/media/MediaProvider.java: getQueryBuilder, getQueryBuilderInternal and appendAccessCheckQuery gain an Optional<List<String>> includedDefaultDirectoriesOptional parameter, which is passed down to both getWhereForConstrainedAccess calls in appendAccessCheckQuery. The extras Bundle is still passed but is no longer consulted for directory grants.",
      "src/com/android/providers/media/MediaProvider.java: updateDatabaseForFuseRename (the overloads taking qbExtras) and deleteForFuseRename gain the same Optional parameter and forward it to getQueryBuilder. The directory rename loop no longer builds a qbExtras Bundle containing the key. It now passes new Bundle() plus Optional.of(getIncludedDefaultDirectories()).",
      "src/com/android/providers/media/MediaProvider.java: every other call site of getQueryBuilder, getWhereForConstrainedAccess and updateDatabaseForFuseRename passes Optional.empty(). These include getWhereClauseForMatchableVisibleFromFilePath, the owner-package clearing path, the oldPathGrantedUri rename, query(), insert(), delete() including the uriGranted branch, update(), the replace delete, playlist member removal, the clearLocalCallingIdentity wrapper, and the direct write/read access probes.",
      "tests/src/com/android/providers/media/AccessCheckerTest.java: existing tests are updated to the new signature. A new assertion checks that Optional.of(DCIM, Pictures, Movies) for FILES produces the three RELATIVE_PATH LIKE clauses after the owner SQL and the files-access SQL."
     ],
     "completeness": [
      "The fix removes the mechanism rather than adding a blocklist: the Bundle key is deleted, so a caller-supplied Bundle can no longer trigger the directory grant. Only the FUSE directory rename loop passes a non-empty Optional, and it takes the list from getIncludedDefaultDirectories().",
      "The old defensive remove() calls in query/insert/delete/update are dropped. This is consistent with the fix because AccessChecker no longer reads the key.",
      "getWhereForDefaultDirectoryMatch still builds SQL by concatenating directory names, but its only input is now the internal getIncludedDefaultDirectories() result.",
      "The stale Javadoc '@see MediaProvider#INCLUDED_DEFAULT_DIRECTORIES' on getWhereForDefaultDirectoryMatch is left in place (documentation only).",
      "updateDatabaseForFuseRename and deleteForFuseRename still accept a qbExtras Bundle and forward it to getQueryBuilder for other extras. The fix does not change this."
     ],
     "behavior_changes": [
      "A caller-supplied Bundle containing the key android:included-default-directories no longer affects access-check SQL.",
      "The directory-rename grant is kept: the rename loop still supplies getIncludedDefaultDirectories(), now through the Optional parameter.",
      "Internal method signatures change (an extra parameter) for getQueryBuilder, getQueryBuilderInternal, appendAccessCheckQuery, updateDatabaseForFuseRename, deleteForFuseRename and AccessChecker.getWhereForConstrainedAccess. No public API changes."
     ],
     "preconditions": [
      "The caller is a local app that can reach a MediaProvider operation whose Bundle is forwarded into getQueryBuilder, and from there to appendAccessCheckQuery / getWhereForConstrainedAccess. The material does not identify the specific entry point.",
      "The directory clause is only appended in the constrained-access branch of getWhereForConstrainedAccess (shown for FILES in the test), which applies to callers without full collection access."
     ],
     "per_major": {
      "14": "Adapted fix (f79b709905cf in android-security-14.0.0_r22). Same change, but appendAccessCheckQuery in 14 has one getWhereForConstrainedAccess call instead of two, and line offsets differ. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "Analysed fix c34a7f642548 in android-security-15.0.0_r10 (cherry-pick of a631dca1a8c0). At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "Same fix (a5f1f1827339 in android-security-16.0.0_r1), with different line offsets. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "Not affected per the bulletin (not listed). At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit, mostly mechanical: delete the constant; change the AccessChecker signatures; add the Optional parameter through getQueryBuilder -> getQueryBuilderInternal -> appendAccessCheckQuery and the FUSE rename helpers; pass Optional.empty() at every other call site. Where appendAccessCheckQuery has only one getWhereForConstrainedAccess call (as in 14), only that call needs updating. Any out-of-tree caller of getQueryBuilder or getWhereForConstrainedAccess must be updated to compile."
     ],
     "confidence": "high",
     "unknowns": [
      "Which MediaProvider entry point let a caller Bundle carrying INCLUDED_DEFAULT_DIRECTORIES reach AccessChecker.",
      "Which case labels of getWhereForConstrainedAccess contain the default-directory clause (the test shows FILES).",
      "Whether the grant applied to reads as well as writes in practice. The shown hunk does not condition it on forWrite.",
      "Whether the fix is also delivered through a MediaProvider module update in addition to the platform security tags.",
      "Why one of the 8 lines of the patched region is not found at the latest security tags."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48567": {
     "cve": "CVE-2025-48567",
     "summary": "MediaProvider now NFD-normalizes paths and strips Unicode default-ignorable code points (e.g. zero-width space) before the regex-based Android/data and Android/obb private-directory access checks, via a new FileUtils.normalizeAndFilterDefaultIgnorableCodepoints applied in isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse.",
     "component": "platform/packages/providers/MediaProvider, FUSE path access checks (MediaProvider.java, util/FileUtils.java)",
     "weakness": "Improper input canonicalization before a path-based access check (regex matched on a non-canonical path; filesystem ignores the extra characters)",
     "root_cause": "The private-app-directory protections in MediaProvider (isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse, which feed extractPathOwnerPackageName / isExternalMediaDirectory / isCallingIdentityAllowedAccessToDataOrObbPath via extractRelativePathWithDisplayName) ran their regex checks on the raw path string. Per the commit message, the lower filesystem ignores certain characters when saving files, so a path containing such characters (the test uses U+200B inside 'Android') can refer to an Android/data or Android/obb location on disk while the regex checks do not recognise it as a private data/obb path.",
     "fix_analysis": [
      "util/FileUtils.java: adds public static normalizeAndFilterDefaultIgnorableCodepoints(String path). Returns null/empty unchanged; otherwise applies java.text.Normalizer NFD, scans code points with android.icu.lang.UCharacter.hasBinaryProperty(cp, UProperty.DEFAULT_IGNORABLE_CODE_POINT); if none found returns the NFD string, else rebuilds the string without the default-ignorable code points.",
      "MediaProvider.java:isUidAllowedAccessToDataOrObbPathForFuse (public, @Keep, presumably called from native FUSE code) now normalizes the path with FileUtils.normalizeAndFilterDefaultIgnorableCodepoints and delegates to a new private isUidAllowedAccessToDataOrObbPath(uid, path), which contains the previous body (clearLocalCallingIdentity / isCallingIdentityAllowedAccessToDataOrObbPath(extractRelativePathWithDisplayName(path)) / restore).",
      "MediaProvider.java:isPrivatePackagePathNotAccessibleByCaller now reassigns path = FileUtils.normalizeAndFilterDefaultIgnorableCodepoints(path) as its first statement, so extractPathOwnerPackageName, isExternalMediaDirectory and the final call see the sanitized path; the final call is now to the private isUidAllowedAccessToDataOrObbPath, so the already sanitized path is not normalized again.",
      "MediaProvider.java:shouldBypassFuseRestrictions switched from isUidAllowedAccessToDataOrObbPathForFuse to the private isUidAllowedAccessToDataOrObbPath, passing filePath unnormalized (see completeness).",
      "util/FileUtils.java:extractRelativePath: 'final String path' changed to 'String path' with no other change in that function (no functional effect in this combined CL).",
      "tests/FileUtilsTest.java: adds testNormalizeAndFilterDefaultIgnorableCodepoints checking that U+200B inside 'Android' and U+FE0F in an emoji sequence are removed."
     ],
     "completeness": [
      "Covers the two MediaProvider entry points that decide private data/obb directory access: isPrivatePackagePathNotAccessibleByCaller and the public @Keep isUidAllowedAccessToDataOrObbPathForFuse.",
      "shouldBypassFuseRestrictions now calls the private isUidAllowedAccessToDataOrObbPath with the raw filePath, so no sanitization happens at that call site (before the fix it called the ForFuse variant, which also did no sanitization). The material does not show how an unsanitized path is evaluated there.",
      "Sanitization is applied only to these checks; other regex-based path helpers in FileUtils (extractRelativePath, extractPathOwnerPackageName, isDataOrObbPath, isDataOrObbRelativePath, isExternalMediaDirectory) are unchanged by the diff. The commit message's listed tests testIsDataOrObbRelativePathWithZwsChars_flagEnabled/_flagDisabled are not present in this diff, suggesting the original CLs touched more than this combined cherry-pick carries.",
      "Only DEFAULT_IGNORABLE_CODE_POINT characters are removed after NFD normalization; whether other filesystem-level name folding exists is not shown in the material.",
      "On Android 16 the entire sanitizer is gated behind Flags.enablePathSanitization() (is_fixed_read_only); if the build has the flag off, normalizeAndFilterDefaultIgnorableCodepoints returns the input unchanged and the fix is inert. Android 14 and 15 are unconditional."
     ],
     "behavior_changes": [
      "Private-directory access decisions in the two patched checks are now made on an NFD-normalized string with default-ignorable code points removed, so paths that differ only by such characters are treated as the canonical Android/data or Android/obb path.",
      "NFD normalization is applied to every non-empty path passing through these checks, including ones without ignorable characters (the NFD result is returned even when no ignorable code point is found), which may change matching for precomposed vs decomposed characters in the checked path.",
      "Small added per-check CPU cost (normalization and ICU property lookup per code point)."
     ],
     "preconditions": [
      "An app accesses external storage through paths reaching isUidAllowedAccessToDataOrObbPathForFuse or isPrivatePackagePathNotAccessibleByCaller using a path containing Unicode default-ignorable code points that the lower filesystem ignores.",
      "The caller does not otherwise have access to the target package's Android/data or Android/obb directory (not the owner, no legacy-storage or other bypass in shouldBypassFuseRestrictions).",
      "Per the diff, shouldBypassFuseRestrictions and isPrivatePackagePathNotAccessibleByCaller called isUidAllowedAccessToDataOrObbPathForFuse; per index search nothing outside MediaProvider uses these functions; the native FUSE caller is not shown in the material.",
      "On Android 16 the enable_path_sanitization fixed-read-only flag must be on in the build for the fix to be active."
     ],
     "per_major": {
      "14": "same fix (e599e8d934f2, android-security-14.0.0_r25, udc-staging): identical code change, no aconfig flag, unconditional sanitizer; test lacks the @EnableFlags annotation. Region 6/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix (2f45f53ce92c, android-security-15.0.0_r13, 24Q3-staging): unconditional sanitizer with no Flags check in FileUtils, although the commit message cites flag enable_path_sanitization and the added test carries @EnableFlags(Flags.FLAG_ENABLE_PATH_SANITIZATION) without the corresponding imports or a SetFlagsRule in the diff. Region 6/8 lines present at android-security-15.0.0_r17.",
      "16": "adapted fix (237fb89f5325, android-security-16.0.0_r4, 25Q2-staging): adds flag enable_path_sanitization (is_fixed_read_only, bug 377888957) to mediaprovider_flags.aconfig and gates normalizeAndFilterDefaultIgnorableCodepoints on Flags.enablePathSanitization(); in shouldBypassFuseRestrictions the existing local isUidAllowedAccessToDataOrObbPath (used for owned-photos logic) now takes the private method's result; test uses SetFlagsRule + @EnableFlags. Region 7/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region is present (6/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single combined commit per branch, depending on android.icu (UCharacter/UProperty) and java.text.Normalizer. For 14/15 take the unflagged variant; for 16 the aconfig flag must exist and be enabled in the build or the sanitizer is a no-op. If the target tree's shouldBypassFuseRestrictions differs (16 stores the result in a local), only the method name swap to the private variant needs adapting.",
      "The Android 15 test diff references @EnableFlags/Flags without visible imports or a SetFlagsRule; when backporting, either drop the annotation (as the 14 commit did) or add the flag plumbing from the 16 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "Which lower filesystem behavior drops default-ignorable characters and the exact set of affected code points; the material only states that the lower filesystem ignores certain characters.",
      "The native FUSE call site(s) of isUidAllowedAccessToDataOrObbPathForFuse and the full set of operations whose access decisions pass through the two patched functions.",
      "How isCallingIdentityAllowedAccessToDataOrObbPath evaluates an unsanitized path in shouldBypassFuseRestrictions (whether it fails restrictive or permissive).",
      "Whether the enable_path_sanitization flag is enabled in shipping Android 16 builds (the aconfig entry defines the flag but the diff shows no value).",
      "Contents of the original CLs ag/33577707 and ag/33920560 referenced by the message, including the testIsDataOrObbRelativePathWithZwsChars tests that are not in this combined diff."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48568": {
     "cve": "CVE-2025-48568",
     "summary": "Reworks user switching so that, when switching to a secure user, system_server asks SystemUI to lock via lockNow with an IRemoteCallback and blocks until SystemUI signals that callback (up to 20 s). SystemUI cancels pending dismisses during the switch and rejects a keyguard exit animation that was requested for a different user.",
     "component": "platform/frameworks/base: ActivityManager UserController, PhoneWindowManager KeyguardServiceDelegate, SystemUI KeyguardViewMediator / UserTracker",
     "weakness": "Race condition / improper state synchronization during user switch (keyguard could be dismissed or go away for the wrong user)",
     "root_cause": "Before the fix, UserController.Injector.lockDeviceNowAndWaitForKeyguardShown returned at once if WindowManager already reported the keyguard as locked. Otherwise it called lockDeviceNow and waited for any ActivityTaskManagerInternal.ScreenObserver.onKeyguardStateChanged(true) event, which says nothing about the incoming user. In SystemUI, the KeyguardUpdateMonitorCallback.onUserSwitching handler in KeyguardViewMediator called dismiss() unconditionally, and onUserSwitchComplete called dismiss() again after 500 ms. Pending DISMISS messages and an in-flight keyguardGoingAway request from the previous user were not cancelled. handleStartKeyguardExitAnimation also did not check that the going-away request belonged to the current user. A keyguard dismissal or exit started for the old user could therefore complete after the switch to a secure user. In addition, KeyguardServiceDelegate.doKeyguardTimeout silently dropped the request if the keyguard service was not yet bound.",
     "fix_analysis": [
      "KeyguardManager: adds the hidden constant LOCK_ON_USER_SWITCH_CALLBACK (\"onSwitchCallback\").",
      "UserController.Injector.lockDeviceNowAndWaitForKeyguardShown: drops the isKeyguardLocked() early return and the ScreenObserver. It now puts an IRemoteCallback.Stub binder into a Bundle under LOCK_ON_USER_SWITCH_CALLBACK, calls getWindowManager().lockNow(bundle), and waits up to 20 s for sendResult. On timeout it throws a RuntimeException. The getActivityTaskManagerInternal() injector method is removed.",
      "KeyguardServiceDelegate.doKeyguardTimeout: if mKeyguardService is null, the request and its options are saved in KeyguardState (doKeyguardTimeoutRequested / doKeyguardTimeoutRequestedOptions) and replayed in onServiceConnected.",
      "KeyguardViewMediator.doKeyguardLocked: when the options carry LOCK_ON_USER_SWITCH_CALLBACK, a LockNowCallback is recorded for the selected user. notifyLockNowCallback() is called on every early-return path (externally disabled, already showing and reset, not provisioned, lockscreen disabled), in handleShow when the system is not ready, and in updateActivityLockScreenState(showing=true). notifyLockNowCallback only signals callbacks whose user id matches the selected user.",
      "KeyguardViewMediator: the old KeyguardUpdateMonitorCallback onUserSwitching/onUserSwitchComplete handlers are replaced by a UserTracker.Callback (mUserChangedCallback) that posts BEFORE_USER_SWITCHING, USER_SWITCHING and USER_SWITCH_COMPLETE messages. handleBeforeUserSwitching removes pending DISMISS messages, notifies trust state, resets keyguardDonePending, adjusts the status bar, calls notifyKeyguardGoingAway(false), and calls doKeyguardLocked if the new user is secure and the keyguard is not showing (otherwise resetStateLocked). handleUserSwitching calls dismiss only for an insecure user. handleUserSwitchComplete calls dismiss with a 500 ms delay only for a secure user, which shows the bouncer.",
      "KeyguardViewMediator.start: if UserTracker.isUserSwitching() is already true, handleBeforeUserSwitching and handleUserSwitching are called manually. isUserSwitching is a new flag in UserTracker/UserTrackerImpl, set in onUserSwitching and cleared in onUserSwitchComplete. The system-ready path does the same check and calls mUserChangedCallback.onUserChanging only.",
      "KeyguardViewMediator.dismiss: ignored while mKeyguardStateController.isKeyguardGoingAway().",
      "KeyguardViewMediator: mKeyguardGoingAwayRunnable and showSurfaceBehindKeyguard record mGoingAwayRequestedForUserId. If that id differs from the selected user, handleStartKeyguardExitAnimation finishes the remote animation without hiding the keyguard and re-locks (secure user) or resets and dismisses (insecure user). On the same mismatch, handleCancelKeyguardExitAnimation calls setPendingLock(true). These checks only apply when KeyguardWmStateRefactor is disabled."
     ],
     "completeness": [
      "The user-id checks in handleStartKeyguardExitAnimation, handleCancelKeyguardExitAnimation and the recording of mGoingAwayRequestedForUserId are skipped when KeyguardWmStateRefactor.isEnabled(). In that case going-away is handled in WmLockscreenVisibilityManager, which this diff does not touch.",
      "On the system_server side, the lock handshake covers only lockDeviceNowAndWaitForKeyguardShown. The material does not show which code paths call it.",
      "notifyLockNowCallback clears the whole callback list and discards callbacks whose user does not match the selected user without signalling them. That system_server wait can then end only through the 20 s timeout."
     ],
     "behavior_changes": [
      "If SystemUI does not deliver the LOCK_ON_USER_SWITCH_CALLBACK result within 20 seconds, system_server throws a RuntimeException. Per the commit message this crashes the system.",
      "When the keyguard is already locked, the user switch now still performs a lockNow round trip (the early return is gone).",
      "Switching to an insecure user dismisses the keyguard during USER_SWITCHING. Switching to a secure user no longer dismisses during the switch and shows the bouncer about 500 ms after completion.",
      "dismiss() calls are dropped while the keyguard is going away. A keyguard exit for a stale user is aborted and the keyguard is re-shown.",
      "doKeyguardTimeout requests made before KeyguardService connects are queued instead of dropped.",
      "UserControllerTest.testContinueUserSwitchDismissKeyguard is removed, and the dismissKeyguard expectation in the related test is dropped."
     ],
     "preconditions": [
      "A multi-user device with at least one user that has a secure lock screen (based on the diff).",
      "A foreground user switch, with a keyguard dismissal or keyguardGoingAway in flight around the switch (based on the diff and the added tests)."
     ],
     "per_major": {
      "14": "Adapted fix: ec3f9fa9145c in android-security-14.0.0_r24. In SystemUI it adds the same LOCK_ON_USER_SWITCH_CALLBACK and LockNowCallback mechanism and the mGoingAwayRequestedForUserId checks, but performs the switch-time work in KeyguardViewMediator.setCurrentUser (calling setLockScreenShown(true) directly for secure users) and uses a mDismissToken. Only USER_SWITCH_COMPLETE is added, and it dismisses only insecure users. It also adds mIsKeyguardExitAnimationCanceled and changes KeyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(showKeyguard). 6/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Fixed by bec2efb2d714 in android-security-15.0.0_r12 (cherry-picked from 026ee0c15ca9), using UserTracker callbacks for before, during and after the switch. All 8/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin (not listed). The patched region is present (8/8 lines) at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This fix spans system_server (UserController, KeyguardServiceDelegate) and SystemUI (KeyguardViewMediator, UserTracker/UserTrackerImpl), so both sides should ship together. Inferred from the diff: an unpatched SystemUI does not read the LOCK_ON_USER_SWITCH_CALLBACK option, so the server-side 20 s wait would time out and throw. Forks that customize UserTracker implementations or fakes must also implement the new isUserSwitching property.",
      "The Android 14 port is structurally different (setCurrentUser-based, KeyguardUpdateMonitor.getCurrentUser instead of SelectedUserInteractor, extra KeyguardUnlockAnimationController changes). Use the branch-specific commit rather than cherry-picking the Android 15 change."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact attack trigger and the security impact behind the EoP rating are not described in the material.",
      "The material does not show which UserController paths call lockDeviceNowAndWaitForKeyguardShown, or under what conditions.",
      "The material does not show whether devices with KeyguardWmStateRefactor enabled get equivalent protection in WmLockscreenVisibilityManager.",
      "The remaining 370 lines of the Android 14 diff (likely tests and the server-side changes) are not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-opus-5-5",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48574": {
     "cve": "CVE-2025-48574",
     "summary": "DisplayPolicy.validateAddingWindowLw now enforces MANAGE_ACTIVITY_TASKS against the explicitly passed callingPid/callingUid when a window requests PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, instead of calling ActivityTaskManagerService.enforceTaskPermission(), which reads the Binder calling identity that has already been cleared on this path.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/DisplayPolicy.java)",
     "weakness": "Ineffective permission check (permission enforced against the wrong identity because the Binder calling identity had been cleared)",
     "root_cause": "In DisplayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid), the check guarding PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP called the static ActivityTaskManagerService.enforceTaskPermission(\"DisplayPolicy\"). Per the commit message, that helper uses the Binder calling uid/pid, but the Binder calling identity has been cleared before validateAddingWindowLw is invoked, so the check did not evaluate the requesting client's identity. The sibling checks in the same function (PRIVATE_FLAG_TRUSTED_OVERLAY, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_NAVIGATION_BAR) already used the explicitly passed callingPid/callingUid via mContext.enforcePermission; the drag-and-drop flag check was the one inconsistent with that pattern.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/DisplayPolicy.java:validateAddingWindowLw: replaces ActivityTaskManagerService.enforceTaskPermission(\"DisplayPolicy\") with mContext.enforcePermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS, callingPid, callingUid, \"DisplayPolicy\"), so the permission is evaluated for the pid/uid that the caller of validateAddingWindowLw supplied, matching the pattern used by the adjacent PRIVATE_FLAG_TRUSTED_OVERLAY and status-bar type checks.",
      "services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java:testPrivateInterceptGlobalDragDropFlagChecksPermission: updated to verify mWm.mContext.enforcePermission(eq(MANAGE_ACTIVITY_TASKS), anyInt(), anyInt(), any()) instead of mWm.mAtmService.enforceTaskPermission(any())."
     ],
     "completeness": [
      "Covers the single PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP branch in validateAddingWindowLw; after the fix all permission checks visible in that function (TRUSTED_OVERLAY, INTERCEPT_GLOBAL_DRAG_AND_DROP, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_NAVIGATION_BAR) use the explicit callingPid/callingUid.",
      "The fix does not change where or how the Binder identity is cleared before validateAddingWindowLw; it only makes this check independent of the Binder identity. Other uses of ActivityTaskManagerService.enforceTaskPermission() elsewhere in the WM code are not shown in the material and are not touched by this commit.",
      "The fix does not change how the flag is consumed later (e.g. drag dispatch in DragDropController); it only tightens the gate at window-add time."
     ],
     "behavior_changes": [
      "Adding a window with PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP now throws a SecurityException (from Context.enforcePermission) unless the supplied callingPid/callingUid holds android.permission.MANAGE_ACTIVITY_TASKS.",
      "The check is now specifically MANAGE_ACTIVITY_TASKS via mContext.enforcePermission rather than whatever ActivityTaskManagerService.enforceTaskPermission() accepted; the material does not show that helper's implementation, so any difference in accepted permissions is unknown.",
      "Test expectation changed from verifying mAtmService.enforceTaskPermission to verifying mContext.enforcePermission with MANAGE_ACTIVITY_TASKS."
     ],
     "preconditions": [
      "A client supplies WindowManager.LayoutParams with PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP set in privateFlags on a window-add request that reaches DisplayPolicy.validateAddingWindowLw.",
      "Per the commit message, the Binder calling identity has been cleared before validateAddingWindowLw is called, which is what made the old enforceTaskPermission() check ineffective; the caller that clears it is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 04b124bf59a8 in android-security-14.0.0_r24, identical DisplayPolicy hunk and test change (different line offsets); 5/7 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base e6e8c87c3c99 in android-security-15.0.0_r12 (cherry-pick of 467b18585de4); 5/7 lines of the patched region reported present at android-security-15.0.0_r17, and the code context there shows the fixed DisplayPolicy check and updated test.",
      "16": "same fix: platform/frameworks/base 1cfd8237b5a8 in android-security-16.0.0_r3, identical hunk; only surrounding context differs (followed by 'final String systemUiPermission =' instead of 'switch (attrs.type)'); 5/7 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); 5/7 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit, two-file change; the DisplayPolicy hunk is a 1-line to 3-line replacement that applied identically on 14, 15 and 16 (only offsets and trailing context differ), assuming validateAddingWindowLw takes callingPid/callingUid and has the PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP check."
     ],
     "confidence": "high",
     "unknowns": [
      "The call site of validateAddingWindowLw and the point where the Binder calling identity is cleared are not shown in the material (only asserted by the commit message).",
      "The implementation of ActivityTaskManagerService.enforceTaskPermission() and the exact set of permissions it accepted are not shown, so whether the new MANAGE_ACTIVITY_TASKS-only check is stricter for legitimate privileged callers is unknown.",
      "What capabilities PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP grants to a window at drag time is not shown beyond the test name, so the concrete EoP impact is only known from the bulletin rating.",
      "Which 2 of the 7 patched-region lines are missing at the latest 14/16/17 tags is not shown.",
      "Whether DragDropControllerTests already imports the Mockito matchers used by the updated test on a given branch is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48577": {
     "cve": "CVE-2025-48577",
     "summary": "KeyguardViewMediator now records which user a pending keyguard-done belongs to and checks the current user again at four points: biometric-auth success, hide-animation completion, the keyguardGoingAway background runnable and the exit-animation mismatch path. If a user switch happened in between, it re-shows or resets the keyguard and cancels queued dismiss/hide work.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardViewMediator)",
     "weakness": "Race condition: the user identity is not checked again across asynchronous keyguard dismissal (TOCTOU on the current user during a user switch)",
     "root_cause": "In packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java, work already in flight in the keyguard dismissal state machine was not tied to a user. mKeyguardDonePending was a plain boolean. keyguardDonePending() checked targetUserId only on entry, then scheduled mHideAnimationFinishedRunnable. That Runnable had no user attached and called tryKeyguardDone() unconditionally once the pre-hide animation finished. onBiometricAuthenticated() did not check that userId matched the selected user before proceeding. The keyguard going-away lambda ran later on mUiBgExecutor and called mActivityTaskManagerService.keyguardGoingAway() without checking the current user again. The 500 ms delayed dismiss() post (for a secure userId) had no token, and handleBeforeUserSwitching() removed only DISMISS messages, not HIDE messages or that runnable. Per the commit message, if the user changed in the middle of this chain, the prior user's keyguard could be dismissed.",
     "fix_analysis": [
      "KeyguardViewMediator.java: mUpdateCallback.onBiometricAuthenticated() now returns early, with a warning log, when userId != mSelectedUserInteractor.getSelectedUserId(). A success for a non-current user is no longer reported to DevicePolicyManager and does not continue the unlock chain.",
      "KeyguardViewMediator.java: the boolean mKeyguardDonePending is replaced by int mKeyguardDonePendingForUser, with the sentinel NO_KEYGUARD_DONE_PENDING (-10000). keyguardDonePending(targetUserId) stores the target user. readyForKeyguardDone() reads and clears it, then calls tryKeyguardDone(user). hideWithAnimation(), resetKeyguardDonePendingLocked() and dump() now use the sentinel.",
      "KeyguardViewMediator.java: mHideAnimationFinishedRunnable is replaced by the inner class OnHideAnimationFinished(int userId). Its run() clears mHideAnimationRunning, then compares the captured userId with mSelectedUserInteractor.getSelectedUserId(). On a mismatch it logs, calls mShadeController.get().instantCollapseShade() and resetStateLocked(), and returns without calling tryKeyguardDone(). keyguardDonePending() and tryKeyguardDone(int userId) now pass a new OnHideAnimationFinished(user) to startPreHideAnimation(). keyguardDone(targetUserId) passes targetUserId to tryKeyguardDone(targetUserId).",
      "KeyguardViewMediator.java: in the keyguard going-away runnable, the mUiBgExecutor lambda now compares mGoingAwayRequestedForUserId with the current selected user. On a mismatch it logs, calls mUpdateMonitor.setKeyguardGoingAway(false) and mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false), and returns without calling mActivityTaskManagerService.keyguardGoingAway().",
      "KeyguardViewMediator.java: in the exit-animation user-mismatch branch for a secure currentUserId, doKeyguardLocked(null) is now followed by mKeyguardInteractor.showKeyguard(), mShadeController.get().instantCollapseShade() and setShowingLocked(true, true /* force */).",
      "KeyguardViewMediator.java: the 500 ms delayed dismiss() is now posted with the token mDismissToken. handleBeforeUserSwitching() now also calls mHandler.removeCallbacksAndMessages(mDismissToken) and mHandler.removeMessages(HIDE), alongside removeMessages(DISMISS) and resetKeyguardDonePendingLocked().",
      "KeyguardViewMediatorTest.java: adds four tests: biometricAuthSuccessForCurrentUserDoesProceed, biometricAuthSuccessForDifferentUserDoesNotProceed, testDonePendingExecutesOnHideAnimationFinished and testDonePendingExecutesOnHideAnimationFinishedButTerminatesWhenUserHasChanged."
     ],
     "completeness": [
      "Covers five paths: biometric success for a non-current user, the pre-hide-animation completion callback, the deferred keyguardGoingAway call on mUiBgExecutor, recovery on an exit-animation user mismatch, and cancellation of the delayed dismiss runnable and HIDE messages in handleBeforeUserSwitching.",
      "tryKeyguardDone(int userId) does not itself compare userId with the current user. When mKeyguardDonePendingForUser is unset and mHideAnimationRun && !mHideAnimationRunning, it calls handleKeyguardDone() directly. The material does not show whether handleKeyguardDone() checks the user itself. This path is guarded by the entry checks in keyguardDone()/keyguardDonePending() and the reset in handleBeforeUserSwitching().",
      "The Android 14 variant leaves out the mDismissToken cancellation, removeMessages(HIDE) in handleBeforeUserSwitching, and mKeyguardInteractor.showKeyguard(). The material does not show whether the matching delayed-dismiss code exists on 14.",
      "On Android 16, the forced re-show in the exit-animation mismatch path only runs when KeyguardWmStateRefactor is disabled (!KeyguardWmStateRefactor.isEnabled()). The flag-enabled path depends on code the material does not show."
     ],
     "behavior_changes": [
      "KeyguardViewMediator ignores biometric authentication results for any user other than the selected user and logs them at WARN. DevicePolicyManager.reportSuccessfulBiometricAttempt is not called for them.",
      "If the user changes between keyguardDonePending() and the end of the pre-hide animation, the shade is collapsed and keyguard state is reset instead of the keyguard being dismissed.",
      "If the user changed before the background runnable ran, the keyguardGoingAway call to ActivityTaskManager is skipped and the going-away state is cleared.",
      "On a user switch, the delayed dismiss() runnable and any queued HIDE messages are dropped. Before the fix, only DISMISS messages were.",
      "On an exit-animation user mismatch for a secure user, the keyguard is forcibly re-shown (setShowingLocked with force=true) and the shade is collapsed.",
      "Several logs that were gated on DEBUG are now unconditional Log.d lines that include user ids. The dumpsys field is renamed to mKeyguardDonePendingForUser."
     ],
     "preconditions": [
      "A multi-user device where a user switch starts while a keyguard dismissal for the previous user is still in flight: a biometric success, or keyguardDonePending()/keyguardDone() called from KeyguardSecurityContainerController.finish() (per index search).",
      "The race window described in the commit message: between the start of the previous user's dismiss chain and the completion of the user switch.",
      "The bulletin rates this as a High severity EoP. The material gives no further detail on the attacker's position."
     ],
     "per_major": {
      "14": "Adapted fix (commit 2acf4a1801c1, android-security-14.0.0_r24). It makes the same user-tracking and OnHideAnimationFinished changes, but differs from 15 in five ways: it uses KeyguardUpdateMonitor.getCurrentUser() instead of mSelectedUserInteractor; the signature is keyguardDonePending(boolean primaryAuth, int targetUserId); the existing code calls ActivityTaskManager.getService(); and it leaves out mDismissToken/removeMessages(HIDE) and mKeyguardInteractor.showKeyguard(). 7/8 patched-region lines are present at android-security-14.0.0_r29.",
      "15": "This commit (3b6cc0893907, android-security-15.0.0_r12). 8/8 patched-region lines are present at android-security-15.0.0_r17.",
      "16": "Adapted fix (commit 78760f098fab, android-security-16.0.0_r3). Same as 15 with two differences: the going-away lambda also contains the ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS/startKeyguardTransition branch, and the forced re-show in the exit-animation mismatch path runs only when KeyguardWmStateRefactor is disabled (!KeyguardWmStateRefactor.isEnabled()), with a reason string passed to setShowingLocked. 8/8 patched-region lines are present at android-security-16.0.0_r8.",
      "17": "Not affected: the bulletin does not list Android 17. The patched region is fully present (8/8) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single-commit fix, but it cannot be applied verbatim across branches. The user lookup differs (KeyguardUpdateMonitor.getCurrentUser() on 14, mSelectedUserInteractor.getSelectedUserId() on 15/16), and the keyguardDonePending callback signature differs on 14, so the hunks must be re-targeted.",
      "The Android 14 variant leaves out the mDismissToken/HIDE cancellation hunks. Forks on 14-based trees should check whether the delayed dismiss() post exists there and consider carrying those hunks too."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether handleKeyguardDone() checks the current user on its own (not shown).",
      "Which function contains the delayed dismiss() post, and the field name of the going-away runnable (the hunks do not show them).",
      "Why the Android 14 backport left out the mDismissToken and removeMessages(HIDE) changes, and which 1 of 8 region lines differs at android-security-14.0.0_r29.",
      "Behavior on Android 16 when KeyguardWmStateRefactor is enabled, since the forced re-show is skipped there."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48578": {
     "cve": "CVE-2025-48578",
     "summary": "Adds missing restoreCallingIdentity / restoreLocalCallingIdentity calls on two exception paths in MediaProvider (getResultForGetMediaUri and updateInternal), so a thrown IllegalArgumentException or NumberFormatException no longer leaves the caller's identity cleared.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (ContentProvider call/update paths)",
     "weakness": "Improper handling of exceptional conditions: the cleared (privileged) calling identity is not restored when an exception is thrown after the identity was cleared",
     "root_cause": "In src/com/android/providers/media/MediaProvider.java, two code paths clear the calling identity and then run code that can throw with no restore on that exception path. In getResultForGetMediaUri, clearCallingIdentity() is called and then the documentUri authority is checked. An unsupported authority threw IllegalArgumentException without calling restoreCallingIdentity(token), and this check sits before the try/finally that restores the identity. In updateInternal, clearLocalCallingIdentity() is called and then ContentUris.parseId(uri) is evaluated outside any try block. A non-numeric id throws NumberFormatException and restoreLocalCallingIdentity(token) is never run. Per the commit message and diff, in both cases the identity stays cleared after the exception propagates.",
     "fix_analysis": [
      "MediaProvider.java:getResultForGetMediaUri: in the branch where the authority is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, restoreCallingIdentity(token) is now called just before throwing IllegalArgumentException(\"Provider for this Uri is not supported.\").",
      "MediaProvider.java:updateInternal: the computation of genericUri via MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)) is wrapped in try/catch (NumberFormatException e). The catch calls restoreLocalCallingIdentity(token) and rethrows the same exception. The try-with-resources block after it (queryForSingleItem ...) is unchanged.",
      "Single commit, one file. No new helpers, flags or tests (Test: EXEMPT bugfix, Flag: EXEMPT bugfix)."
     ],
     "completeness": [
      "Covers exactly the two exception paths named in the commit message: the unsupported authority in getResultForGetMediaUri and the NumberFormatException from ContentUris.parseId in updateInternal.",
      "In getResultForGetMediaUri, the code between clearCallingIdentity() and the new restore (documentUri.getAuthority() followed by authority.equals(...)) is still outside any try/finally. The code shows no null check on authority, so a NullPointerException could leave the identity cleared. Whether such a Uri can get past enforceCallingUriPermission is not shown (see potential_variants).",
      "The Android 14 commit (12fbdbc6beda) contains only the updateInternal hunk. The getResultForGetMediaUri hunk is not in that diff, and the material does not show whether the Android 14 version of that function has the same unprotected branch.",
      "The fix does not restructure the functions into a single clear/restore try/finally. It patches the specific throw sites."
     ],
     "behavior_changes": [
      "No change to return values or exception types: the same IllegalArgumentException and NumberFormatException are still thrown to the caller.",
      "After these exceptions, the calling identity (getResultForGetMediaUri) and MediaProvider's LocalCallingIdentity (updateInternal) are now restored to the caller's identity instead of staying cleared."
     ],
     "preconditions": [
      "getResultForGetMediaUri path: the function is reached with MediaStore.EXTRA_URI set to a Uri whose authority is neither MediaDocumentsProvider.AUTHORITY nor EXTERNAL_STORAGE_PROVIDER_AUTHORITY. The caller must first pass getContext().enforceCallingUriPermission(documentUri, FLAG_GRANT_READ_URI_PERMISSION) on that Uri (code context lines 7151-7153). The call() dispatch to this function is not shown.",
      "updateInternal path: an update on a MediaProvider Uri that reaches the clearLocalCallingIdentity() block and whose id cannot be parsed as a long, so ContentUris.parseId throws NumberFormatException.",
      "Which Uri match types reach the updateInternal block is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit 12fbdbc6beda in android-security-14.0.0_r25 contains only the updateInternal NumberFormatException hunk (6/6 lines present at android-security-14.0.0_r29). The getResultForGetMediaUri hunk is not in the Android 14 diff.",
      "15": "same fix: commit 2a8f3f27275c in android-security-15.0.0_r13, both hunks. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 7ea0a039ec32 in android-security-16.0.0_r4, both hunks (at shifted line numbers). 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. The patched code (8/8 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit that touches only MediaProvider.java. The hunks apply by context: the 'if (!authority.equals(MediaDocumentsProvider.AUTHORITY)' block and the 'final LocalCallingIdentity token = clearLocalCallingIdentity();' line in updateInternal. On a 14-based tree, check whether getResultForGetMediaUri has the unprotected throw and apply the first hunk if it does, because Google's Android 14 commit leaves that hunk out. MediaProvider is a mainline (APEX) module (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the Android 14 commit omits the getResultForGetMediaUri hunk (code absent in 14, or hunk deliberately dropped) is not shown.",
      "The material does not describe which privileged operation becomes reachable through the leaked cleared identity. The bulletin only rates it High EoP.",
      "Which update() Uri match cases lead into the patched updateInternal block is not shown.",
      "How call() dispatches to getResultForGetMediaUri is not shown.",
      "Where restoreLocalCallingIdentity normally runs in updateInternal (outside the patched catch) is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48579": {
     "cve": "CVE-2025-48579",
     "summary": "Restores the cleared Binder/local calling identity on two exception paths in MediaProvider (unsupported authority in getResultForGetMediaUri, NumberFormatException from ContentUris.parseId in updateInternal) so the identity is not left cleared after the exception propagates.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (ContentProvider update and call() method handlers)",
     "weakness": "Missing cleanup on exception path: calling identity cleared and not restored when an exception is thrown (improper privilege/identity management, CWE-459 style)",
     "root_cause": "In MediaProvider.java, two code paths clear the calling identity and then run code that can throw before the identity is restored. In getResultForGetMediaUri, clearCallingIdentity() is called and then the authority of the caller-supplied documentUri is checked. If the authority is neither MediaDocumentsProvider.AUTHORITY nor EXTERNAL_STORAGE_PROVIDER_AUTHORITY, an IllegalArgumentException is thrown without restoreCallingIdentity(token). In updateInternal, clearLocalCallingIdentity() is called and then MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)) is evaluated outside any try block. A non-numeric id segment makes parseId throw NumberFormatException, and per the commit message the calling identity is then not restored.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:getResultForGetMediaUri: adds restoreCallingIdentity(token) immediately before the throw of IllegalArgumentException(\"Provider for this Uri is not supported.\") in the unsupported-authority branch, so the Binder calling identity is restored on that early-exit path.",
      "src/com/android/providers/media/MediaProvider.java:updateInternal: moves the computation of genericUri (MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri))) into a try block. On NumberFormatException it calls restoreLocalCallingIdentity(token) and rethrows the same exception, so callers see the same exception but the local calling identity is restored.",
      "Single commit per branch. No new checks are added and the error types seen by callers are unchanged."
     ],
     "completeness": [
      "Covers exactly the two exception sites named in the commit message: the unsupported-authority throw in getResultForGetMediaUri and the NumberFormatException from ContentUris.parseId in updateInternal.",
      "In getResultForGetMediaUri (code context lines 7158-7165), documentUri.getAuthority() is evaluated after clearCallingIdentity() and then dereferenced with authority.equals(...). A null authority would raise a NullPointerException in that same unguarded segment, which this fix does not wrap. The material does not show whether such a URI can pass the earlier enforceCallingUriPermission check.",
      "The rest of getResultForGetMediaUri already used try/finally with restoreCallingIdentity (context lines 7167-7184). After the fix, the only unguarded code between clear and restore in that method is the getAuthority() call and the equals() comparisons. The rest of updateInternal is not shown in the material.",
      "The Android 14 commit contains only the updateInternal hunk. The getResultForGetMediaUri hunk is not in the 14 diff (per-major facts show a 6-line patched region on 14 versus 8 on 15/16). The material does not show whether the 14 code for getResultForGetMediaUri differs or is unpatched."
     ],
     "behavior_changes": [
      "No change in exceptions visible to callers: the same IllegalArgumentException and NumberFormatException are still thrown.",
      "On either failing path, the calling identity is now restored before the exception propagates."
     ],
     "preconditions": [
      "updateInternal path: an update request to MediaProvider that reaches the branch calling clearLocalCallingIdentity() with a URI whose last path segment cannot be parsed as a long, which triggers NumberFormatException in ContentUris.parseId. The exact branch condition before this block is not in the material.",
      "getResultForGetMediaUri path: a MediaStore call() method routed to getResultForGetMediaUri with an EXTRA_URI whose authority is neither MediaDocumentsProvider.AUTHORITY nor EXTERNAL_STORAGE_PROVIDER_AUTHORITY. The caller must first pass getContext().enforceCallingUriPermission(documentUri, FLAG_GRANT_READ_URI_PERMISSION), which runs before the identity is cleared."
     ],
     "per_major": {
      "14": "adapted fix: commit 12fbdbc6beda in android-security-14.0.0_r25 contains only the updateInternal hunk (try/catch NumberFormatException with restoreLocalCallingIdentity). The getResultForGetMediaUri hunk is not in the 14 diff. 6/6 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 2a8f3f27275c in android-security-15.0.0_r13, both hunks. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 7ea0a039ec32 in android-security-16.0.0_r4, both hunks at shifted line numbers. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change is self-contained (two hunks, no new symbols). When backporting to a tree based on Android 14, check whether getResultForGetMediaUri exists with the same unsupported-authority throw after clearCallingIdentity(), because the 14 commit did not include that hunk and the material does not explain why.",
      "MediaProvider ships as a mainline module in addition to the platform tree, so the fix may also need to land in the module branch used by the device (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the Android 14 commit omits the getResultForGetMediaUri hunk (whether that code differs on 14 or was left unpatched).",
      "The branch condition in updateInternal before clearLocalCallingIdentity(), and so which update URIs reach the affected block.",
      "Where updateInternal restores the local calling identity on its normal path (not shown in the material).",
      "Which MediaStore call() method names route to getResultForGetMediaUri (not shown in the material).",
      "Concrete consequences of the retained cleared identity for later requests on the same thread. The material only shows the identity leak itself."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48582": {
     "cve": "CVE-2025-48582",
     "summary": "MediaProvider now stores the calling uid (getCallingUidOrSelf()) of the app that makes a create bulk request in the PermissionActivity intent extras. PermissionActivity then uses that uid, instead of getCallingPackage(), to decide whether a consent dialog is needed and which package receives the URI permissions.",
     "component": "platform/packages/providers/MediaProvider, PendingIntent creation for PermissionActivity in MediaProvider and the PermissionActivity consent flow",
     "weakness": "Authorization based on the wrong caller identity: the consent and grant decisions used getCallingPackage() instead of the uid of the original requester",
     "root_cause": "MediaProvider builds a PendingIntent that targets PermissionActivity for create bulk requests. Before the fix, PermissionActivity.resolveCallingAppInfo() identified the requester through Activity.getCallingPackage(). That identity (appInfo.uid plus getCallingPackage()) was passed to shouldShowActionDialog() to decide whether the user must be prompted, and to grantUriPermission() to choose which package gets read/write URI grants on a positive action. The intent carried no record of the uid that made the MediaProvider call, so the checks and the grant were not tied to the original requester.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java (PendingIntent creation for PermissionActivity, hunk at line 7645 in the 15 tree): before intent.putExtras(extras), the change calls extras.putInt(EXTRA_CALLING_PACKAGE_UID, getCallingUidOrSelf()). It also adds a static import of android.provider.MediaStore.EXTRA_CALLING_PACKAGE_UID.",
      "src/com/android/providers/media/PermissionActivity.java:resolveCallingAppInfo(): when the intent has extras, the uid is read from EXTRA_CALLING_PACKAGE_UID. The package becomes Objects.requireNonNull(getPackagesForUid(callingUid))[0], and the user becomes getUserHandleForUid(callingUid). ApplicationInfo is then resolved with getApplicationInfoAsUser(callingPackage, 0, userHandle). getCallingPackage() and getUser() are used only when extras is null.",
      "src/com/android/providers/media/PermissionActivity.java, shouldShowActionDialog call sites (both overloads; the enclosing method is not shown): the package argument changes from getCallingPackage() to appInfo.packageName.",
      "src/com/android/providers/media/PermissionActivity.java, positive-action AsyncTask.doInBackground(): for CREATE_WRITE_REQUEST_CALL, grantUriPermission() now grants to appInfo.packageName. Metrics.logPermissionGranted() and Metrics.logPermissionDenied() (in the negative-action task) now log appInfo.packageName."
     ],
     "completeness": [
      "Every getCallingPackage() use visible in the diff is replaced, except the fallback inside resolveCallingAppInfo() when getIntent().getExtras() is null. That fallback still uses getCallingPackage() and getUser().",
      "The extra is set only at the one PendingIntent creation site changed in MediaProvider.java. If extras are present but EXTRA_CALLING_PACKAGE_UID is missing, Bundle.getInt returns 0. How the code behaves in that case is not shown.",
      "For shared-uid apps, only getPackagesForUid(uid)[0] is used, so checks and grants target the first package of that uid.",
      "The userHandle derived from the uid is used only for getApplicationInfoAsUser(). grantUriPermission() still receives only a package name."
     ],
     "behavior_changes": [
      "PermissionActivity intents created by MediaProvider now carry an int extra, EXTRA_CALLING_PACKAGE_UID ('calling_package_uid' per the Android 14 commit).",
      "The consent-dialog decision, the URI grant target and Metrics logging now use the uid that made the MediaProvider call.",
      "If extras are present and getPackagesForUid returns null, resolveCallingAppInfo() throws NullPointerException (from Objects.requireNonNull).",
      "ApplicationInfo is resolved for the user that owns the calling uid (getApplicationInfoAsUser), not via getApplicationInfo()."
     ],
     "preconditions": [
      "An app obtains a PendingIntent from MediaProvider through a create bulk request, and PermissionActivity is launched from that PendingIntent (the diff context shows the CREATE_WRITE_REQUEST_CALL case).",
      "The scenario the bulletin rates as High EoP is not described in the material.",
      "The positive-action path runs in PermissionActivity's AsyncTask.doInBackground(), which is invoked by the AsyncTask worker in frameworks/base/core/java/android/os/AsyncTask.java (per index search)."
     ],
     "per_major": {
      "14": "adapted fix (45db544a1f1a in android-security-14.0.0_r25). It makes the same MediaProvider and PermissionActivity changes and also adds the hidden constant EXTRA_CALLING_PACKAGE_UID = \"calling_package_uid\" to apex/framework/java/android/provider/MediaStore.java. The PendingIntent creation site has no ActivityOptions block. 8/8 lines are present at android-security-14.0.0_r29.",
      "15": "this fix (6b652db8a6d3 in android-security-15.0.0_r13). It touches only MediaProvider.java and PermissionActivity.java and only imports EXTRA_CALLING_PACKAGE_UID, which implies the constant already exists in this tree. 8/8 lines are present at android-security-15.0.0_r17.",
      "16": "same fix (1b7d4ce446d1 in android-security-16.0.0_r4), identical hunks at shifted line numbers. 8/8 lines are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. The code depends on MediaStore.EXTRA_CALLING_PACKAGE_UID existing in apex/framework/java/android/provider/MediaStore.java. Trees that lack it need the constant addition from the Android 14 commit (45db544a1f1a)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The name of the MediaProvider method that contains the PendingIntent creation hunk is not shown.",
      "The name of the PermissionActivity method that contains the shouldShowActionDialog call sites is not shown.",
      "Which package Activity.getCallingPackage() returned before the fix when the activity is started through the PendingIntent is not shown.",
      "The concrete High EoP scenario (which identities are confused and what is gained) is not described.",
      "Whether PermissionActivity can be started by any path other than the MediaProvider-created PendingIntent is not shown. This determines whether the extras==null fallback or the missing-extra (uid 0) path is reachable.",
      "What getPackagesForUid(0) returns, and so how the missing-extra path behaves, is not shown.",
      "Whether the extras Bundle at the MediaProvider PendingIntent site can be null is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48602": {
     "cve": "CVE-2025-48602",
     "summary": "KeyguardViewMediator now cancels a pending keyguard exit animation when a user switch begins (drops queued START_KEYGUARD_EXIT_ANIM, sets mIsKeyguardExitAnimationCanceled) and, in the post-traversal completion of exitKeyguardAndFinishSurfaceBehindRemoteAnimation, re-shows the keyguard instead of finishing the exit when that flag is set.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardViewMediator)",
     "weakness": "Race condition / missing cancellation state check between a user switch and the asynchronous completion of the keyguard exit animation",
     "root_cause": "When a user switch arrived while a keyguard exit (unlock) animation was in flight, handleBeforeUserSwitching removed only the DISMISS and HIDE messages and the mDismissToken callbacks. A queued START_KEYGUARD_EXIT_ANIM message was left in place and nothing marked the in-progress exit as canceled. The exit animation completion path, exitKeyguardAndFinishSurfaceBehindRemoteAnimation, defers its final work through DejankUtils.postAfterTraversal; that deferred runnable could execute after the user switch had reset keyguard state and still call onKeyguardExitFinished(), so the keyguard could end up hidden after a switch to a secure user (the scenario encoded by the added test). On Android 14 and 16 an earlier mIsKeyguardExitAnimationCanceled check existed but simply returned without restoring the keyguard or finishing the remote animation with the keyguard shown (and on 16 it ran synchronously before the deferred runnable, so it could not observe a cancel that happened later).",
     "fix_analysis": [
      "KeyguardViewMediator.java:handleBeforeUserSwitching: additionally calls mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM) and sets mIsKeyguardExitAnimationCanceled = true inside the synchronized block, before notifyTrustedChangedLocked/resetKeyguardDonePendingLocked and the existing secure-user doKeyguardLocked/resetStateLocked logic.",
      "KeyguardViewMediator.java: new @VisibleForTesting method postAfterTraversal(Runnable) wrapping DejankUtils.postAfterTraversal, so the deferred runnable can be captured in tests; exitKeyguardAndFinishSurfaceBehindRemoteAnimation now calls this wrapper.",
      "KeyguardViewMediator.java:exitKeyguardAndFinishSurfaceBehindRemoteAnimation (inside the posted runnable, after the existing !mPM.isInteractive() && !mPendingLock early-return): if mIsKeyguardExitAnimationCanceled is true, logs, calls finishSurfaceBehindRemoteAnimation(true /* showKeyguard */), calls setShowingLocked(true /* showing */, true /* force */) and returns, skipping onKeyguardExitFinished().",
      "KeyguardViewMediatorTest.java: adds testGoingAwayFollowedByBeforeUserSwitchWithDelayedExitAnimationDoesNotHideKeyguard, which starts the exit animation for an insecure user, calls handleBeforeUserSwitching for a secure user, then runs the captured post-traversal runnable and asserts notifyFinishedKeyguardExitAnimation(true), ATMS lock screen showing, and isShowingAndNotOccluded(); the existing going-away test also now issues keyguardDonePending/readyForKeyguardDone and expects rejection."
     ],
     "completeness": [
      "Covers the deferred (postAfterTraversal) completion path of exitKeyguardAndFinishSurfaceBehindRemoteAnimation and the queued START_KEYGUARD_EXIT_ANIM message; a cancel set by handleBeforeUserSwitching is now honored and the keyguard is forced back to showing.",
      "The flag is set only in handleBeforeUserSwitching; the diff does not show where mIsKeyguardExitAnimationCanceled is cleared again, nor whether other exit paths (e.g. the non-remote-animation path of handleStartKeyguardExitAnimation or other callers of onKeyguardExitFinished) consult the flag.",
      "The new check sits after the non-interactive early return, so a cancel during a non-interactive, non-pending-lock state takes the pre-existing error branch rather than the new restore branch.",
      "On Android 14 the backport adds removeMessages(START_KEYGUARD_EXIT_ANIM) in the user-switch handler but does not add mIsKeyguardExitAnimationCanceled = true there; whether 14 sets that flag on user switch through another existing path is not shown in the material."
     ],
     "behavior_changes": [
      "A user switch now discards any queued START_KEYGUARD_EXIT_ANIM handler message in addition to DISMISS/HIDE.",
      "When the exit animation completion runs after a cancel, SystemUI finishes the WM remote animation with showKeyguard=true (test verifies notifyFinishedKeyguardExitAnimation(true)) and calls setShowingLocked(true, true) to force the keyguard showing, instead of silently returning (14/16) or completing the unlock (15).",
      "New debug log 'Ignoring exitKeyguardAndFinishSurfaceBehindRemoteAnimation. mIsKeyguardExitAnimationCanceled==true'.",
      "On Android 16 the cancel check moved from before postAfterTraversal into the posted runnable, so blockPanelExpansionFromCurrentTouch and InteractionJankMonitor end now run even for a canceled exit."
     ],
     "preconditions": [
      "Multi-user device where a user switch (UserTracker onBeforeUserSwitching -> BEFORE_USER_SWITCHING -> handleBeforeUserSwitching) is delivered while the keyguard is going away.",
      "WindowManager has started the keyguard exit animation (startKeyguardExitAnimation with TRANSIT_OLD_KEYGUARD_GOING_AWAY) and the exit has reached exitKeyguardAndFinishSurfaceBehindRemoteAnimation, whose completion is deferred via DejankUtils.postAfterTraversal.",
      "Per the added test, the scenario is an unlock of an insecure user followed by a switch to a secure user before the deferred runnable executes."
     ],
     "per_major": {
      "14": "adapted fix (commit 1c3c01a414a0, android-security-14.0.0_r24): user-switch handler is in a different location (around line 2431) and only gains removeMessages(START_KEYGUARD_EXIT_ANIM), no flag set there; the pre-existing early-return mIsKeyguardExitAnimationCanceled check at the top of the posted runnable is moved after the non-interactive check and now restores the keyguard; test uses setCurrentUser rather than handleBeforeUserSwitching and keyguardDonePending(true, userId). 8/8 lines present at 14.0.0_r29.",
      "15": "same fix (commit 67769a49e360, cherry-pick of f17b66e73122, android-security-15.0.0_r12): adds removeMessages(START_KEYGUARD_EXIT_ANIM) and mIsKeyguardExitAnimationCanceled = true in handleBeforeUserSwitching, adds postAfterTraversal wrapper and the restore branch; no prior check removed. 8/8 lines present at 15.0.0_r17.",
      "16": "adapted fix (commit c69e16c3efda, android-security-16.0.0_r3): same handleBeforeUserSwitching changes; removes a synchronous mIsKeyguardExitAnimationCanceled early return that preceded postAfterTraversal and replaces it with the in-runnable restore branch; uses setShowingLocked(true, true, reason) and onKeyguardExitFinished(reason) signatures. 8/8 lines present at 16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); patched region (8/8 lines) present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The production change is small and self-contained in KeyguardViewMediator; the test requires the postAfterTraversal(Runnable) seam and an overridable constructor site, and the constructor argument list differs per release (14 ends with mSystemPropertiesHelper, 15 with WindowManagerOcclusionManager, 16 adds Kosmos communal interactors), so the test hunk needs manual adaptation.",
      "When porting to a 14-based tree, check whether mIsKeyguardExitAnimationCanceled is actually set on user switch; the 14 commit only adds the START_KEYGUARD_EXIT_ANIM removal in the switch handler, unlike 15/16 which set the flag explicitly in handleBeforeUserSwitching."
     ],
     "confidence": "high",
     "unknowns": [
      "Where mIsKeyguardExitAnimationCanceled is reset to false after a canceled exit (needed for subsequent unlocks) is not in the diff or context.",
      "Whether other exit-completion paths consult the flag, and whether the Android 14 user-switch handler sets the flag via another existing path.",
      "The exact user-visible trigger and the reporter scenario beyond what the added test encodes."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48609": {
     "cve": "CVE-2025-48609",
     "summary": "MmsProvider.deleteDataRows now checks that the canonical path in a part/drm row's _data column is under the provider's \"parts\" directory before it deletes the file from disk. The check uses a new isFilePathCanonical helper, which openFile also uses now.",
     "component": "platform/packages/providers/TelephonyProvider, MmsProvider (MMS part/DRM row deletion)",
     "weakness": "Missing path validation before file deletion (a path taken from a database column was trusted, so the provider's process could delete files outside its intended directory)",
     "root_cause": "In src/com/android/providers/telephony/MmsProvider.java, deleteDataRows queried the _data column of the matching part or drm rows and called new File(path).delete() on every non-null value without any check. It is reached through deleteParts and deleteTempDrmData from delete(), and through deleteMessages for pdu deletions. Nothing guarantees the _data value points into the MMS parts directory: the existing comment in openFile says callers may have inserted or updated the _data column with disallowed paths, including via SQL injection. openFile already limited paths to getContext().getDir(PARTS_DIR_NAME, 0), but the delete path had no equivalent check. A delete through the provider could therefore remove files outside the parts directory that the telephony provider process can delete. The bulletin rates this High / DoS.",
     "fix_analysis": [
      "MmsProvider.java:delete - reads Context context = getContext() once and passes it to deleteMessages, deleteParts and deleteTempDrmData, so the file-path check can reach the app data directory.",
      "MmsProvider.java:deleteParts / deleteTempDrmData / deleteDataRows - each signature gains a Context parameter. The per-message deleteParts call in deleteMessages also passes the context.",
      "MmsProvider.java:deleteDataRows - a row with a null _data is skipped (continue). Otherwise a File is built and isFilePathCanonical(context, file) is evaluated. If it returns false, an error is logged (\"deleteFile: path ... does not start with ...\") and the file is not deleted; the trailing db.delete still removes the DB row. file.delete() is called only when the check passes. The whole block stays inside the existing try/catch Throwable.",
      "MmsProvider.java:isFilePathCanonical (new, @VisibleForTesting public static) - returns file.getCanonicalPath().startsWith(context.getDir(PARTS_DIR_NAME, 0).getCanonicalPath()). Any IOException is logged and treated as false (fail closed).",
      "MmsProvider.java:openFile - the existing inline canonical-path prefix check is replaced by a call to isFilePathCanonical(getContext(), filePath). The check is the same; it is just moved into the shared helper.",
      "tests/.../MmsProviderTest.java - adds a temporary testDir/parts layout, mocks context.getDir(\"parts\", anyInt()), and adds testDeleteDataRows_pathStartsWithPartsDirectory_canDeleteFile and testDeleteDataRows_invalidPath_cannotDeleteFile, which call the helper directly."
     ],
     "completeness": [
      "Covers every file deletion done by deleteDataRows. That function serves both TABLE_PART (deleteParts, including the per-message loop in deleteMessages) and TABLE_DRM (deleteTempDrmData). openFile keeps the same restriction through the shared helper.",
      "Does not validate the _data column at insert/update time. The TODO in openFile about stopping disallowed _data paths from being inserted or updated (including via SQL injection) is still there, so the fix is a defensive check at the point of use. The commit message says the change may be redundant given separate SQL injection protection changes, which are not part of this diff.",
      "The check is a plain string prefix match on canonical paths with no trailing separator, so a directory whose name only starts with the parts directory path would also pass. The existing openFile check already worked this way.",
      "The canonical path is computed for the check, but file.delete() is then called on the original (non-canonical) File object, so the check and the delete each resolve the path separately on the filesystem.",
      "Files referenced from the DRM table must now also be under the parts directory. The material does not show where temporary DRM data files are created, so it cannot be determined whether this changes DRM cleanup."
     ],
     "behavior_changes": [
      "When a part/drm row's _data path resolves outside the directory returned by getDir(PARTS_DIR_NAME, 0), deleting the row no longer removes the referenced file. The database row is still deleted, and an error line 'deleteFile: path ... does not start with ...' is logged.",
      "If computing either canonical path throws IOException, the file is not deleted (fail closed) and an error is logged.",
      "deleteParts, deleteTempDrmData and deleteDataRows change signature (new Context first parameter). isFilePathCanonical is a new public static method."
     ],
     "preconditions": [
      "The caller can invoke MmsProvider.delete for a URI that maps to TABLE_PDU, TABLE_PART or TABLE_DRM, subject to the provider's normal access control and the per-user subId selection filter shown in delete().",
      "A matching part or drm row must have a _data value pointing outside the parts directory. Per the openFile comment, such values can come from a caller inserting/updating _data or from SQL injection. The material does not show exactly how such a row is created.",
      "The target file must be deletable by the telephony provider process."
     ],
     "per_major": {
      "14": "Same fix logic, adapted at different line offsets: commit 1552b67718fc in android-security-14.0.0_r25. The test diff also adds a 'private UserManager mUserManager;' field, and delete() gets an extra blank line. 7/8 patched-region lines present at android-security-14.0.0_r29 (resolved).",
      "15": "Same fix: commit e40b30ca3fa3 in android-security-15.0.0_r13 (cherry-pick of fce8998a1770). 7/8 patched-region lines present at android-security-15.0.0_r17 (resolved).",
      "16": "Same fix: commit 37babc702849 in android-security-16.0.0_r4, same content as 15 at shifted line offsets. 7/8 patched-region lines present at android-security-16.0.0_r8 (resolved).",
      "17": "Not affected per the bulletin (not listed). 7/8 patched-region lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. It relies on PARTS_DIR_NAME and the openFile canonical-path check existing, which they do in the 14, 15 and 16 diffs. Pass the Context through deleteMessages -> deleteParts -> deleteDataRows and update every call site of the changed static helpers.",
      "The Android 14 variant's test adds a UserManager field that is not in the 15/16 diffs. When porting, check that the target test file imports UserManager, or drop that line."
     ],
     "confidence": "high",
     "unknowns": [
      "How a _data value outside the parts directory gets into the part/drm table in affected builds (insert/update path or SQL injection) is not shown. The 'recent sql injection protection changes' the commit refers to are not part of this material.",
      "Where TABLE_DRM temporary files are stored, and therefore whether limiting their deletion to the parts directory affects DRM cleanup.",
      "Which one of the 8 patched-region lines differs in each latest security tag (reported as 7/8 present).",
      "Whether a fix commit exists on the Android 17 branch."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48619": {
     "cve": "CVE-2025-48619",
     "summary": "Adds a validateFileMode() sanitizer to ContentProvider.Transport.openFile and openAssetFile. When 'w' is absent from the mode string, it strips the 't' (truncate) and 'a' (append) characters, and the sanitized mode is then used both for the permission check and for the call forwarded to the provider.",
     "component": "platform/frameworks/base, android.content.ContentProvider (Transport binder stub for openFile/openAssetFile)",
     "weakness": "Improper validation of the file mode string: the modifying flags (truncate/append) were accepted without the write bit",
     "root_cause": "In core/java/android/content/ContentProvider.java, Transport.openFile and Transport.openAssetFile passed the caller-supplied mode string unchanged to enforceFilePermission and then to mInterface.openFile/openAssetFile. Per the new comment, the supported modes are r, w, wt, wa, rw and rwt, but nothing rejected or normalized other combinations. The commit message says invalid truncation/append bits without the write bit could cause unintended changes. Inferred from the fix, not shown in the material: such a mode passes the permission check as a non-write request, while the provider may still apply truncate or append semantics.",
     "fix_analysis": [
      "core/java/android/content/ContentProvider.java:Transport.validateFileMode (new private method): if mode is non-null and contains no 'w', it removes every 't' and every 'a' from the string (String.replace). Modes containing 'w' and null modes are returned unchanged. The comment says a stricter design (throwing a SecurityException on unknown modes) was not used, to avoid app-compat issues.",
      "core/java/android/content/ContentProvider.java:Transport.openFile: computes updatedMode = validateFileMode(mode) after validateIncomingUri/maybeGetUriWithoutUserId, passes updatedMode to enforceFilePermission, and forwards updatedMode (not the original mode) to mInterface.openFile.",
      "core/java/android/content/ContentProvider.java:Transport.openAssetFile: same change; updatedMode is used for enforceFilePermission and for mInterface.openAssetFile.",
      "Net effect: 't' and 'a' no longer reach the provider through these Transport entry points unless 'w' is present. With 'w' present, the presumed intent is that enforceFilePermission applies the write check (its body is not shown)."
     ],
     "completeness": [
      "Covers the two Transport entry points changed by the diff, openFile and openAssetFile. In both, the permission check and the forwarded mode use the sanitized value.",
      "Only 't' and 'a' are stripped. Any other characters in a mode without 'w' are passed through unchanged. The comment says a full allowlist was deliberately not implemented, for compatibility.",
      "The sanitization is placed only in the Transport layer methods shown in the diff.",
      "openTypedAssetFile is not touched by the diff. The material does not show whether it takes a mode string.",
      "The fix normalizes silently rather than rejecting: no exception is thrown and the provider receives the mode without 't'/'a'."
     ],
     "behavior_changes": [
      "Mode strings such as \"rt\", \"ra\" or \"rta\" sent through Transport.openFile/openAssetFile are silently rewritten to \"r\" before the permission check and before reaching the provider. No exception is thrown.",
      "When a client omits 'w' but includes 't' or 'a', provider implementations now see a different mode string from the one the client sent.",
      "Modes containing 'w' and null modes are unaffected."
     ],
     "preconditions": [
      "The caller reaches a ContentProvider through ContentProvider.Transport.openFile or openAssetFile and supplies a mode string containing 't' and/or 'a' without 'w'.",
      "Inferred, not shown: the caller holds only non-write access to the URI, enough for enforceFilePermission to pass for a non-write mode.",
      "Inferred, not shown: the target provider's implementation honors 't'/'a' in the mode string when opening the underlying file."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base f54544f81d7d in android-security-14.0.0_r24, identical hunks (only the surrounding trace tag differs: TRACE_TAG_DATABASE instead of TRACE_TAG_ACTIVITY_MANAGER). Patched region present at android-security-14.0.0_r29 (7/8 lines).",
      "15": "same fix: platform/frameworks/base e623c490905c in android-security-15.0.0_r12, a cherry-pick of f8099b069367. The code context at android-security-15.0.0_r17 shows validateFileMode wired into openFile and openAssetFile (7/8 lines).",
      "16": "same fix: platform/frameworks/base 31e77d73c5e6 in android-security-16.0.0_r3, identical hunks. Patched region present at android-security-16.0.0_r8 (7/8 lines).",
      "17": "not affected (not listed by the bulletin). Patched region present at android-security-17.0.0_r1 (7/8 lines)."
     },
     "backport_notes": [
      "Single self-contained commit in one file. The three hunks apply where Transport.openFile/openAssetFile call enforceFilePermission(attributionSource, uri, mode) and then mInterface.openFile/openAssetFile. Only the traceBegin tag constant differs between 14 and 15+, and it is context, not part of the change.",
      "Every major reports 7/8 lines of the patched region present. The cause is not shown. The r17 context shows both call sites but not the validateFileMode body, so check that the method and both call sites are present when backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of enforceFilePermission is not in the material, so how it classifies a mode as read or write is inferred, not shown.",
      "Which provider(s) were affected or used to demonstrate the issue is not shown. The content of bug 414387646 is not provided.",
      "Whether other Transport entry points (e.g. openTypedAssetFile) accept a mode string is not shown.",
      "Why one line of the patched region is reported missing on every major is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48630": {
     "cve": "CVE-2025-48630",
     "summary": "SkiaRenderEngine::drawLayersInternal now clips the canvas to the layer bounds before drawing blur when the layer has no rounded-corner clip, so a client-supplied blur region larger than the layer can no longer draw blurred content outside the layer.",
     "component": "platform/frameworks/native, libs/renderengine (Skia render engine)",
     "weakness": "Missing bounds clip (drawing not restricted to the layer geometry), leading to information disclosure (bulletin type ID)",
     "root_cause": "In libs/renderengine/skia/SkiaRenderEngine.cpp, drawLayersInternal gets [bounds, roundRectClip] from getBoundsAndClip. For layers with blur, it added a canvas clip only when roundRectClip was non-empty. When roundRectClip was empty, no clip to the layer bounds was added inside the SkAutoCanvasRestore scope. The drawBlurRegion call for backgroundBlurRadius and the loop over layer.blurRegions (run after concat with layer.blurRegionTransform) were therefore limited only by the existing device clip and the regions themselves. The commit message says a client sending a blur region bigger than the layer could let the blur \"escape\" the layer bounds, so a 1-2 pixel layer could fill the entire screen. The blur source is blurInput, a snapshot of the active surface.",
     "fix_analysis": [
      "libs/renderengine/skia/SkiaRenderEngine.cpp:SkiaRenderEngine::drawLayersInternal: in the blur branch (mBlurFilter && layerHasBlur), the existing `if (!roundRectClip.isEmpty()) canvas->clipRRect(roundRectClip, true);` gains an `else` branch that calls `canvas->clipRRect(bounds, true);`, where bounds is the layer's SkRRect from getBoundsAndClip.",
      "The clip is set inside the existing SkAutoCanvasRestore acr(canvas, true) scope. It therefore applies to the backgroundBlurRadius drawBlurRegion call and to the loop over layer.blurRegions, and it is restored before shadows are drawn (as the existing comment says).",
      "Single commit adding 4 lines. The hunk content is identical on Android 14, 15 and 16."
     ],
     "completeness": [
      "Covers the blur drawing site shown: in drawLayersInternal, both the whole-layer background blur and the per-region blurRegions are now always drawn under a clip (the rounded clip or the plain bounds).",
      "The rounded-corner case is unchanged: it still clips to roundRectClip only. Whether roundRectClip always lies within bounds depends on getBoundsAndClip, which the material does not show.",
      "Only SkiaRenderEngine.cpp is changed. The fix adds no validation of blurRegions on the client or SurfaceFlinger side; oversized regions are clipped when drawn.",
      "blurRect (the area generated for the blur) is still computed from bounds intersected with the device clip; the fix does not change this."
     ],
     "behavior_changes": [
      "Blur output for layers without rounded corners is now clipped to the layer bounds, so blur regions that extended past the layer are cut off. No new error codes or API changes."
     ],
     "preconditions": [
      "SkiaRenderEngine with a blur filter (mBlurFilter non-null), and the layer satisfies layerHasBlur(layer, ctModifiesAlpha).",
      "The layer has no rounded-corner clip (roundRectClip empty), which was the path without a layer-bounds clip.",
      "A client supplies blur region geometry larger than its layer bounds (per the commit message). Per index search, drawLayersInternal is reached from RenderEngineThreaded::drawLayers."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/native 6906658b06e3 in android-security-14.0.0_r25, an identical 4-line else branch (hunk at line ~833); 4/4 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/native 4deb93b73906 in android-security-15.0.0_r13; 4/4 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/native c81cf361489e in android-security-16.0.0_r4, identical hunk at line ~907; 4/4 lines present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17; the patched region (4/4 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Self-contained 4-line change inside the existing SkAutoCanvasRestore block in drawLayersInternal. It needs the `bounds` SkRRect from getBoundsAndClip and the `roundRectClip` if-statement to exist. Apply it by context, not by line number: the hunk starts at different lines on each branch (833/864/907)."
     ],
     "confidence": "high",
     "unknowns": [
      "How getBoundsAndClip is implemented, and whether roundRectClip always lies within bounds (the rounded-corner path is unchanged).",
      "How layerHasBlur is defined.",
      "How client blurRegions reach LayerSettings, and whether SurfaceFlinger validates them before that; the material only shows the render engine side.",
      "Whether layers whose blur regions were already within bounds render pixel-identically after the fix (the new clip is anti-aliased).",
      "What content is exposed, beyond the commit message's description; the PoC app is not provided."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48634": {
     "cve": "CVE-2025-48634",
     "summary": "Adds a sanitizePrivateFlags step to WindowManagerService window relayout. When these bits are newly added in the relayout attrs, it strips PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY and PRIVATE_FLAG_TRUSTED_OVERLAY from callers lacking INTERNAL_SYSTEM_WINDOW, and PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP from callers lacking MANAGE_ACTIVITY_TASKS.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/WindowManagerService.java, relayout path)",
     "weakness": "Missing permission check on client-supplied LayoutParams.privateFlags during window relayout",
     "root_cause": "In WindowManagerService's relayout path (the block after displayPolicy.adjustWindowParamsLw(win, attrs)), the client-supplied attrs.privateFlags were accepted as-is. The existing sanitizers in that block only covered attrs.flags (sanitizeFlagSlippery) and attrs.inputFeatures (sanitizeInputFeatures; sanitizeSpyWindow on 14), plus a status bar permission check for disable flags. Nothing checked that the caller was allowed to set the privileged private flags PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP. A window owner without the matching permissions could therefore turn these flags on with a relayout call after the window had been added.",
     "fix_analysis": [
      "WindowManagerService.java (relayout block, after displayPolicy.adjustWindowParamsLw): a new line, attrs.privateFlags = sanitizePrivateFlags(attrs.privateFlags, win.mAttrs.privateFlags, win.getName(), uid, pid);, is inserted before sanitizeFlagSlippery and sanitizeInputFeatures. It uses the Binder calling uid/pid captured at the top of the method.",
      "WindowManagerService.java:sanitizePrivateFlags(newPrivateFlags, oldPrivateFlags, windowName, callingUid, callingPid): computes addedPrivateFlags = ~oldPrivateFlags & newPrivateFlags, so only newly set bits are examined. If either PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_TRUSTED_OVERLAY is being added and the caller lacks INTERNAL_SYSTEM_WINDOW, both bits are cleared from the result and a Slog.w is logged. If PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP is being added and the caller lacks MANAGE_ACTIVITY_TASKS, that bit is cleared and a Slog.w is logged. The sanitized value is written back to attrs.privateFlags. The tests show that the stripped flags are then not reported as private-flag changes.",
      "WindowManagerService.java: new helpers hasFlags(flags, mask) and hasPermission(permission, callingPid, callingUid). hasPermission wraps mContext.checkPermission(...) == PackageManager.PERMISSION_GRANTED.",
      "WindowManagerServiceTests.java: the testRelayoutFlagChanges helper gains internalSystemWindowGranted and manageActivityTasksGranted parameters, which stub mWm.mContext.checkPermission for the two permissions (anyInt() for pid and uid). Six new tests cover the granted and denied cases for each of the three flags. A new early return checks that keepActivityOnWindowFlagsChanged is never called on a non-first relayout when no changes are expected."
     ],
     "completeness": [
      "Covers only the relayout path. The addWindow path is not touched by this diff, so the material does not show whether these private flags are validated at window creation.",
      "Only newly added bits (~old & new) are checked. Bits already present in win.mAttrs.privateFlags are carried over without being checked again, and clearing bits is never restricted.",
      "Only three private flags are gated (IS_ROUNDED_CORNERS_OVERLAY, TRUSTED_OVERLAY, INTERCEPT_GLOBAL_DRAG_AND_DROP). This path does not check any other LayoutParams.privateFlags bits.",
      "Argument order seen in the diff: hasPermission is declared as (String permission, int callingPid, int callingUid) and forwards (permission, callingPid, callingUid) to mContext.checkPermission. Both call sites in sanitizePrivateFlags call hasPermission(permission, callingUid, callingPid), so the uid value ends up in checkPermission's pid parameter and the pid value in its uid parameter. The new tests stub checkPermission with anyInt(), anyInt(), so they do not exercise this order. The runtime effect depends on how Context.checkPermission handles those values, which the material does not show."
     ],
     "behavior_changes": [
      "During relayout, if a caller without INTERNAL_SYSTEM_WINDOW newly sets PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, both bits are cleared and a Slog.w is logged. No exception is thrown.",
      "If a caller without MANAGE_ACTIVITY_TASKS newly sets PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, that bit is cleared and a Slog.w is logged.",
      "Stripped flags do not produce a reported private-flag change: in the denied cases, the tests assert that keepActivityOnWindowFlagsChanged is not called.",
      "Up to two additional mContext.checkPermission calls can happen per relayout when the relevant bits are being added."
     ],
     "preconditions": [
      "The caller owns a window already registered with WindowManagerService (windowForClientLocked returns a WindowState) and issues a relayout with non-null attrs.",
      "The relayout attrs newly set PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP relative to win.mAttrs.privateFlags.",
      "The caller lacks INTERNAL_SYSTEM_WINDOW (for the two overlay flags) or MANAGE_ACTIVITY_TASKS (for the drag-and-drop flag)."
     ],
     "per_major": {
      "14": "Adapted fix: commit c88804906997 in android-security-14.0.0_r24. The sanitizePrivateFlags body and insertion point are identical. The neighbouring production line is sanitizeSpyWindow instead of sanitizeInputFeatures, and the test adds an android.content.pm.PackageManager import. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "Fix commit a54ba90ae134 in android-security-15.0.0_r12 (cherry-pick of ec6a92be6c93, Change-Id I4fdf2152f06082e13690e7f5b628e0ca0acdff84). 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Same fix: commit d550a457e65c in android-security-16.0.0_r3, identical production diff. The test helper already used newWindowBuilder. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). The patched region is present 8/8 at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The production change is self-contained: two new private helpers plus one call in the relayout block. Across branches only the neighbouring line differs (sanitizeSpyWindow on 14, sanitizeInputFeatures on 15/16). The test change uses newWindowBuilder and stubs mWm.mContext.checkPermission. Google's 14 backport also added an android.content.pm.PackageManager import to the test.",
      "When porting, note the pid/uid parameter order mismatch between hasPermission's declaration and its call sites (see completeness), and decide whether to keep the upstream code verbatim or align the order. The upstream unit tests cannot tell the two apart."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether addWindow validates these private flags at window creation (not in the material).",
      "Runtime effect of the swapped pid/uid order at the hasPermission call sites; this depends on Context.checkPermission internals not shown.",
      "How win.isTrustedOverlay() (passed to sanitizeInputFeatures) relates to PRIVATE_FLAG_TRUSTED_OVERLAY and to the sanitized attrs.",
      "Which downstream consumer of the trusted-overlay or drag-and-drop private flags the bulletin's EoP rating refers to."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48641": {
     "cve": "CVE-2025-48641",
     "summary": "In the ST NFC AIDL HAL (hardware/st/nfc aidl/Nfc.h), the static callbacks Nfc::eventCallback and Nfc::dataCallback now copy the static shared_ptr Nfc::mCallback into a local shared_ptr under a new static pthread mutex sCallbackLock. They call sendEvent/sendData on that local strong reference instead of dereferencing the shared static directly.",
     "component": "platform/hardware/st/nfc, AIDL NFC HAL (aidl/Nfc.h, struct Nfc)",
     "weakness": "Use-after-free caused by a race on a shared static std::shared_ptr (unsynchronized read of Nfc::mCallback across threads)",
     "root_cause": "Nfc::mCallback is a static shared pointer to INfcClientCallback. It is used both by the static Nfc::eventCallback and Nfc::dataCallback, which run on an asynchronous thread, and by the main thread running Nfc::open. Both callbacks did a plain `if (mCallback != nullptr)` check and then called `mCallback->sendEvent(...)` / `mCallback->sendData(...)` directly on the static pointer, with no lock and no local strong reference. Per the commit message, Nfc::open could destroy the underlying object while eventCallback was about to call a method on it, so the null check did not protect against the race.",
     "fix_analysis": [
      "aidl/Nfc.h (file scope): adds `static pthread_mutex_t sCallbackLock = PTHREAD_MUTEX_INITIALIZER;` before `struct Nfc`.",
      "aidl/Nfc.h:Nfc::eventCallback: declares `std::shared_ptr<INfcClientCallback> localCallback;`, locks sCallbackLock, assigns `localCallback = mCallback;` and unlocks. It then does the null check on localCallback and calls `localCallback->sendEvent(mEvent, mStatus)` instead of `mCallback->sendEvent(...)`. The local copy holds a strong reference for the whole call.",
      "aidl/Nfc.h:Nfc::dataCallback: same pattern. The locked copy into localCallback happens before the std::vector<uint8_t> data is built, and both the null check and `localCallback->sendData(data)` use the local copy.",
      "The commit touches only aidl/Nfc.h (14 insertions, 4 deletions). The code that writes mCallback is not part of the diff."
     ],
     "completeness": [
      "Covers both static reader paths in aidl/Nfc.h that dereference mCallback: eventCallback (sendEvent) and dataCallback (sendData).",
      "The commit message says reads and writes of mCallback are protected by the lock, but the diff only shows the reads. The material does not show whether the code that writes mCallback (for example Nfc::open) takes sCallbackLock. If it does not, copying the shared_ptr while another thread assigns it is still unsynchronized.",
      "sCallbackLock is declared `static` at namespace scope in a header. With internal linkage, each translation unit that includes Nfc.h gets its own instance, so writer-side locking must use the same instance for mutual exclusion.",
      "The diff adds no closed-state check. It only guarantees that the callback object stays alive during sendEvent/sendData."
     ],
     "behavior_changes": [
      "eventCallback and dataCallback now briefly take a mutex and copy a shared_ptr on every NFC event or data delivery.",
      "The client callback object stays alive through the local reference until sendEvent/sendData returns, even if mCallback is reassigned at the same time. There are no new error codes and no return-value changes."
     ],
     "preconditions": [
      "The ST NFC AIDL HAL (hardware/st/nfc) must be the NFC HAL in use on the device.",
      "An asynchronous thread must invoke Nfc::eventCallback or Nfc::dataCallback while another thread calls Nfc::open (per the commit message) or otherwise replaces mCallback.",
      "In the normal platform configuration, the NFC HAL is a vendor service used by the system NFC stack (general platform knowledge). Per index search, no relevant callers of these callbacks exist outside the component."
     ],
     "per_major": {
      "14": "Same fix: platform/hardware/st/nfc 502f1802c4e1 in android-security-14.0.0_r25. The Nfc.h diff is identical to the Android 15 commit (same blob indexes and hunks). At android-security-14.0.0_r29, 7/8 lines of the patched region are reported present.",
      "15": "Fix commit ffe16073e613 in android-security-15.0.0_r13 (cherry-pick of fc619c3348188ff0020cdeb7d4c4728a0246fe1a). The code at android-security-15.0.0_r17 shows sCallbackLock and the localCallback pattern in eventCallback. 7/8 lines of the region are reported present.",
      "16": "Same fix: c6da9eeb710c in android-security-16.0.0_r4. The logic is identical, and the hunk offsets are shifted by one line. The 16 header context includes a `binder_status_t dump(...)` override declaration. At android-security-16.0.0_r8, 7/8 lines of the region are reported present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 7/8 lines of the patched region are reported present."
     },
     "backport_notes": [
      "This is a single-file, header-only change. Google applied the same diff to 14, 15 and 16; only the context differs on 16. When backporting, check that every write of mCallback in the HAL implementation takes sCallbackLock. The commit message describes read/write protection, but the diff does not show the writer side.",
      "sCallbackLock is a static declared in the header. Any writer-side locking must use the same mutex instance as the translation unit containing eventCallback/dataCallback."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the code that writes mCallback (for example Nfc::open, and possibly close) locks sCallbackLock. This code is not in the diff.",
      "Why the per-major check reports 7/8 rather than 8/8 lines of the patched region in every latest tag. The r17 code shown does contain the fix.",
      "The exact caller path and the privilege needed to trigger the race on a deployed device, beyond what the commit message states."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48642": {
     "cve": "CVE-2025-48642",
     "summary": "Changes the data-cache cleans in pvmfw's payload hand-off (jump_to_payload) and in vmbase's flush_region from `dc cvau` (clean to Point of Unification) to `dc cvac` (clean to Point of Coherency), so that zeroed or flushed memory is written back to main memory on SoCs with a unified cache before the PoC.",
     "component": "platform/packages/modules/Virtualization, pvmfw (protected VM firmware) and vmbase (bare-metal Rust runtime), aarch64 cache maintenance",
     "weakness": "Incomplete clearing of sensitive data: cache maintenance was done only to the PoU, so zeroes written over secrets could stay in a cache level and never reach main memory (hardware-dependent)",
     "root_cause": "pvmfw::entry::jump_to_payload zeroes .data/.bss (skipping the BCC) and the stack with `stp xzr, xzr`. It then cleans the d-cache over those ranges with `dc cvau` before writing sctlr_el1 (the code comment says this disables caches and the page table) and jumping to the payload. vmbase's flush_region, which is called by flush() and flushed_zeroize(), also used `dc cvau`. Per the commit message, on some SoCs with a unified cache before the Point of Coherency, CMOs to the PoU might not flush the data to main memory. Memory that pvmfw meant to scrub could therefore keep stale contents in main memory.",
     "fix_analysis": [
      "pvmfw/src/entry.rs:jump_to_payload: the loop that flushes the d-cache over .data & .bss (including BCC) changes from `dc cvau, {cache_line}` to `dc cvac, {cache_line}`.",
      "pvmfw/src/entry.rs:jump_to_payload: the loop that flushes the d-cache over the zeroed stack region also changes from `dc cvau` to `dc cvac`.",
      "vmbase/src/memory/util.rs:flush_region: the per-line `asm!(\"dc cvau, {x}\")` becomes `asm!(\"dc cvac, {x}\")`. This also covers the public flush() and flushed_zeroize() helpers, which call flush_region.",
      "No other logic changes: the line-size computation (min_dcache_line_size via ctr_el0 DminLine), loop bounds, zeroing, sctlr_el1 handling and register clearing are untouched."
     ],
     "completeness": [
      "Covers the three `dc cvau` sites in the diff: the two flush loops in jump_to_payload and the single clean in vmbase flush_region (and therefore flush()/flushed_zeroize()). The material does not show whether other sites exist in the branch.",
      "The doc comments on flush() (\"Flushes the slice to the point of unification\") and flushed_zeroize() (\"to the point of unification\") in vmbase/src/memory/util.rs are not updated and are now stale relative to the `dc cvac` behaviour.",
      "The fix changes only the clean point of the CMO. It adds no barrier and does not change which regions are zeroed. The Android 15 jump_to_payload shown flushes only .data/.bss and the stack. The Android 16 variant also flushes an EH stack region, which does not appear in the Android 15 code shown.",
      "Whether the fix is sufficient on a given SoC depends on the hardware cache topology. The commit message describes a hardware-specific issue, and the test is referenced only as a bug comment."
     ],
     "behavior_changes": [
      "Data-cache cleans in the pvmfw exit path and in vmbase flush/flushed_zeroize now go to the Point of Coherency (`dc cvac`) instead of the Point of Unification (`dc cvau`).",
      "No API, error-code or control-flow change."
     ],
     "preconditions": [
      "The device uses pvmfw (from the Virtualization module) to boot a protected VM. jump_to_payload is reached from pvmfw entry::start after main_wrapper succeeds (per index search).",
      "The SoC has a unified cache before the PoC (per the commit message).",
      "For the vmbase path: vmbase-based guest firmware that calls flush() or flushed_zeroize() on sensitive memory."
     ],
     "per_major": {
      "14": "Adapted fix (ed2818f8301e in android-security-14.0.0_r25): the same cvau->cvac change in pvmfw/src/entry.rs:jump_to_payload (two loops), plus flush_region, which on this branch is in pvmfw/src/helpers.rs rather than vmbase. At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "Fix fa7ca326d451 in android-security-15.0.0_r13 (cherry-pick of a6f64040): pvmfw/src/entry.rs (two loops) and vmbase/src/memory/util.rs:flush_region. The code at android-security-15.0.0_r17 shows `dc cvac` at all three sites (6/8 lines of the patched region present per the facts).",
      "16": "Adapted fix (f0271f36388e in android-security-16.0.0_r4): the files are relocated to guest/pvmfw/src/arch/aarch64/payload.rs:jump_to_payload (three loops, including an extra EH stack region) and libs/libvmbase/src/arch.rs:flush_region, which uses `crate::dc!(\"cvac\", line)`. At android-security-16.0.0_r8, 6/8 lines of the patched region are present.",
      "17": "Not affected (not listed by the bulletin). The patched files pvmfw/src/entry.rs and vmbase/src/memory/util.rs are absent at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Each change replaces one token (`cvau` -> `cvac`) in a `dc` instruction, but the files differ between branches. flush_region is in pvmfw/src/helpers.rs on 14, in vmbase/src/memory/util.rs on the 15 security branch, and in libs/libvmbase/src/arch.rs (via the dc! macro) on 16. Per index search, the android-15.0.0_r36 tree uses guest/pvmfw/src/entry.rs and libs/libvmbase/src/arch.rs. Match by content rather than by path, and apply the change to any extra flush loop present in the target (for example the EH stack loop on 16)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which SoCs have the unified-cache-before-PoC behaviour. The test evidence is only referenced as b/434562039#comment35.",
      "What concretely becomes observable to the payload or to a later guest. The bulletin only rates the issue High / ID.",
      "Whether other `dc cvau` uses exist in the Virtualization module on the 15 security branch beyond the three sites shown.",
      "Which 2 of the 8 patched-region lines differ at the latest 14/15/16 security tags."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48644": {
     "cve": "CVE-2025-48644",
     "summary": "Two frameworks/base commits harden the input method stack against oversized IME metadata: InputMethodInfo parsing now enforces a cumulative MAX_METADATA_SIZE_BYTES budget over all resolved attribute values (not just the raw XML file size), and IInputMethodManager#getEnabledInputMethodSubtypeList now returns a blob-backed InputMethodSubtypeSafeList instead of a plain List<InputMethodSubtype> to avoid TransactionTooLargeException.",
     "component": "platform/frameworks/base, input method framework (android.view.inputmethod.InputMethodInfo, com.android.internal.inputmethod SafeList parcelables, InputMethodManagerService IPC)",
     "weakness": "Insufficient size validation of IME-supplied metadata leading to uncontrolled resource consumption (OOM / oversized Binder transaction), i.e. DoS",
     "root_cause": "Per the commit message, InputMethodInfo's constructor validated only the raw size of the IME's meta-data XML (an InputStream.skip based file-size check) plus the length of the activity component names. Attribute values read through TypedArray.getString() can be resource references (e.g. @string/large_text) whose resolved content is not counted by the file-size check, so parsing could build InputMethodInfo/InputMethodSubtype objects holding very large strings, leading to OOM or large Binder transactions. In addition, the IInputMethodManager AIDL method getEnabledInputMethodSubtypeList returned a plain List<InputMethodSubtype> across Binder, so a large subtype list could trigger TransactionTooLargeException (InputMethodInfoSafeList already existed for the InputMethodInfo lists, but the subtype list path was not covered).",
     "fix_analysis": [
      "Commit 903125b03826 'Harden InputMethodInfo parsing against large metadata', core/java/android/view/inputmethod/InputMethodInfo.java, constructor: introduces nested classes TypedArrayWrapper (an AutoCloseable wrapper around TypedArray) and MetadataReadBytesTracker. One tracker (readTracker) is created per InputMethodInfo and shared by TypedArrayWrapper.createForMethod (the <input-method> TypedArray) and TypedArrayWrapper.createForSubtype (each <subtype> TypedArray). Every read is charged against the budget: getString charges ret.length() * Character.BYTES, getInt and getResourceId charge Integer.BYTES, and getBoolean charges 1 byte (int, bool and resId are charged only when hasValue(index)). MetadataReadBytesTracker.onReadBytes subtracts from mRemainingBytes (initialised to MAX_METADATA_SIZE_BYTES) and throws XmlPullParserException once the value goes negative, which aborts the parse.",
      "Same commit, TypedArrayWrapper.getString/getMaxLength: for the <input-method> tag, InputMethod_settingsActivity and InputMethod_languageSettingsActivity are limited to COMPONENT_NAME_MAX_LENGTH characters (otherwise XmlPullParserException is thrown). All other strings and all subtype strings use Integer.MAX_VALUE, so there is no per-string limit (TODO b/456008595) and only the cumulative budget applies. The old combined 'Activity name exceeds maximum of 1000 characters' check is removed. A separate si.name length check is added just before the existing InputStream.skip file-size validation.",
      "Same commit: the TypedArrays are now opened in try-with-resources, so TypedArrayWrapper.close() calls recycle() on every path, including the exception path. The subtype loop is restructured (continue on non-START_TAG) but keeps the same 'subtype' tag validation. Tests are added in InputMethodInfoTest (testTypedArrayWrapper*, including a loop of 103 reads of a 1000-char string that exceeds the 200*1024 byte budget).",
      "Commit 42ad31c95505 'Introduce InputMethodSubtypeSafeList', core/java/com/android/internal/inputmethod/AbstractSafeList.java (new): a generic Parcelable that holds the list pre-marshalled as a byte[] (marshall via Parcel.writeTypedList + marshall; unmarshall via Parcel.unmarshall + createTypedArrayList) and writes it with Parcel.writeBlob. extractFrom is null-tolerant, returns the list once and clears the buffer. InputMethodInfoSafeList.java is refactored to extend it (InputMethodInfoSafeList.empty() is removed; create(null) now yields an empty list). The new InputMethodSubtypeSafeList.java/.aidl specialise it for InputMethodSubtype.",
      "Same commit, core/java/com/android/internal/view/IInputMethodManager.aidl: the return type of getEnabledInputMethodSubtypeList changes from List<InputMethodSubtype> to InputMethodSubtypeSafeList. services/core/java/com/android/server/inputmethod/InputMethodManagerService.java#getEnabledInputMethodSubtypeList wraps the result of getEnabledInputMethodSubtypeListLocked in InputMethodSubtypeSafeList.create(...). IInputMethodManagerImpl.Callback, IInputMethodManagerImpl#getEnabledInputMethodSubtypeList and ZeroJankProxy#getEnabledInputMethodSubtypeList are updated to the new type, and InputMethodManagerService's two InputMethodInfoSafeList.empty() calls become create(null). On the client side, core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java#getEnabledInputMethodSubtypeList unwraps the result with InputMethodSubtypeSafeList.extractFrom(...)."
     ],
     "completeness": [
      "Covered: every attribute read in the InputMethodInfo XML constructor (the <input-method> TypedArray and every <subtype> TypedArray) goes through TypedArrayWrapper and shares one MetadataReadBytesTracker. Resolved string references are therefore bounded per InputMethodInfo by MAX_METADATA_SIZE_BYTES, in addition to the existing raw file-size check.",
      "Per-string limits apply only to InputMethod_settingsActivity and InputMethod_languageSettingsActivity (COMPONENT_NAME_MAX_LENGTH). InputMethod_stylusHandwritingSettingsActivity and all <subtype> string attributes (e.g. physicalKeyboardHintLanguageTag, physicalKeyboardHintLayoutType) get getMaxLength() == Integer.MAX_VALUE, so only the cumulative budget constrains them (marked TODO b/456008595 in the diff).",
      "The length check in getString runs after mTypedArray.getString(index) has already resolved and allocated the string, so a single oversized string is still created once before it is rejected (a diff comment mentions the resource layer's DEFAULT_MAX_STRING_ATTR_LENGTH = 32_768 limit).",
      "The budget is per InputMethodInfo instance, and nothing in the diff limits the total across many IMEs. The InputMethodInfo lists (getInputMethodList/getEnabledInputMethodList) already travel as InputMethodInfoSafeList, and the subtype list now travels as InputMethodSubtypeSafeList.",
      "The AIDL change converts only getEnabledInputMethodSubtypeList. getEnabledInputMethodListLegacy still returns a plain List<InputMethodInfo> (visible in the IInputMethodManagerImpl.Callback context). The AIDL context shows a TODO(b/339761278) removal comment in front of a method whose declaration is cut off."
     ],
     "behavior_changes": [
      "IMEs whose resolved <input-method>/<subtype> attribute values together exceed MAX_METADATA_SIZE_BYTES (200*1024 bytes per the test comment) now fail InputMethodInfo construction with XmlPullParserException ('The input method service has metadata exceeds the ... byte limit').",
      "A settingsActivity or languageSettingsActivity longer than COMPONENT_NAME_MAX_LENGTH now throws with a new message ('String resources in input method exceed the length limit of ... characters'). The si.name check has its own new message ('Input method name exceeds ... characters').",
      "Binder signature change: IInputMethodManager#getEnabledInputMethodSubtypeList returns InputMethodSubtypeSafeList (written via Parcel.writeBlob) instead of List<InputMethodSubtype>. The public InputMethodManager API is unchanged because IInputMethodManagerGlobalInvoker unwraps the result. Blob transfers above a threshold go through ashmem rather than the Binder buffer (general platform knowledge).",
      "InputMethodInfoSafeList.empty() is removed and InputMethodInfoSafeList.create(null) replaces it. AbstractSafeList.extractFrom(null) returns an empty list, whereas the old code would have thrown an NPE.",
      "TypedArrays are now always recycled via try-with-resources, including on exception paths."
     ],
     "preconditions": [
      "An installed package declaring an input method service whose meta-data XML attributes reference large resources; the metadata is parsed in InputMethodInfo's XML constructor. Which component and process run this parsing is not shown in the material.",
      "For the IPC part: a client calls the enabled-subtype-list API, which goes from IInputMethodManagerGlobalInvoker#getEnabledInputMethodSubtypeList through IInputMethodManagerImpl to ZeroJankProxy/InputMethodManagerService#getEnabledInputMethodSubtypeList. Cross-user queries need INTERACT_ACROSS_USERS_FULL per the AIDL annotation and the service's enforceCallingOrSelfPermission.",
      "Exact trigger details are not in the material (bugs 449416164, 449181366, 449393786 and 449227003 are referenced only by id)."
     ],
     "per_major": {
      "14": "adapted fix: 'Harden InputMethodInfo parsing against large metadata' as 558ee85e1d4a in android-security-14.0.0_r25. Same TypedArrayWrapper/MetadataReadBytesTracker design, but without the languageSettingsActivity, isVirtualDeviceOnly and supportsConnectionlessStylusHandwriting attributes (getMaxLength special-cases only InputMethod_settingsActivity), and the test lives in core/tests/coretests. Whether the InputMethodSubtypeSafeList commit was also applied to 14 is not shown. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "same fix: both commits (903125b03826 hardening and 42ad31c95505 InputMethodSubtypeSafeList) are in android-security-15.0.0_r13. At android-security-15.0.0_r17, 7/8 lines of the patched region are present, and the new files (AbstractSafeList, InputMethodSubtypeSafeList.java/.aidl) appear in the code context.",
      "16": "adapted fix: hardening commit 2bca2265ff3e in android-security-16.0.0_r4. The added code matches 15 except that the flag guard is Flags.imeSwitcherRevampApi(); the other test-file differences are context that already existed. Whether the InputMethodSubtypeSafeList commit was also applied to 16 is not shown. At android-security-16.0.0_r8, 7/8 lines are present.",
      "17": "not affected per the bulletin (not listed). The patched region is present (7/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The hardening commit is self-contained in core/java/android/view/inputmethod/InputMethodInfo.java (plus tests) and only needs MAX_METADATA_SIZE_BYTES and COMPONENT_NAME_MAX_LENGTH to exist. Drop attributes the target branch lacks (as the 14 backport did for languageSettingsActivity, isVirtualDeviceOnly and connectionless stylus handwriting), and keep the single readTracker shared across the method TypedArray and every subtype TypedArray.",
      "The InputMethodSubtypeSafeList commit depends on InputMethodInfoSafeList already existing (change I0a7667070fcdf17d34b248a5988c38064588718a) and changes the IInputMethodManager AIDL return type. Every implementer of IInputMethodManagerImpl.Callback (InputMethodManagerService, ZeroJankProxy) and the client unwrapping in IInputMethodManagerGlobalInvoker must therefore change together. The commit also removes InputMethodInfoSafeList.empty(), so replace any remaining callers with create(null)."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the InputMethodSubtypeSafeList/AIDL commit was cherry-picked to Android 14 and 16 (only the InputMethodInfo hardening commit is shown for those branches).",
      "The numeric value of MAX_METADATA_SIZE_BYTES is defined outside the diff (the commit message says 200KB, the test comment says 200 * 1024).",
      "The javadoc of TypedArrayWrapper references STRING_ATTRIBUTES_MAX_CHAR_LENGTH, which is not defined anywhere in the diff; whether such a per-string limit exists is unknown.",
      "Where the method containing the new si.name check and the file-size validation is called relative to XML parsing, and which component and process construct InputMethodInfo from XML.",
      "Which of the four referenced bugs corresponds to which symptom (OOM vs TransactionTooLargeException), and which line of the 8-line patched region is reported missing in the latest security tags."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48645": {
     "cve": "CVE-2025-48645",
     "summary": "DeviceAdminInfo.loadDescription now catches an OutOfMemoryError thrown while it loads a device admin receiver's description resource through PackageManager.getText, and throws the already-declared NotFoundException instead, so an oversized description string no longer escapes to the caller as an uncaught Error.",
     "component": "platform/frameworks/base, DevicePolicy / DeviceAdminInfo (core/java/android/app/admin)",
     "weakness": "Uncontrolled resource consumption / unhandled error from an app-supplied resource (no size handling for an app-controlled string resource)",
     "root_cause": "DeviceAdminInfo.loadDescription called pm.getText(mActivityInfo.packageName, mActivityInfo.descriptionRes, mActivityInfo.applicationInfo) directly. The description resource belongs to the device admin app's own receiver, so the app controls its content. Loading a very large string can throw OutOfMemoryError. That is an Error, not the NotFoundException the method declares, so a caller that is ready for NotFoundException would not handle it and the calling process could crash. The commit message calls this a DoS caused by injecting a maliciously large string into the receiver's label. The code fix is on the description path. The bulletin rates the issue High, type EoP.",
     "fix_analysis": [
      "core/java/android/app/admin/DeviceAdminInfo.java:loadDescription: the pm.getText(...) call is now wrapped in try/catch. On OutOfMemoryError the method throws a new NotFoundException, the exception type it already declares. No other logic changes. When descriptionRes == 0 the method still throws NotFoundException as before.",
      "The patch is the same in the Android 14, 15 and 16 commits (same 5 inserted lines). Only the hunk offset differs: @@ -442 on 14, @@ -486 on 15, @@ -473 on 16."
     ],
     "completeness": [
      "Covers: only the OutOfMemoryError path inside DeviceAdminInfo.loadDescription, which it turns into the declared exception type.",
      "Does not limit the string size. The fix catches OutOfMemoryError after the allocation has been attempted; it does not trim or bound the description. The r17 code context shows a separate @hide method, loadDescriptionSafe(pm). It delegates to mActivityInfo.loadDescription(pm) and is documented as trimming to 1024 characters. This fix commit does not add or use it, and the material does not show where it came from.",
      "loadLabel(pm) in the same class, which delegates to mActivityInfo.loadLabel(pm), is untouched, even though the commit message mentions the receiver's label.",
      "The material does not show how callers handle NotFoundException."
     ],
     "behavior_changes": [
      "An OutOfMemoryError raised while loading a device admin description now reaches callers as a NotFoundException instead of an Error.",
      "No change for normal-size descriptions or when descriptionRes == 0."
     ],
     "preconditions": [
      "An installed app declares a device admin receiver whose description resource is a string large enough to exhaust the caller's heap when loaded.",
      "A process builds a DeviceAdminInfo for that receiver and calls loadDescription(pm). Per index search, Settings (DeviceAdminListPreferenceController.createDeviceAdminInfo) and Car Settings (EnterpriseUtils.getDeviceAdminInfo) create DeviceAdminInfo instances. Settings' DeviceAdminListItemTest mocks loadDescription, which suggests DeviceAdminListItem uses it. The material does not show other callers."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base ecf88469d44f in android-security-14.0.0_r25, the same try/catch in DeviceAdminInfo.loadDescription (@@ -442); 5/5 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 42b92b39e52c in android-security-15.0.0_r13 (@@ -486); 5/5 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base cee45869c491 in android-security-16.0.0_r4 (@@ -473); 5/5 lines present at android-security-16.0.0_r8.",
      "17": "not affected (the bulletin does not list Android 17); the patched region (5/5 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-hunk, self-contained change with no new dependencies. It applies wherever loadDescription still has the original pm.getText call. The commit lists two Cherrypick-From sources, but the material contains only this one hunk. The trimmed loadDescriptionSafe path seen at r17 is not part of this diff."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the bulletin classifies the issue as EoP rather than DoS; the commit message describes only a DoS.",
      "Which process or processes the uncaught OutOfMemoryError crashed in the reported scenario.",
      "How callers of loadDescription handle NotFoundException.",
      "Whether loadDescriptionSafe was added for this bug or separately, and whether any callers were switched to it.",
      "Test coverage: the commit says 'Test: manual' and the diff has no automated test."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48646": {
     "cve": "CVE-2025-48646",
     "summary": "ActivityStarter now only propagates sourceRecord.launchedFromPackage/launchedFromFeatureId to a FLAG_ACTIVITY_FORWARD_RESULT target after verifying, via PackageManagerInternal.getPackageUid and UserHandle.isSameApp, that the package belongs to the calling uid.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (services/core/java/com/android/server/wm/ActivityStarter.java)",
     "weakness": "Improper validation of caller identity (trust of an unverified package name; launchedFromPackage spoofing)",
     "root_cause": "In ActivityStarter, when an activity is started with Intent.FLAG_ACTIVITY_FORWARD_RESULT and sourceRecord.launchedFromUid == callingUid, the code unconditionally set callingPackage = sourceRecord.launchedFromPackage and callingFeatureId = sourceRecord.launchedFromFeatureId. The removed comment said this was safe because \"these two packages come from the same uid\". However, the code only compared uids and never checked that sourceRecord.launchedFromPackage actually belongs to callingUid. The added test shows an ActivityRecord whose launchedFromPackage is a different (privileged) package while its launchedFromUid is the caller's uid. The test models this as the result of startNextMatchingActivity(). Forwarding the result from such a record let the new activity's launchedFromPackage be a package the caller does not own.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java (FLAG_ACTIVITY_FORWARD_RESULT branch, inside the sourceRecord.launchedFromUid == callingUid block): the fix reads sourceRecord.launchedFromPackage into a local variable. If it is non-null, the fix gets PackageManagerInternal via mService.getPackageManagerInternalLocked() and resolves the package's uid with getPackageUid(launchedFromPackage, 0, UserHandle.getUserId(callingUid)).",
      "callingPackage and callingFeatureId are overridden with the source record's values only if UserHandle.isSameApp(packageUid, callingUid) holds. Otherwise they stay the immediate caller's package and feature id, and the test asserts that the new ActivityRecord then records these as launchedFromPackage.",
      "The comment claiming the override was inherently safe is removed, and a reference to b/457742426 is added.",
      "services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java: the new testLaunchedFromPackage_nextMatchingActivity_forwardResult builds P1 (privileged) plus M1 and M2 (malicious uid). M2 has launchedFromPackage set to the privileged package but launchedFromUid set to the malicious uid. The test mocks getPackageUid for both packages and starts P2 from M2 with FLAG_ACTIVITY_FORWARD_RESULT. It then asserts that P2.launchedFromPackage and launchedFromUid are the malicious (immediate caller) values."
     ],
     "completeness": [
      "Covers the single site in ActivityStarter where launchedFromPackage/launchedFromFeatureId are inherited across a FLAG_ACTIVITY_FORWARD_RESULT hop. The override is now gated on a check that the package belongs to the calling uid.",
      "The null case is handled: if sourceRecord.launchedFromPackage is null, no override happens. Previously callingPackage would have been set to null.",
      "The fix does not change how an ActivityRecord ends up with a launchedFromPackage that does not match its launchedFromUid. The test attributes that state to startNextMatchingActivity(). That upstream inconsistency is left as-is in the material shown, so this commit does not address other consumers of ActivityRecord.launchedFromPackage. The material does not show whether any such consumer is security relevant."
     ],
     "behavior_changes": [
      "For FLAG_ACTIVITY_FORWARD_RESULT launches where the source's launchedFromPackage does not resolve to the caller's uid, the started activity now records the immediate caller's package and feature id instead of the inherited ones.",
      "A PackageManagerInternal.getPackageUid lookup is added on this launch path. It runs only when the forward-result and same-uid conditions hold and launchedFromPackage is non-null.",
      "No new error codes or failures; the start proceeds either way."
     ],
     "preconditions": [
      "The caller starts an activity with Intent.FLAG_ACTIVITY_FORWARD_RESULT and a non-null sourceRecord (resultTo), with requestCode < 0.",
      "sourceRecord.launchedFromUid equals the calling uid, while sourceRecord.launchedFromPackage names a package that uid does not own. The test models this state as produced by startNextMatchingActivity() after a privileged app launched the caller's activity.",
      "The target activity relies on launchedFromPackage (or the callingPackage derived from it) to attribute the launch."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base ad0b8a7b63ac in android-security-14.0.0_r25. The code change is identical at a different hunk offset (line 990), and the identical test is added. At android-security-14.0.0_r29, 5/8 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/base 04c368b5f527 in android-security-15.0.0_r13 (hunk at line 1030). The fixed code is visible at android-security-15.0.0_r17, where 5/8 lines of the patched region are reported present.",
      "16": "same fix: platform/frameworks/base c148b4fae634 in android-security-16.0.0_r4 (hunk at line 1142), with identical code and test. At android-security-16.0.0_r8, 5/8 lines of the patched region are present.",
      "17": "not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 5/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit with no dependencies. The Java change is identical across 14/15/16; only the hunk offset differs. It uses mService.getPackageManagerInternalLocked(), PackageManagerInternal and UserHandle, which the fixed branches already use without adding imports. The test depends on ActivityBuilder.setLaunchedFromPackage/setLaunchedFromUid and the mMockPackageManager mock in wmtests."
     ],
     "confidence": "high",
     "unknowns": [
      "The test comment is the only description of how launchedFromPackage diverges from launchedFromUid (startNextMatchingActivity()); the code for that path is not in the material.",
      "The material does not show which privileged consumers of launchedFromPackage made this a High severity EoP.",
      "The material does not show whether the two internal Cherrypick-From commits differ in content from the single AOSP commit.",
      "The material does not show getPackageUid's return value for a package that is not installed for the caller's user, so it is unclear how isSameApp evaluates in that case.",
      "For Android 17, the material does not show whether the fix commit itself is in the branch."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64783": {
     "cve": "CVE-2025-64783",
     "summary": "Moves platform/external/dng_sdk up to Adobe DNG SDK 1.7.1 build 2410. The upgrade touches 61 files: parsing, stream I/O, JPEG XL coding, opcodes and fingerprinting.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK: DNG/TIFF parsing, opcodes, JXL codec, fingerprinting)",
     "weakness": "unknown (bulletin type ID, information disclosure; the diff is a whole-SDK upgrade and does not single out one defect)",
     "root_cause": "The material does not identify the specific defect. The fix is a vendor SDK upgrade (commit message 'Update to DNG SDK 1.7.1 2410', Bug 465781139) with no explanation of the security issue. The memory-safety-relevant changes visible in the shown diff are:\n- dng_opcode_GainMap::ProcessArea (dng_gain_map.cpp) now walks rows and columns by precomputed counts, with both row and column pitch clamped to the overlap size.\n- The new tcGainMapMetadata_ISO_21496_1 parsing in dng_ifd::ParseTag caps the tag count at 4 MiB.\n- The fixed-size dng_fingerprint::data array is now private and copied through typed Get/Put/Process overloads.\n- Large changes to dng_jxl.cpp (I/O buffering, chunk readers and writers that use SafeUint32Mult and size limits), mostly truncated in the material. The dng_stream.cpp/.h diffs are not shown.",
     "fix_analysis": [
      "dng_gain_map.cpp:dng_opcode_GainMap::ProcessArea: rowPitch = Min_uint32(RowPitch, overlap.H()), colPitch = Min_uint32(ColPitch, overlap.W()). The loops now run over precomputed rows/cols counts, ceil(overlap/pitch), instead of open-ended pitch stepping.",
      "dng_ifd.cpp:dng_ifd::ParseTag: new tcGainMapMetadata_ISO_21496_1 case. It requires type ttUndefined, throws ThrowBadFormat if tagCount > 4 MiB, and reads into a block allocated to exactly that size. IsValidDNG now also accepts sfPreviewGainMap.",
      "dng_fingerprint.h/.cpp: fingerprint data made private, with explicit copy constructor/operator= and Data()/MutableData() accessors. dng_md5_printer::Process(ptr,len) renamed to ProcessPtr, plus typed Process(dng_fingerprint), Process_bool and Process_size. New dng_md5_printer_le_stream / dng_md5_direct_printer classes. Callers across the SDK (big_table, bmff, camera_profile, image_writer, ifd, jpeg_image, hue_sat_map, etc.) now use Get/Put/Process with a dng_fingerprint instead of raw .data with a hard-coded 16.",
      "dng_jxl.cpp: large rework (about 1200 lines) adding jxl_data_writer2 with a kMaxSize limit, plus jxl_image_chunk_reader / jxl_buffer_chunk_reader that size their allocations with SafeUint32Mult. Most of this is truncated in the material.",
      "Other: dng_info::ParseDNGPrivateData adds RICOH maker-note parsing. dng_info initializes fTIFFBlockOriginalOffset to 0. dng_sniffer_task destructor now catches exceptions. Exceptions now derive from std::exception, with verbose messages enabled."
     ],
     "completeness": [
      "The material cannot show which hunk addresses the CVE, so completeness against the specific flaw cannot be judged.",
      "The GainMap pitch clamping covers dng_opcode_GainMap::ProcessArea only. At android-security-15.0.0_r17, dng_opcode_MapTable::ProcessArea (dng_misc_opcodes.cpp) divides by RowPitch/ColPitch without the same clamping, and keeps a Google-specific plane-bound modification. The changes to dng_misc_opcodes.cpp (128 lines) are not shown in the excerpt.",
      "The changes to dng_stream.cpp (189 lines), dng_read_image.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_preview.cpp and dng_xmp.cpp are not visible in the provided excerpt."
     ],
     "behavior_changes": [
      "DNGs with a GainMapMetadata (ISO 21496-1) tag larger than 4 MiB are now rejected with a bad-format error. sfPreviewGainMap IFDs are accepted as valid.",
      "dng_exception now derives from std::exception and carries a message, because qDNGVerboseExceptions defaults to 1.",
      "Fingerprint inputs are now serialized through stream Put_* / Put_swap4 / Put_swap8 calls (many via dng_md5_printer_le_stream) instead of raw memory Process calls.",
      "The WriteTIFF/WriteDNG API signatures gain gain-map metadata and alternate-profile parameters, with defaults. WriteTIFF writes a gain map only when a metadata block is also supplied."
     ],
     "preconditions": [
      "Processing an untrusted DNG/TIFF file through dng_sdk, presumably on a decode path. The exact trigger is unknown.",
      "Index search found frameworks/base/core/jni/android_hardware_camera2_DngCreator.cpp (DngCreator_setup), which uses TiffWriter. That is a DNG write path, and its relation to the vulnerable code is unclear."
     ],
     "per_major": {
      "14": "Same fix: the 'Update to DNG SDK 1.7.1 2410' commit d0bfe8eeee73 in android-security-14.0.0_r25. 8/8 patched-region lines are present at r29.",
      "15": "Same fix: commit 45da10961287 in android-security-15.0.0_r13 (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8). 8/8 patched-region lines are present at r17, where the tree shows build 2502.",
      "16": "Same fix: commit 6b5cf2a88ebd in android-security-16.0.0_r4. 8/8 patched-region lines are present at r8.",
      "17": "Not affected per the bulletin (not listed). The patched region is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a whole-SDK drop, not a targeted patch. Backport it as a unit: the fingerprint API changes (private data, Process renamed to ProcessPtr) and the new WriteTIFF/WriteDNG parameters break any out-of-tree caller that uses fingerprint.data or the old dng_md5_printer::Process.",
      "Keep the Google-local modifications, such as the '// BEGIN GOOGLE MODIFICATION' plane loop in dng_opcode_MapTable::ProcessArea, when merging into forks."
     ],
     "confidence": "low",
     "unknowns": [
      "Which specific change in the SDK update fixes the CVE, and its trigger.",
      "Most of the dng_jxl.cpp diff and all of the dng_stream.cpp diff, plus several other files, are not shown in the material.",
      "Whether JXL decoding (libjxl) is built and reachable in Android's dng_sdk configuration.",
      "Whether the fingerprint serialization changes alter digest values compared with earlier builds.",
      "The pre-fix state of dng_opcode_MapTable::ProcessArea."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-opus-5-5",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64784": {
     "cve": "CVE-2025-64784",
     "summary": "Google fixed this by updating platform/external/dng_sdk wholesale to Adobe DNG SDK 1.7.1 build 2410. The update changes 61 files and adds stricter validation in several parsing and processing paths. The material does not single out which hunk is the security fix.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK: DNG/TIFF parsing, opcodes, JPEG XL, fingerprinting)",
     "weakness": "unknown (bulletin type is Information Disclosure; the diff is a vendor SDK update, not a targeted patch)",
     "root_cause": "Not identifiable precisely from the material. The commit is a full SDK version bump (\"Update to DNG SDK 1.7.1 2410\", Bug 465781139) and has no targeted security hunk. Changed areas that could plausibly relate include dng_opcode_GainMap::ProcessArea in dng_gain_map.cpp, the row/column opcode ProcessArea functions in dng_misc_opcodes.cpp, dng_stream.cpp, dng_pixel_buffer.cpp, dng_linearization_info.cpp and dng_jxl.cpp. The diffs for most of these are truncated or not shown, so the actual flaw is unknown.",
     "fix_analysis": [
      "dng_gain_map.cpp:dng_opcode_GainMap::ProcessArea: both row pitch and column pitch are now clamped to the overlap size (Min_uint32); previously only the column pitch was clamped. The code precomputes the row and column iteration counts as ceil(size/pitch) and loops on those counts instead of stepping row/col against overlap bounds with the raw pitch.",
      "dng_ifd.cpp:dng_ifd::ParseTag: adds parsing of the new tcGainMapMetadata_ISO_21496_1 tag. The tag must be ttUndefined, its count is capped at 4 MiB (ThrowBadFormat if larger), and the data is read into a host-allocated block of exactly tagCount bytes.",
      "dng_fingerprint.h/.cpp: dng_fingerprint::data becomes private behind Data()/MutableData(). dng_md5_printer::Process(void*,len) is renamed to ProcessPtr, and the dng_md5_printer constructor is now protected. Explicit dng_md5_printer_le_stream and dng_md5_direct_printer classes are added, and call sites across the SDK switch to the typed Get/Put/Process(dng_fingerprint) overloads instead of raw 16-byte copies.",
      "dng_rgb_to_rgb_table_data::AddDigest and other digest code: digest input is now serialized with explicit endian handling (Put_uint32, Put_swap4/8, Put_real32/64) instead of hashing raw host memory.",
      "Other changes in dng_stream.cpp/.h, dng_misc_opcodes.cpp, dng_pixel_buffer.cpp, dng_linearization_info.cpp, dng_resample.cpp, dng_jxl.cpp (about 1200 lines, including new chunked JXL reader/writer classes with SafeUint32Mult size computation) and dng_read_image.cpp are part of the update. Their full diffs are truncated in the material."
     ],
     "completeness": [
      "The fix is the whole vendor SDK update, so coverage depends on Adobe's upstream changes. The material does not show which change addresses the CVE.",
      "On Android 15 (android-security-15.0.0_r17), the code-context header dng_flags.h shows kDNGSDK_BuildVersion 2502. That indicates a later SDK update has landed on top of 2410 in that branch."
     ],
     "behavior_changes": [
      "dng_exception now derives from std::exception and carries a message. With qDNGVerboseExceptions (default 1), it includes the message and sub_message.",
      "ThrowBadFormat when a GainMapMetadata tag is larger than 4 MiB. New handling for the sfPreviewGainMap subfile type in IsValidDNG.",
      "JPEG compression with RGB and 3 or 4 samples per pixel is now treated as baseline JPEG (dng_ifd::IsBaselineJPEG). A RICOH DNGPrivateData parse path is added.",
      "Many fingerprint and digest computations now use dng_md5_printer_le_stream or explicit Put_* serialization rather than hashing raw host memory. Some paths, such as tile, JPEG and IPTC digests, use dng_md5_direct_printer instead."
     ],
     "preconditions": [
      "unknown: the specific trigger, input format and reachable entry points are not shown in the material."
     ],
     "per_major": {
      "14": "Same SDK 1.7.1 2410 update, platform/external/dng_sdk d0bfe8eeee73 in android-security-14.0.0_r25; patched region present at r29.",
      "15": "Same update, 45da10961287 in android-security-15.0.0_r13; patched region present at r17, where the SDK build is already 2502.",
      "16": "Same update, 6b5cf2a88ebd in android-security-16.0.0_r4; patched region present at r8.",
      "17": "Not listed by the bulletin; patched region already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a wholesale vendor SDK bump with API changes: fingerprint data is now private, Process is renamed to ProcessPtr, the dng_md5_printer constructor is protected, and WriteTIFF/WriteDNG take new parameters. Out-of-tree code calling dng_sdk may need adapting. Taking the full update is safer than cherry-picking hunks, since the security-relevant hunk is not identified."
     ],
     "confidence": "low",
     "unknowns": [
      "Which hunk of the SDK update fixes CVE-2025-64784.",
      "The trigger file structure and affected code path.",
      "Which Android components link dng_sdk and reach the affected code.",
      "Whether the digest serialization changes alter resulting digest values.",
      "Full diffs of dng_stream.cpp, dng_misc_opcodes.cpp, dng_pixel_buffer.cpp, dng_linearization_info.cpp and dng_jxl.cpp, which are truncated in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-opus-5-5",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64893": {
     "cve": "CVE-2025-64893",
     "summary": "Rebases platform/external/dng_sdk onto Adobe DNG SDK 1.7.1 build 2410. This is a 61-file vendor update, and the material does not point to any single hunk as the security fix.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK: DNG/TIFF parsing, opcodes, JPEG XL, fingerprinting)",
     "weakness": "unknown (the bulletin type is information disclosure; the material does not identify which hunk is the security fix)",
     "root_cause": "The material does not identify the root cause. Visible hunks that touch bounds handling: (1) dng_gain_map.cpp dng_opcode_GainMap::ProcessArea: the old row loop was `for (int32 row = overlap.t; row < overlap.b; row += fAreaSpec.RowPitch ())`. colPitch was clamped with Min_uint32 to the overlap width, but the row step used RowPitch unclamped. Whether this loop is the reported bug is not shown. (2) dng_ifd.cpp dng_ifd::ParseTag adds a new tag case, tcGainMapMetadata_ISO_21496_1, with a size limit. (3) Large rewrites in dng_jxl.cpp, dng_stream.cpp/.h, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp and dng_resample.cpp are truncated in the material and cannot be assessed.",
     "fix_analysis": [
      "dng_gain_map.cpp dng_opcode_GainMap::ProcessArea: rowPitch and colPitch are now clamped with Min_uint32 to overlap.H() and overlap.W(). The loops run over precomputed counts, rows = ceil(H/rowPitch) and cols = ceil(W/colPitch), using unsigned indices (rowIdx, colIdx). Rows still advance by row += rowPitch, but with the clamped pitch and a fixed iteration count.",
      "dng_ifd.cpp dng_ifd::ParseTag: the new tcGainMapMetadata_ISO_21496_1 case checks the tag type is ttUndefined, throws ThrowBadFormat when tagCount exceeds 4 MiB, then allocates through host.Allocate and reads tagCount bytes.",
      "dng_ifd.cpp IsValidDNG and IsBaselineJPEG: accept sfPreviewGainMap, and treat JPEG RGB images with 3 or 4 samples per pixel as baseline.",
      "dng_fingerprint.h/.cpp: dng_fingerprint::data is now private behind Data()/MutableData(). dng_md5_printer::Process(void*,len) is renamed ProcessPtr and made virtual. New dng_md5_printer_le_stream and dng_md5_direct_printer classes are added, and digest code across the SDK now writes typed values through stream Put_* calls.",
      "dng_exceptions.h: dng_exception now derives from std::exception and carries a message (qDNGVerboseExceptions defaults to 1). dng_sniffer_task's destructor is now virtual and swallows exceptions from EndTask.",
      "dng_jxl.cpp (+1217/-), dng_stream.cpp (+189), dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_resample.cpp, dng_read_image.cpp: changed, but the diffs are truncated in the material."
     ],
     "completeness": [
      "Because this is a whole-SDK rebase, Android picks up every upstream change in build 2410 together. Whether it fully covers the reported bug cannot be judged from the material.",
      "In the GainMap opcode, loop counts now come from the overlap dimensions with clamped pitches, and planes are bounded by Plane+Planes and buffer.Planes(). Whether the overlap is always inside the buffer is not shown.",
      "dng_misc_opcodes.cpp dng_opcode_MapTable::ProcessArea still carries a local 'BEGIN GOOGLE MODIFICATION' plane-bound check at r17, so that Google-local change was kept."
     ],
     "behavior_changes": [
      "A gain map metadata tag (tcGainMapMetadata_ISO_21496_1) with a count above 4 MiB now causes a bad-format error.",
      "dng_exception::what() now returns a message string that may include the throw site's message text.",
      "Fingerprint (MD5) code now hashes typed values through stream Put_* calls with explicit endianness setup.",
      "DNG files with sfPreviewGainMap subfiles are now accepted as valid. TIFF files with JPEG + RGB (3 or 4 samples) are treated as baseline JPEG.",
      "New writer parameters (gainMapMetadataBlock, gainMapAltProfileData/Size) are added to WriteTIFF and WriteDNG, with default arguments."
     ],
     "preconditions": [
      "The changed parsing code is reached when dng_sdk parses a DNG/TIFF file, for example through opcode lists or IFD tags. Which Android process does this is not shown.",
      "Per index search, frameworks/base/core/jni/android_hardware_camera2_DngCreator.cpp uses TiffWriter for DNG output. It is not shown calling the patched dng_sdk parsing paths."
     ],
     "per_major": {
      "14": "Same SDK update: commit d0bfe8eeee73 'Update to DNG SDK 1.7.1 2410', in android-security-14.0.0_r25. The patched region is present at r29.",
      "15": "Same fix: commit 45da10961287 (cherry-picked, Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8), in android-security-15.0.0_r13. The patched region is present at r17. At r17, dng_flags.h shows kDNGSDK_BuildVersion 2502, which suggests a later SDK update.",
      "16": "Same fix: commit 6b5cf2a88ebd, in android-security-16.0.0_r4. The patched region is present at r8.",
      "17": "Not affected per the bulletin (Android 17 is not listed). The patched region is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply this as a whole-library rebase rather than cherry-picking individual hunks. The API changes are breaking: dng_fingerprint::data is now private, dng_md5_printer::Process(void*,len) became ProcessPtr, AddDigest now takes dng_md5_printer_stream, and the WriteTIFF/WriteDNG signatures changed. Out-of-tree code using these APIs must be updated, and Google-local modifications (for example the MapTable plane check in dng_misc_opcodes.cpp) must be kept."
     ],
     "confidence": "low",
     "unknowns": [
      "Which hunk or hunks of the SDK update fix the bug referenced by Bug 465781139.",
      "Whether the unclamped RowPitch step in the old GainMap loop could actually be reached with harmful values.",
      "Contents of the truncated diffs (dng_jxl.cpp, dng_stream.cpp/.h, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_resample.cpp, dng_read_image.cpp, dng_negative.cpp, dng_xmp*).",
      "Whether existing fingerprint values change on Android after the digest refactor.",
      "Which Android component parses untrusted DNG files with this library, and with what privileges."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-opus-5-5",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0005": {
     "cve": "CVE-2026-0005",
     "summary": "Adds a permission-guarded IActivityTaskManager.rebuildSystemLockTaskPinnedMode(). It re-applies app pinning (status bar disable, keyguard disable, lock task listeners, recents) from the state LockTaskController still holds. SystemUI's KeyguardViewMediator calls it when the system reports LOCK_TASK_MODE_PINNED. The fix also keeps the keyguard enabled/disabled state across a keyguard service disconnection, so app pinned mode is kept after a SystemUI crash.",
     "component": "platform/frameworks/base, WindowManager lock task / app pinning (LockTaskController, ActivityTaskManagerService), SystemUI KeyguardViewMediator, KeyguardServiceDelegate",
     "weakness": "Missing state restoration after a process restart: when SystemUI crashed, the pinned mode restrictions applied through it were lost while system_server still considered pinning active (inconsistent security state).",
     "root_cause": "LockTaskController in system_server tracks app pinning (LOCK_TASK_MODE_PINNED) in mLockTaskModeState and mLockTaskModeTasks. Its enforcement is applied when lock task mode starts: status bar disable via setStatusBarState, keyguard disable via setKeyguardState, and notifyLockTaskModeChanged to task stack listeners. The test comments say these calls happen once for startLockTaskMode and once more for the rebuild. Before the fix, nothing re-applied this state after SystemUI restarted, while LockTaskController still reported pinned mode. Separately, KeyguardServiceDelegate.onServiceDisconnected called mKeyguardState.reset(), which discarded the keyguard enabled flag. The reconnect path only replays setKeyguardEnabled when !mKeyguardState.enabled, so a disabled keyguard was not re-disabled after reconnect.",
     "fix_analysis": [
      "core/java/android/app/IActivityTaskManager.aidl: adds a new binder method rebuildSystemLockTaskPinnedMode() next to startSystemLockTaskMode/stopSystemLockTaskMode.",
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java:rebuildSystemLockTaskPinnedMode: calls enforceTaskPermission(\"rebuildSystemLockTaskPinnedMode\"), clears the calling identity, and under mGlobalLock calls getLockTaskController().rebuildSystemLockTaskPinnedMode(). This mirrors startSystemLockTaskMode.",
      "services/core/java/com/android/server/wm/LockTaskController.java:rebuildSystemLockTaskPinnedMode: returns early, with a Slog, if mLockTaskModeState != LOCK_TASK_MODE_PINNED or mLockTaskModeTasks is empty. Otherwise it takes the first locked task and calls mSupervisor.mRecentTasks.onLockTaskModeStateChanged(LOCK_TASK_MODE_PINNED, task.mUserId). It then posts to mHandler: showPinningEnterExitToast(true), mTaskChangeNotificationController.notifyLockTaskModeChanged(state), setStatusBarState(state, userId) and setKeyguardState(state, userId). It does not change the controller's own state.",
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java: in the block that handles mBootSendUserPresent / sendUserPresentBroadcast, posts a runnable. The runnable queries mActivityTaskManagerService.getLockTaskModeState() and, if it is LOCK_TASK_MODE_PINNED, calls rebuildSystemLockTaskPinnedMode(). A RemoteException is logged.",
      "services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java:onServiceDisconnected: saves mKeyguardState.enabled before mKeyguardState.reset() and restores it afterwards. The reconnect path can then re-apply setKeyguardEnabled(false) if the keyguard had been disabled.",
      "services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java: adds four tests: no-op when there are no locked tasks, no-op for a DONT_LOCK task, no-op in non-pinned mode, and a full rebuild in pinned mode. In the rebuild test, notify, disableKeyguard, status bar disable/disable2 and recents are each called twice."
     ],
     "completeness": [
      "Covers only LOCK_TASK_MODE_PINNED. rebuildSystemLockTaskPinnedMode returns for any other state, so this change does not re-send full lock task mode (LOCK_TASK_MODE_LOCKED) status bar / keyguard state. The material does not show whether that mode is affected.",
      "The rebuild is triggered only from SystemUI's KeyguardViewMediator. No framework-side trigger is added, so re-application depends on SystemUI reaching that code after it restarts.",
      "The rebuild uses only mLockTaskModeTasks.getFirst() for the user id.",
      "KeyguardServiceDelegate preserves only the enabled flag across reset(). Other fields are still reset as before."
     ],
     "behavior_changes": [
      "New IActivityTaskManager binder method rebuildSystemLockTaskPinnedMode(), guarded by enforceTaskPermission (the same check as startSystemLockTaskMode/stopSystemLockTaskMode).",
      "When the rebuild runs in pinned mode, the pinning enter toast is shown again, the status bar is disabled again, the keyguard is disabled again, and recents and task stack listeners receive another onLockTaskModeStateChanged / notifyLockTaskModeChanged callback.",
      "KeyguardServiceDelegate now keeps the keyguard enabled state across a keyguard service disconnection, so a keyguard that was disabled before the disconnection is disabled again on reconnect."
     ],
     "preconditions": [
      "The device is in app pinning mode (LockTaskController.mLockTaskModeState == LOCK_TASK_MODE_PINNED, with a task in mLockTaskModeTasks).",
      "SystemUI crashes while pinning is active (per the commit title). KeyguardServiceDelegate.onServiceDisconnected and the reconnect path are involved."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base 9ff1c081e9f7 in android-security-14.0.0_r25. It touches the same five source files plus the test, with three differences: KeyguardViewMediator uses ActivityTaskManager.getService() instead of the mActivityTaskManagerService field, LockTaskController uses mLockTaskModeTasks.get(0) instead of getFirst(), and the tests use verifyNoInteractions(mSupervisor). The patched region is fully present at android-security-14.0.0_r29.",
      "15": "this fix: platform/frameworks/base 08abd0bc0451 in android-security-15.0.0_r13 (cherry-pick of 575845147cb4, RESTRICT AUTOMERGE). The patched region is fully present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 92b109b60414 in android-security-16.0.0_r4. The content matches the 15 change apart from line offsets and context. The patched region is fully present at android-security-16.0.0_r8.",
      "17": "not affected: Android 17 is not listed by the bulletin. The patched region is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change spans the framework side (IActivityTaskManager.aidl, ActivityTaskManagerService, LockTaskController, KeyguardServiceDelegate) and SystemUI (KeyguardViewMediator), all in frameworks/base. SystemUI calls the new binder method, so the AIDL method and both sides must ship together. The Android 14 variant shows the adaptation for trees without the KeyguardViewMediator mActivityTaskManagerService field: it uses ActivityTaskManager.getService(), and mLockTaskModeTasks.get(0) instead of getFirst()."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how SystemUI comes to crash or what information becomes accessible once pinning restrictions are lost. The bulletin only rates the issue High / ID.",
      "The diff context does not show which method encloses the KeyguardViewMediator hunk (it sits next to mBootSendUserPresent / sendUserPresentBroadcast). It is also not shown whether reset() preserves bootCompleted, so it is not confirmed that the rebuild call runs after a SystemUI restart via a replayed onBootCompleted.",
      "The material does not show whether full lock task mode (LOCK_TASK_MODE_LOCKED) loses state in the same way when SystemUI restarts."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0011": {
     "cve": "CVE-2026-0011",
     "summary": "Settings.enableSystemPackageLPw and addPackageLPw in PackageManager Settings now reuse the disabled PackageSetting when a shared-UID system package is re-enabled. A new hasSharedUser parameter bypasses the failing mAppIds.registerExistingAppId check, and sharedUserAppId is copied over. Before the fix, the preload was re-scanned as a brand-new app, which per the commit message could change the app's UID when the manifest has sharedUserMaxSdkVersion.",
     "component": "platform/frameworks/base, PackageManagerService Settings (system app update uninstall / re-enable path)",
     "weakness": "Logic error in package state restoration that leads to inconsistent UID assignment. addPackageLPw did not handle the shared-UID case.",
     "root_cause": "When an update to a system app is uninstalled, Settings.enableSystemPackageLPw takes the PackageSetting saved in mDisabledSysPackages and calls addPackageLPw to re-register it. addPackageLPw builds a new PackageSetting with setAppId(uid) and stores it in mPackages only if mAppIds.registerExistingAppId(uid, p, name) succeeds (or in the SDK-library INVALID_UID case). Per the commit message, registerExistingAppId fails for a shared-uid package, so the disabled setting is not reused. Even so, enableSystemPackageLPw removes it from mDisabledSysPackages. Per the commit message, the preloaded version is then re-scanned and installed as a new app. When the manifest has sharedUserMaxSdkVersion, this can change the app's UID, which differs from the non-shared-UID path that reuses the disabled setting. The sharedUserAppId was also not copied to the recreated setting.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/Settings.java:addPackageLPw gains a boolean hasSharedUser parameter. The condition that stores the new PackageSetting in mPackages becomes `(uid == INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps()) || mAppIds.registerExistingAppId(uid, p, name) || hasSharedUser`. A shared-UID package is therefore kept even when registerExistingAppId fails.",
      "services/core/java/com/android/server/pm/Settings.java:enableSystemPackageLPw passes p.hasSharedUser() to addPackageLPw. On success it copies ret.setSharedUserAppId(p.getSharedUserAppId()).",
      "services/core/java/com/android/server/pm/Settings.java: the settings-parsing caller around line 4197 (it logs \"Reading package\"; the diff does not name the enclosing function) passes /* hasSharedUser= */ false, which keeps its previous behavior.",
      "services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt: the Mockito stub for settings.addPackageLPw is updated to the 9-argument signature (test only)."
     ],
     "completeness": [
      "The fix changes only the enableSystemPackageLPw -> addPackageLPw path. The settings-parsing caller passes hasSharedUser=false, so its behavior is unchanged.",
      "registerExistingAppId is still called before the hasSharedUser short-circuit. Any side effects it has on failure are not changed by the diff.",
      "The diff sets sharedUserAppId on the recreated setting but does not itself add the new setting to the SharedUserSetting's package list. The material does not show whether a caller does this.",
      "The disabled setting is still removed from mDisabledSysPackages unconditionally, even if addPackageLPw does not return a setting for other reasons. The fix does not change this."
     ],
     "behavior_changes": [
      "Re-enabling a disabled system package that has a shared user now stores the new PackageSetting in mPackages and returns it. The disabled setting is reused instead of the preload being scanned as a new install.",
      "The re-enabled PackageSetting now carries the original sharedUserAppId.",
      "Internal signature change: Settings.addPackageLPw takes an extra boolean hasSharedUser. The two callers in Settings.java and the MockSystem test stub are updated in the diff."
     ],
     "preconditions": [
      "A preloaded system app with a shared user has an installed update, so its original setting is in mDisabledSysPackages.",
      "The update is uninstalled, which drives Settings.enableSystemPackageLPw for that package.",
      "Per the commit message, the UID change shows up when the manifest also declares sharedUserMaxSdkVersion. The material does not show how an unprivileged party could trigger this."
     ],
     "per_major": {
      "14": "Adapted fix (commit 77d8e59a1af6 in android-security-14.0.0_r25). Android 14 has the older addPackageLPw signature with many explicit parameters and no isSdkLibrary/Flags clause. The fix appends hasSharedUser after domainSetId, adds `|| hasSharedUser` to the registerExistingAppId check, copies setSharedUserAppId in enableSystemPackageLPw, and has the settings-parsing caller pass false. The MockSystem.kt stub is widened to 18 arguments. 7 of 8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix as described (commit 65e7256522b4 in android-security-15.0.0_r13). 7 of 8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix with identical hunks at slightly different line offsets (commit 51368785bd09 in android-security-16.0.0_r4). 7 of 8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). 7 of 8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. Across versions, the only adaptation is the addPackageLPw parameter list: Android 14 uses the long explicit-parameter form without isSdkLibrary and without the Flags.disallowSdkLibsToBeApps() clause. Any OEM-added callers of addPackageLPw need the new boolean, and the number of nullable() matchers in the MockSystem.kt stub must match the new arity."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact EoP consequence of the UID change is not shown (which UID the re-scanned package gets and what privileges that gives). The material has only the bulletin rating and the commit message.",
      "Why registerExistingAppId fails for shared-uid packages, and the exact return value of addPackageLPw on that path, are not shown.",
      "Whether the caller of enableSystemPackageLPw adds the recreated setting to the SharedUserSetting's package list is not in the material.",
      "The material does not explain the 7/8-line match in every latest tag. The Android 15 r17 code context does show the fixed lines in enableSystemPackageLPw.",
      "Whether there are other callers of addPackageLPw beyond the two in the diff. The index search found none, but it may be incomplete."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0012": {
     "cve": "CVE-2026-0012",
     "summary": "In SystemUI's ExpandableNotificationRow.setHideSensitive, the fix makes the row take the immediate (non-animated) visibility switch when the public (redacted) layout should be shown and the showing-public state has not changed, instead of running the hide-sensitive animation; on Android 16 it narrows an existing flag-gated skip to that same condition.",
     "component": "platform/frameworks/base, SystemUI notification row (packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java)",
     "weakness": "Logic error in deciding when to bypass the hide-sensitive animation between the public and private notification layouts; a more specific CWE class is not evident from the diff. Bulletin type: ID.",
     "root_cause": "ExpandableNotificationRow.setHideSensitive decides whether a sensitive row shows its public (redacted) or private layout. On Android 14/15, when animated was true the code always went to the animated path (animateShowingPublic on 15) unless the early return fired, even when mShowingPublic is true and unchanged. Given the early return on mShowingPublicInitialized && unchanged, that is the not-yet-initialized case. On Android 16 the skip condition was Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged, which skipped the animation regardless of mShowingPublic. The commit message calls the existing code too aggressive and says it can cause 0 content alpha. In both cases the decision to bypass the hide-sensitive animation did not depend on whether the public layout is the one being shown.",
     "fix_analysis": [
      "ExpandableNotificationRow.setHideSensitive (Android 15, commit 679801a99ccd): introduces boolean isShowingLayoutNotChanged = (mShowingPublic == oldShowingPublic) and reuses it in the existing early return (mShowingPublicInitialized && isShowingLayoutNotChanged).",
      "Same function: adds final boolean shouldSkipHideSensitiveAnimation = mShowingPublic && isShowingLayoutNotChanged and changes the branch to if (!animated || shouldSkipHideSensitiveAnimation). When true, the code cancels pending animations on mPublicLayout, mPrivateLayout and mChildrenContainer, calls resetAllContentAlphas(), sets mPublicLayout visibility directly (VISIBLE when mShowingPublic), calls updateChildrenVisibility(), and, if NotificationContentAlphaOptimization is enabled and oldAlpha != 1f, restores oldAlpha on the now-showing layout. In that case the else branch (animateShowingPublic) is no longer reached.",
      "Android 16 (commit ef2a8e203164): the skip already existed as Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged. The fix replaces the flag check with mShowingPublic, so the animation is skipped only when the public layout is the one being shown and the state is unchanged."
     ],
     "completeness": [
      "Only setHideSensitive in ExpandableNotificationRow.java is changed. animateShowingPublic and other paths that toggle public/private layout visibility are untouched.",
      "The new skip applies only when mShowingPublic is true and unchanged. When mShowingPublic is false and unchanged (not-initialized case), or whenever the state flips, the animated path still runs if animated is true.",
      "Because the early return already handles mShowingPublicInitialized && isShowingLayoutNotChanged, the new skip condition can only be true before mShowingPublicInitialized is set. The material does not show where that flag is set.",
      "The GONE early return is unchanged."
     ],
     "behavior_changes": [
      "Android 14/15: when a sensitive row should show its public layout and that state has not changed, the switch now goes through the non-animated branch instead of the animated one. On 15 this means animations are cancelled, alphas are reset and mPublicLayout is set VISIBLE.",
      "Android 16: rows that are not showing the public layout no longer skip the hide-sensitive animation when the state is unchanged. The commit message says the old skip could leave content at 0 alpha. Flags.skipHideSensitiveNotifAnimation() no longer controls the skip."
     ],
     "preconditions": [
      "A notification row with mSensitive true whose visibility is not GONE.",
      "setHideSensitive called with animated=true such that mShowingPublic is true and equal to its previous value. From the code, this requires mShowingPublicInitialized to be false (otherwise the early return fires).",
      "The exact UI sequence that triggers the problematic transition is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix (334c15a0ebee in android-security-14.0.0_r25): introduces the same isShowingLayoutNotChanged and shouldSkipHideSensitiveAnimation logic (no prior skip existed). The hunk context differs from 15 (no oldAlpha line). 6/7 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "fix as analysed (679801a99ccd in android-security-15.0.0_r13): introduces isShowingLayoutNotChanged and shouldSkipHideSensitiveAnimation. 7/8 lines present at android-security-15.0.0_r17, and the code context there shows the fixed form.",
      "16": "adapted fix (ef2a8e203164 in android-security-16.0.0_r4): one-line change replacing Flags.skipHideSensitiveNotifAnimation() with mShowingPublic in an existing skip condition. The surrounding branch differs (it opens with a NotificationContentAlphaOptimization.isEnabled() || mShowingPublic != oldShowingPublic check). 4/5 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); 6/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Branches that already contain the flag-gated skip (Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged, as on 16) need only the one-line condition change. Branches without it (14/15) need the full hunk that introduces isShowingLayoutNotChanged and shouldSkipHideSensitiveAnimation, adapted to that branch's surrounding context.",
      "No fork comparison section was provided in the material, so no per-file fork verdict can be given."
     ],
     "confidence": "medium",
     "unknowns": [
      "How the pre-fix behavior exposes information. The bulletin states only type ID, severity High; the commit message mentions only the animation and 0 content alpha.",
      "Where mShowingPublicInitialized is set and the initial value of mShowingPublic, which determine when the new skip condition is reachable.",
      "Whether NotificationContentAlphaOptimization and the oldAlpha handling exist on Android 14.",
      "Whether the Android 17 code contains this exact change (only a partial line-match count is given)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0013": {
     "cve": "CVE-2026-0013",
     "summary": "PickActivity.setupLayout now clears the Intent selector (moreApps.setSelector(null)) on the copy of the incoming intent that DocumentsUI reuses to show other apps that can handle the request, so a selector supplied by the caller can no longer be used to launch an arbitrary activity with DocumentsUI's permissions.",
     "component": "platform/packages/apps/DocumentsUI, picker (PickActivity / \"more apps\" intent forwarding)",
     "weakness": "Incomplete sanitization of a caller-controlled Intent before re-launching it (intent redirection via Intent selector)",
     "root_cause": "In src/com/android/documentsui/picker/PickActivity.java, setupLayout(Intent intent) builds `moreApps = new Intent(intent)` from the incoming intent. It then removes only the explicit target, by calling setComponent(null) and setPackage(null). The selector that the copy constructor carries over was never cleared. When an Intent has a selector, the system uses the selector for resolution instead of the main intent (general platform knowledge). moreApps is then passed to mUserManagerState.getCanForwardToProfileIdMap or CrossProfileUtils.getCrossProfileResolveInfo, and to RootsFragment.show(..., includeApps, moreApps). The commit message states that the selector could be used to launch an arbitrary activity with the permissions of DocumentsUI.",
     "fix_analysis": [
      "src/com/android/documentsui/picker/PickActivity.java:setupLayout: the fix adds moreApps.setSelector(null), with a comment, right after moreApps.setComponent(null) and moreApps.setPackage(null). This removes the caller-controlled selector from the copied intent before the copy is used for cross-profile resolution and before it is passed to RootsFragment.show.",
      "The change adds two lines in one file. No other files or call sites are touched."
     ],
     "completeness": [
      "The fix covers the one copy of the incoming intent made in PickActivity.setupLayout. Every downstream user of moreApps in this method (the cross-profile resolution and RootsFragment.show) gets the sanitized copy, because the selector is cleared right after the copy is created.",
      "The original incoming intent (getIntent()) still has its selector. The material does not show whether any other DocumentsUI code path re-launches or re-resolves the original intent or another copy of it, so those paths are not shown to be covered.",
      "The fix does not clear any other resolution-affecting fields (for example ClipData or flags). The material gives no sign that they are relevant to this issue."
     ],
     "behavior_changes": [
      "Any selector the calling app sets on the picker intent is now dropped from moreApps, which is used for the 'more apps' list and for cross-profile resolution. No new error or user-visible message is added."
     ],
     "preconditions": [
      "An intent with a selector attached reaches DocumentsUI's PickActivity, and setupLayout builds moreApps from it.",
      "RootsFragment.show receives moreApps for the State actions ACTION_GET_CONTENT, ACTION_OPEN, ACTION_CREATE, ACTION_OPEN_TREE and ACTION_PICK_COPY_DESTINATION, with includeApps=true only for ACTION_GET_CONTENT. The cross-profile branches use moreApps only when mState.supportsCrossProfile is set.",
      "The 'more apps' flow implies that the user interacts with the resulting app entry in the picker, but the material does not show this."
     ],
     "per_major": {
      "14": "Same fix, with different surrounding code. platform/packages/apps/DocumentsUI a48245c5bfa7 in android-security-14.0.0_r25 adds the same two lines after setPackage(null). The surrounding code differs: Android 14 has the older supportsCrossProfile() / getCrossProfileResolveInfo(getPackageManager(), moreApps) code. The patched region is present 6/6 at android-security-14.0.0_r29.",
      "15": "Same fix: 15f898d5082a in android-security-15.0.0_r13, a cherry-pick of 32d6a7338dc3, adds the same two lines. The patched region is present 6/6 at android-security-15.0.0_r17.",
      "16": "Same fix: 9f2d3f09f8fd in android-security-16.0.0_r4 adds the same two lines, with the same diff context as on 15. The patched region is present 6/6 at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin, which does not list 17. At android-security-17.0.0_r1, 5/6 lines of the patched region are present. The material does not show which line differs or whether setSelector(null) is present."
     },
     "backport_notes": [
      "The fix is a self-contained two-line insert right after moreApps.setPackage(null) in PickActivity.setupLayout, with no dependencies. On forks where the cross-profile block differs (as on 14), the insert can be placed using only the lines new Intent(intent), setComponent(null) and setPackage(null). Intent.setSelector exists since API 15, so no SDK gating is needed (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "How RootsFragment launches the selected app entry (the actual startActivity path); the material does not show it.",
      "Whether other DocumentsUI code re-launches the original incoming intent or another copy of it without clearing the selector.",
      "Where setupLayout is called from, and how the State actions map to public Intent actions.",
      "The exact content of PickActivity.java on Android 17 (the material gives only a 5/6 line-match count), including whether setSelector(null) is present there."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0014": {
     "cve": "CVE-2026-0014",
     "summary": "Adds an early `Process.isSdkSandboxUid(uid)` check to the AppOpsService helper that decides whether a UID is trusted for attribution tag validation, so SDK sandbox UIDs are always treated as untrusted.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop/AppOpsService.java)",
     "weakness": "Improper trust classification of the caller UID (missing check), so attribution tag validation is skipped for SDK sandbox UIDs; the bulletin classifies the impact as DoS",
     "root_cause": "In AppOpsService.java, a helper decides whether a UID counts as a trusted (\"system\") app for attribution tag validation. It returns true for a null packageName and for appIds below Process.FIRST_APPLICATION_UID, then runs further checks that the material does not show. It had no handling for SDK sandbox UIDs. According to the commit message, this meant sandboxed UIDs could be classified as \"system\" for attribution tag validation. The bulletin classifies the impact as DoS, and the test name AppOpsMemoryUsageTest points to a memory-usage concern, but the material does not show the exact mechanism.",
     "fix_analysis": [
      "services/core/java/com/android/server/appop/AppOpsService.java, in the trusted-UID helper used for attribution tag validation (the hunk header does not show the function name): a new `if (Process.isSdkSandboxUid(uid)) return false;` is inserted after the `packageName == null` early return and before the `appId < Process.FIRST_APPLICATION_UID` check. Any SDK sandbox UID with a non-null packageName is now reported as untrusted, so the later trust checks in the function can no longer classify it as a system app.",
      "This is a single-commit fix: 3 lines added, no other files changed. Because the sandbox check sits before the appId-based trust path and any later checks, it skips all of them for sandbox UIDs."
     ],
     "completeness": [
      "The fix covers exactly one classification: UIDs for which Process.isSdkSandboxUid() is true now return false from this helper.",
      "The check comes after the `packageName == null` early return, so a sandbox UID with a null packageName still returns true (trusted) from this function. The material does not show whether that combination can be reached.",
      "Only this one helper in AppOpsService changes. The material does not show the rest of the function body or other call sites that make trust decisions, so nothing can be said about sibling paths.",
      "The diff does not change how many attribution tags are stored and adds no cap on AppOps memory. The fix relies on the existing validation for untrusted callers now applying to sandbox UIDs."
     ],
     "behavior_changes": [
      "Calls into AppOps from SDK sandbox UIDs (with a non-null packageName) are no longer treated as system for attribution tag validation. They now go through the same validation as untrusted apps.",
      "Behavior for non-sandbox UIDs is unchanged."
     ],
     "preconditions": [
      "The caller runs under an SDK sandbox UID (Process.isSdkSandboxUid(uid) is true) and reaches an AppOps path that performs attribution tag validation through this helper with a non-null packageName.",
      "The material does not show the exact entry points (which AppOps APIs call this helper)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 7320b5316d76 in android-security-14.0.0_r25, the same 3-line insertion at a different offset (hunk at line 3959); 3/3 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 531f3ac25912 in android-security-15.0.0_r13, the same 3-line insertion (hunk at line 4718); 3/3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base b51a58ecec96 in android-security-16.0.0_r4, the same 3-line insertion (hunk at line 5043); 3/3 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; the 3/3 patched lines are already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix is a self-contained 3-line insertion that applied identically on 14, 15 and 16; only the line offsets differ. It depends on Process.isSdkSandboxUid, which has existed since the SDK sandbox arrived in Android 13 (general platform knowledge). To place it, anchor on the `if (packageName == null) { return true; }` block followed by `int appId = UserHandle.getAppId(uid);`."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the name of the patched helper function or the rest of its body (which later checks could have classified a sandbox UID as system).",
      "The material does not show which AppOps entry points call this helper or how attribution tag validation uses its result.",
      "How the DoS works (what state grows, or what goes wrong, when validation is skipped) is only hinted at by the test name AppOpsMemoryUsageTest; the diff does not show it.",
      "The material does not show what a sandbox caller now sees when attribution tag validation fails (an exception, a dropped or replaced tag, or something else)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0015": {
     "cve": "CVE-2026-0015",
     "summary": "AppOpsService now accepts an attribution tag taken from the proxy's manifest (or via the system-proxy shortcut) only when the proxy is trusted, meaning the op flags lack OP_FLAG_UNTRUSTED_PROXIED. It also checks that a proxy package name actually resolves to the proxy uid before treating it as system, stops the dry-run start path from caching attribution tag information, and makes the finish path find the right AttributedOp by in-progress clientId instead of creating entries.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop: AppOpsService.java, AttributedOp.java)",
     "weakness": "Missing trust/ownership validation of proxy-supplied attribution tags, leading to unbounded state growth (resource exhaustion / DoS), plus state mutation in a dry-run path",
     "root_cause": "In AppOpsService.verifyAndGetBypass, when an attribution tag was not valid for the proxied package, the code always re-checked the tag in the proxy package's manifest (\"Re-check in proxy\"). In the ROOT_UID early return and in a second early return that grants RestrictionBypass.UNRESTRICTED, it accepted the tag whenever isPackageNullOrSystem(proxyPackageName, proxyUid) returned true. None of these checks considered whether the proxy was trusted (OP_FLAG_UNTRUSTED_PROXIED in the op flags), and isPackageNullOrSystem did not verify that proxyPackageName actually resolves to proxyUid. A code comment states the intent was \"to prevent abusive apps clogging the appops system with unlimited attribution tags via proxy calls\". In addition, the dry-run start path (the start variant taking startIfModeDefault, named startOperationDryRun in the commit message) called getOpsLocked with the attribution tag and edit=true, so state changed even though a dry run should change nothing. The finish path called getOpLocked with edit=true.",
     "fix_analysis": [
      "AppOpsService.java:verifyAndGetBypass (7-arg) gains a boolean isProxyTrusted parameter. The ROOT_UID and UNRESTRICTED early returns now compute isAttributionTagValid as isProxyTrusted && isPackageNullOrSystem(proxyPackageName, proxyUid). The proxy-manifest fallback becomes isAttributionTagValid = isProxyTrusted && isAttributionInPackage(proxyPkg, attributionTag). The 5-arg overload becomes 6-arg (it adds isProxyTrusted), and the 3-arg overload passes true. checkPackage now uses the 7-arg signature with isProxyTrusted=true.",
      "AppOpsService.java: the note path, the start path and the dry-run start variant (startIfModeDefault parameter) each compute boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0 and pass it to verifyAndGetBypass. Tags are therefore pulled from the proxy manifest only for trusted proxies. Otherwise attributionTag is set to null, as before, when pvr.isAttributionTagValid is false.",
      "AppOpsService.java: the dry-run start variant now calls getOpsLocked(uid, packageName, null, ...) with edit=true. The Ops object may still be created, but per the code comment no information about the tag is cached.",
      "AppOpsService.java (finish path): calls verifyAndGetBypass with isProxyTrusted=true (the code comment says the proxy's trust is unknown at this point). It switches getOpLocked to edit=false and replaces the direct map lookup with the new getAttributedOpWithClientId(op, clientId, attributionTag, virtualDeviceId, hasProxy), where hasProxy = proxyUid != Process.INVALID_UID.",
      "AppOpsService.java:getAttributedOpWithClientId (new): with no proxy, it returns the AttributedOp for the tag directly. With a proxy it: (1) returns the tag's AttributedOp if that op has an in-progress event for clientId without OP_FLAG_UNTRUSTED_PROXIED; (2) otherwise, if the tag is valid in the proxied package (verifyAndGetBypass(op.uid, op.packageName, attributionTag), with a SecurityException treated as invalid), returns it if it has an untrusted in-progress event for clientId; (3) otherwise looks up the null-tag AttributedOp and returns it if that op has an untrusted in-progress event for clientId. If none of these match, it returns null.",
      "AppOpsService.java:isPackageNullOrSystem now returns false if mPackageManagerInternal.getPackageUid(packageName, PackageManager.MATCH_ALL, UserHandle.getUserId(uid)) != uid, before it consults isSystemPackage.",
      "AttributedOp.java: new public boolean hasInProgressEvent(Predicate<InProgressStartOpEvent>). It iterates mPausedInProgressEvents when paused and mInProgressEvents otherwise. The file also imports java.util.function.Predicate."
     ],
     "completeness": [
      "The note path, the start path and the dry-run start variant all gate the proxy-manifest fallback and the system-proxy acceptance on the trusted flag. The finish path no longer creates Op entries (edit=false) and only resolves existing in-progress events.",
      "Tags valid in the proxied package itself are still accepted for untrusted proxies. Only the proxy-manifest and system-proxy shortcuts are restricted.",
      "The finish path still calls verifyAndGetBypass with isProxyTrusted=true because the proxy's trust is unknown there. Correctness relies on getOpLocked with edit=false and the clientId-based search in getAttributedOpWithClientId.",
      "The dry-run path still calls getOpsLocked with edit=true, so an Ops object for the uid/package can still be created. Only the attribution-tag information is no longer cached.",
      "The fix touches only AppOpsService.java and AttributedOp.java."
     ],
     "behavior_changes": [
      "For proxy note/start calls flagged OP_FLAG_UNTRUSTED_PROXIED, an attribution tag declared only in the proxy's manifest is now treated as invalid and replaced with a null tag.",
      "In the ROOT_UID and UNRESTRICTED early returns, the tag is accepted only when the proxy is trusted and the proxy package is null or system.",
      "isPackageNullOrSystem returns false when the given package name does not resolve to the given uid in that user.",
      "Dry-run start passes a null tag to getOpsLocked, so it no longer caches information about the requested tag.",
      "The finish path no longer creates Op entries. For proxied finishes it finds the AttributedOp by a matching in-progress clientId (trusted event on the tag, then untrusted event on a valid tag, then untrusted event on the null tag). If nothing matches, it logs 'Attribution not found' and returns."
     ],
     "preconditions": [
      "A caller reaches the note or start path, or the dry-run start variant, in AppOpsService with a proxy uid and package set and with op flags containing OP_FLAG_UNTRUSTED_PROXIED.",
      "The attribution tag is not valid for the proxied package but is declared in the proxy's manifest, or the early-return path is taken (ROOT_UID or UNRESTRICTED bypass) and a system package name is supplied as proxyPackageName.",
      "Which callers set OP_FLAG_UNTRUSTED_PROXIED is not shown in the material. In the platform, the proxy flag is normally derived from whether the proxy holds privileged app-ops permissions (general platform knowledge)."
     ],
     "per_major": {
      "14": "Adapted fix (RESTRICT AUTOMERGE, a404da3cbbc6 in android-security-14.0.0_r25). Same logic, with these differences: there is no virtualDeviceId (it uses op.mAttributions), dryRun is a boolean parameter of the start path so getOpsLocked receives dryRun ? null : attributionTag, the 'boolean wasNull' line is removed, and AttributedOp also adds the Consumer import. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "This commit (22d9cd12ef99 in android-security-15.0.0_r13, cherry-pick of 110db0acb84c). It uses getPersistentId(virtualDeviceId) in getAttributedOpWithClientId. 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "Adapted fix (a4523e227733 in android-security-16.0.0_r4). Same logic, but it uses getPersistentDeviceIdForOp(virtualDeviceId, op.op) for the device map lookup. 8/8 patched lines present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit across two files. Three spots need adapting to the branch: (1) the AttributedOp map lookup (op.mAttributions on 14, mDeviceAttributedOps with getPersistentId on 15, getPersistentDeviceIdForOp(virtualDeviceId, op.op) on 16); (2) whether the dry run is a separate start variant or a dryRun boolean (14 uses dryRun ? null : attributionTag); (3) the verifyAndGetBypass overload arity, since all callers of the 5/6-arg forms must pass isProxyTrusted.",
      "AttributedOp.hasInProgressEvent relies on InProgressStartOpEvent exposing getClientId() and getFlags(), and on mPausedInProgressEvents and isPaused(). Confirm these exist on the target branch."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which AppOps entry points or callers set OP_FLAG_UNTRUSTED_PROXIED. It also does not show the body of isPackageNullOrSystem above the patched lines, including how a null package name is handled.",
      "The enclosing method names of the Android 15 hunks (note, start, dry-run, finish) are not shown in the diff.",
      "The condition guarding the UNRESTRICTED early return in verifyAndGetBypass is not shown.",
      "The test referenced by the commit (atest AttributionTest) is not included in the material.",
      "The material does not say whether the bulletin's DoS rating refers to memory/state exhaustion in system_server or to another effect."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0020": {
     "cve": "CVE-2026-0020",
     "summary": "The fix trims leading and trailing whitespace from the names of <permission>, <permission-tree> and <permission-group> manifest elements during package parsing in ParsedPermissionUtils, so the stored name is the trimmed form rather than the literal manifest string.",
     "component": "platform/frameworks/base, package manager manifest parsing (ParsedPermissionUtils, permission / permission-tree / permission-group parsing)",
     "weakness": "Improper input validation / missing normalization of permission names (untrimmed whitespace in manifest-declared permission, permission tree and permission group names)",
     "root_cause": "ParsedPermissionUtils.parsePermission, parsePermissionTree and parsePermissionGroup stored the name from the manifest without normalization, so a name with leading or trailing whitespace was kept as a string that differs from its trimmed form. The material does not show the downstream consumer where this mismatch matters; the bulletin rates it a High severity EoP.",
     "fix_analysis": [
      "core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java:parsePermission: after the attribute block (knownCerts and restricted-flag handling) and before PermissionInfo.fixProtectionLevel is applied, adds permission.setName(permission.getName().trim()), so the stored name is trimmed before the protection level checks, metadata parsing and the returned result.",
      "ParsedPermissionUtils.java:parsePermissionTree: after the TypedArray is recycled and before the existing indexOf('.') dot-count logic, adds permission.setName(permission.getName().trim()); on Android 15/16 that check therefore runs on the trimmed name.",
      "ParsedPermissionUtils.java:parsePermissionGroup: appends .setName(permissionGroup.getName().trim()) to the builder chain that sets the group's resources, priority and flags.",
      "Single commit, one file. java.lang.String.trim() removes only characters with code point <= U+0020 at both ends (general platform knowledge)."
     ],
     "completeness": [
      "Covers all three name-bearing elements parsed in the patched hunks: <permission>, <permission-tree> and <permission-group>.",
      "In parsePermission the trim is applied after the restricted-flag block, so the IllegalStateException message there still uses the untrimmed name; the stored name is trimmed before the method returns.",
      "Only String.trim() is applied; whether characters outside its range matter is not shown by the material."
     ],
     "behavior_changes": [
      "Permission, permission tree and permission group names declared with leading/trailing whitespace are now stored in trimmed form instead of the literal manifest string.",
      "On Android 15/16, the existing dot-count check in parsePermissionTree now evaluates the trimmed name.",
      "The diff adds no new error paths; whitespace-padded names are normalized, not rejected."
     ],
     "preconditions": [
      "A package whose manifest declares a <permission>, <permission-tree> or <permission-group> element with a name containing leading or trailing whitespace is parsed by the package manager.",
      "The code is reached through ParsedPermissionUtils.parsePermission / parsePermissionTree / parsePermissionGroup."
     ],
     "per_major": {
      "14": "adapted fix: commit d7a73fb8bfc2 (RESTRICT AUTOMERGE) in android-security-14.0.0_r25 applies the same three trims in services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java. In parsePermissionTree the trim is placed after a validation block whose error message uses permission.getName(), immediately before setProtectionLevel(PROTECTION_NORMAL).setTree(true), which is later than on 15/16. 7/8 region lines present at 14.0.0_r29.",
      "15": "same fix: commit 4fa32d4fef90 in android-security-15.0.0_r13 (cherry-pick of 595cf99ecd42) in core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java; 8/8 lines present at 15.0.0_r17.",
      "16": "same fix: commit e770e9f02341 in android-security-16.0.0_r4, identical hunks at shifted line offsets; 8/8 lines present at 16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region is present (8/8) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Standalone one-file change. File location differs: services/core/java/com/android/server/pm/pkg/component/ on 14 versus core/java/com/android/internal/pm/pkg/component/ on 15+. On 15/16, the parsePermissionTree trim is placed before the dot-count logic, unlike on 14."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which consumer of permission / permission group names treated the whitespace-padded name as equivalent to the canonical one; the material only shows the parsing side.",
      "Whether the bug (453649815) also involved <uses-permission> or other name-bearing elements parsed outside these hunks.",
      "Whether the Android 14 parsePermissionTree trim falls after the dot-count check; the hunk does not show the indexOf logic.",
      "The test coverage added, beyond the 'atest AppSecurityTests' reference in the commit message."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0021": {
     "cve": "CVE-2026-0021",
     "summary": "On devices with Settings activity embedding (multi-pane) enabled, AppInfoBase now checks INTERACT_ACROSS_USERS_FULL against the initial calling package reported by SettingsActivity before, and instead of, the getLaunchedFromUid-based path. The Settings$AppUsageAccessSettingsActivity intent filter also gets android:priority=\"1\".",
     "component": "platform/packages/apps/Settings, applications/AppInfoBase and AndroidManifest.xml (Settings$AppUsageAccessSettingsActivity)",
     "weakness": "Missing permission check: on multi-pane (embedding) devices there was no caller permission check based on the initial calling package (INTERACT_ACROSS_USERS_FULL).",
     "root_cause": "A caller-permission method in AppInfoBase.java (its name is not shown in the material; it follows an `activity instanceof SettingsActivity` guard) identified the caller only through ActivityManager.getService().getLaunchedFromUid(activity.getActivityToken()). It had no separate handling for devices where Settings activity embedding is enabled. The commit message states the goal as checking the permission of the calling package on multi-pane devices.",
     "fix_analysis": [
      "src/com/android/settings/applications/AppInfoBase.java, caller-permission method: after the `activity instanceof SettingsActivity` guard, a new branch runs when ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) is true. It gets ((SettingsActivity) activity).getInitialCallingPackage(). If that is empty, it logs and returns false. If mPm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) is not PERMISSION_GRANTED, it logs and returns false. Otherwise it returns true. The existing getLaunchedFromUid path is reached only when embedding is not enabled.",
      "src/com/android/settings/applications/AppInfoBase.java: adds import com.android.settings.activityembedding.ActivityEmbeddingUtils.",
      "AndroidManifest.xml, Settings$AppUsageAccessSettingsActivity: the intent-filter for android.settings.USAGE_ACCESS_SETTINGS with data scheme \"package\" gains android:priority=\"1\". The material does not say why. In the r17 context, Settings$UsageAccessSettingsActivity also has priority=\"1\"."
     ],
     "completeness": [
      "Covers only the embedding-enabled branch. The non-embedding path still relies on getLaunchedFromUid and is unchanged.",
      "The new check is based on the package name (mPm.checkPermission with a package name), not on a UID, and it depends on SettingsActivity.getInitialCallingPackage() being accurate. The material does not show how that method is implemented.",
      "On embedding devices, an empty calling package now leads to denial (return false), so the check fails closed.",
      "The manifest change touches only Settings$AppUsageAccessSettingsActivity. No other activity filters are modified in this commit.",
      "Only AppInfoBase is changed. The material does not show whether other Settings fragments run their own getLaunchedFromUid-style caller checks."
     ],
     "behavior_changes": [
      "On multi-pane (embedding-enabled) devices, the AppInfoBase caller check returns false, with a Log.w, when the initial calling package cannot be determined or lacks INTERACT_ACROSS_USERS_FULL. It returns true when the permission is granted.",
      "Intent resolution for android.settings.USAGE_ACCESS_SETTINGS with a package: URI now sees Settings$AppUsageAccessSettingsActivity at priority 1 instead of the default priority 0."
     ],
     "preconditions": [
      "A device where Settings activity embedding is enabled (ActivityEmbeddingUtils.isEmbeddingActivityEnabled returns true).",
      "An AppInfoBase-derived fragment hosted in a SettingsActivity (the new code runs only after the instanceof SettingsActivity guard).",
      "The exact entry point and intent contents are unknown."
     ],
     "per_major": {
      "14": "same fix: commit 19171d5c31ad in android-security-14.0.0_r25, identical diff (only line offsets differ). 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 298fd46325a8 in android-security-15.0.0_r13. 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 48af8a13dd12 in android-security-16.0.0_r4, identical diff. 8/8 patched lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. It depends on ActivityEmbeddingUtils.isEmbeddingActivityEnabled(Activity), SettingsActivity.getInitialCallingPackage() and the mPm field in AppInfoBase already existing. Google applied the identical diff to 14, 15 and 16."
     ],
     "confidence": "medium",
     "unknowns": [
      "The name of the AppInfoBase method being patched and how its return value is used (the hunk header only shows the class).",
      "What the old getLaunchedFromUid path checks after getting the UID, and why it was insufficient on multi-pane devices.",
      "How SettingsActivity.getInitialCallingPackage() derives the package and whether it can be trusted across the embedding flow.",
      "Which fragments extend AppInfoBase (for example, whether UsageAccessDetails does).",
      "Why android:priority=\"1\" was added to the Settings$AppUsageAccessSettingsActivity filter, and how that relates to the EoP.",
      "The exact trigger path and the concrete privilege obtained. The bulletin only rates it High EoP."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0023": {
     "cve": "CVE-2026-0023",
     "summary": "PackageInstallerService now explicitly clears PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE from caller-supplied SessionParams.installFlags before re-deriving it from DevicePolicyManagerInternal.isUserOrganizationManaged(userId) during install session creation.",
     "component": "platform/frameworks/base, PackageManager / PackageInstallerService (install session creation)",
     "weakness": "Trust of a client-controlled flag (missing sanitization of caller-supplied installFlags; a system-only flag was not stripped)",
     "root_cause": "In services/core/java/com/android/server/pm/PackageInstallerService.java, the session-creation method (the one that allocates the session ID and constructs PackageInstallerSession) took the caller's SessionParams.installFlags and only ORed in INSTALL_FROM_MANAGED_USER_OR_PROFILE when dpmi != null && dpmi.isUserOrganizationManaged(userId). It never cleared the bit otherwise. Because installFlags come from the installer app's SessionParams, an app could set INSTALL_FROM_MANAGED_USER_OR_PROFILE itself, and the system would carry it into the created session even when the install was not actually from a managed user or profile. Per the commit message this leads to unexpected behaviour; the bulletin rates the issue High EoP.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java, session-creation method: right after dpmi is obtained via LocalServices.getService(DevicePolicyManagerInternal.class), the fix adds `params.installFlags &= ~PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE;`, so the bit supplied by the caller is always dropped.",
      "The existing conditional `if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) params.installFlags |= INSTALL_FROM_MANAGED_USER_OR_PROFILE;` is unchanged. The flag is now set only by the system's own device-policy check and never from caller input.",
      "This follows the pattern already used nearby for forceQueryableOverride (reset unless the caller is root or shell) and INSTALL_REQUEST_UPDATE_OWNERSHIP (stripped for APEX or when the caller lacks ENFORCE_UPDATE_OWNERSHIP). It is a two-line change in one file. The commit message references a test, testSessionCreationWithManagedUserOrProfileFlag_notFromManagedProfile, but the diff shown does not include it."
     ],
     "completeness": [
      "Covers the session-creation path in PackageInstallerService where SessionParams are first accepted. After this point the flag reflects only dpmi.isUserOrganizationManaged(userId).",
      "The clear happens before the PackageInstallerSession is constructed with params, so the stored session params are sanitized at creation. The material does not show whether any later code path can change a session's installFlags after creation.",
      "If dpmi is null (DevicePolicyManagerInternal not registered), the flag is now always cleared. Before the fix, a bit set by the caller would also have survived in that case.",
      "The material shows no other entry points that accept installFlags, so coverage of sibling paths cannot be judged from the code provided."
     ],
     "behavior_changes": [
      "If an installer puts INSTALL_FROM_MANAGED_USER_OR_PROFILE in SessionParams.installFlags, the bit is silently cleared; no error is returned.",
      "Whether a created session carries the flag now depends only on DevicePolicyManagerInternal.isUserOrganizationManaged(userId) for the target user."
     ],
     "preconditions": [
      "Caller must be able to create a package install session through PackageInstallerService (an installer app or shell reaching the session-creation method).",
      "Target userId is not organization-managed (or dpmi is null); otherwise the system sets the flag anyway.",
      "Caller includes INSTALL_FROM_MANAGED_USER_OR_PROFILE in SessionParams.installFlags."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base a9550d0a75d0 in android-security-14.0.0_r25. The two-line insertion is identical; only the offset differs (hunk starts at line 941). 3/3 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 221ac26392cd in android-security-15.0.0_r13 (cherry-pick, Change-Id I21bbbf628e97244d469eb23ce0558dbf560b7618); 3/3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 09055276288a in android-security-16.0.0_r4. The insertion is identical (hunk starts at line 1027). 3/3 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; the patched region (3/3 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Simple single-hunk backport: insert the clear line directly after `final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);` and before the isUserOrganizationManaged conditional in PackageInstallerService.java."
     ],
     "confidence": "high",
     "unknowns": [
      "The hunk header and context do not show the name of the enclosing method.",
      "The material does not show where INSTALL_FROM_MANAGED_USER_OR_PROFILE is consumed or what behaviour it gates, so the exact privilege impact beyond the bulletin's High/EoP rating is unknown.",
      "The referenced test PackageManagerShellCommandInstallTest#testSessionCreationWithManagedUserOrProfileFlag_notFromManagedProfile is not included in the diff provided.",
      "The material does not show whether other code paths accept or modify installFlags for an existing session.",
      "The material does not show how the fix applies to trees that lack the dpmi block."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0024": {
     "cve": "CVE-2026-0024",
     "summary": "MediaProvider now passes the open() options Bundle into isRedactionNeededForOpenViaContentResolver. When the Bundle contains MediaStore.EXTRA_MEDIA_CAPABILITIES_UID (> 0) and that UID's identity reports PERMISSION_IS_REDACTION_NEEDED, the content is redacted. This closes an ACCESS_MEDIA_LOCATION bypass through a SAF picker.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java open path redaction decision (isRedactionNeededForOpenViaContentResolver)",
     "weakness": "Missing permission check on the delegated (on-behalf-of) UID: the redaction decision ignored the app the picker acts for",
     "root_cause": "In src/com/android/providers/media/MediaProvider.java, isRedactionNeededForOpenViaContentResolver(redactedUri, ownerPackageName, file) did not receive the options Bundle passed to the open call. That Bundle can carry MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, which identifies the app on whose behalf the media is being opened. The redaction decision therefore did not consider the permissions of that app. Per the commit message, an app could use a picker to bypass ACCESS_MEDIA_LOCATION and get unredacted location data. The diff shows the redactedUri check and the owner check (getCallingPackageOrSelf() vs ownerPackageName). The rest of the function is not shown.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java: the open path (the block that runs queryForSingleItem, checkAccess and requireOwnershipForItem, then computes redactionNeeded) now calls isRedactionNeededForOpenViaContentResolver(redactedUri, ownerPackageName, file, opts), passing the options Bundle.",
      "src/com/android/providers/media/MediaProvider.java:isRedactionNeededForOpenViaContentResolver gains a Bundle opts parameter. After the existing 'redactedUri != null -> true' check and before the callerIsOwner check, it reads opts.getInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID). If the value is > 0, it builds LocalCallingIdentity.fromExternal(getContext(), mUserCache, mediaCapabilitiesUid, null, null) and returns true (redact) when identity.hasPermission(PERMISSION_IS_REDACTION_NEEDED) is true.",
      "The existing logic after the new block is unchanged. It still runs when no capabilities UID is supplied or when the supplied UID does not report PERMISSION_IS_REDACTION_NEEDED. The new check can only add redaction, never remove it."
     ],
     "completeness": [
      "Covers the ContentResolver open path in MediaProvider.java where isRedactionNeededForOpenViaContentResolver is called. Per index search, no other caller of this function was found, but the search may be incomplete.",
      "The new check only takes effect when the intermediary (picker) actually places MediaStore.EXTRA_MEDIA_CAPABILITIES_UID in the opts Bundle. If opts is null or the extra is absent or zero, behaviour is identical to before.",
      "The redactedUri path was already unconditional and is untouched. The delegated-UID check is placed before the callerIsOwner check, so a delegated UID that needs redaction gets redacted content even if the direct caller owns the file.",
      "No other file or redaction decision site (for example a FUSE path) is changed. The material does not show whether those paths consider a delegated UID.",
      "The fix does not validate who may set EXTRA_MEDIA_CAPABILITIES_UID. Because the extra can only make the result stricter, this does not reopen the bypass."
     ],
     "behavior_changes": [
      "Opens that carry MediaStore.EXTRA_MEDIA_CAPABILITIES_UID for a UID whose identity reports PERMISSION_IS_REDACTION_NEEDED now return redacted content (redaction ranges from getRedactionRanges applied), even when the direct caller is the file owner.",
      "For such opens, if the Uri requests the original (MediaStore.getRequireOriginal), the existing check now throws UnsupportedOperationException('Caller must hold ACCESS_MEDIA_LOCATION permission to access original').",
      "One additional LocalCallingIdentity.fromExternal construction and permission lookup per open when the extra is present. No feature flag (Flag: EXEMPT BUGFIX)."
     ],
     "preconditions": [
      "A media file is opened through MediaProvider's ContentResolver open path (not a redacted Uri) by an intermediary such as a SAF picker acting for another app.",
      "The intermediary passes the requesting app's UID in MediaStore.EXTRA_MEDIA_CAPABILITIES_UID in the opts Bundle, and the redaction decision for the intermediary itself would otherwise not redact.",
      "The requesting app's identity reports PERMISSION_IS_REDACTION_NEEDED (for example, it lacks ACCESS_MEDIA_LOCATION)."
     ],
     "per_major": {
      "14": "same fix: commit 50387ce9d85a in android-security-14.0.0_r25, identical hunks in isRedactionNeededForOpenViaContentResolver. The only difference in the surrounding code is that the result feeds getRedactionRanges(file) returning RedactionInfo, not RedactionUtils.getRedactionRanges. 6/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 77f9bd12a6ed in android-security-15.0.0_r13 (cherry-pick of c8a1e15d3a79, Change-Id I7ad51535d5ae8a3803162f688c4794edbbcfb167). 6/8 lines of the patched region present at android-security-15.0.0_r17, and the r17 code context shows the call site passing opts.",
      "16": "same fix: commit 69a25763cdb4 in android-security-16.0.0_r4, identical diff at different line offsets. 6/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected: Android 17 is not listed by the bulletin. 6/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained hunk pair in MediaProvider.java. The target branch must already have LocalCallingIdentity.fromExternal (called as fromExternal(getContext(), mUserCache, uid, null, null)), the PERMISSION_IS_REDACTION_NEEDED constant and MediaStore.EXTRA_MEDIA_CAPABILITIES_UID."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact name of the enclosing open method is not shown in the provided context.",
      "Which picker or intermediary components set EXTRA_MEDIA_CAPABILITIES_UID on the affected branches is not shown in the material.",
      "The rest of isRedactionNeededForOpenViaContentResolver after the owner check is not shown.",
      "Whether other redaction decision sites (for example a FUSE path) have an analogous delegated-UID gap is not shown.",
      "Why only 6/8 lines of the patched region are reported present at each latest tag is not explained.",
      "Whether the fix is also delivered through a MediaProvider module update is not shown.",
      "Test coverage: the commit is marked 'Test: manual' and the diff contains no test changes."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0025": {
     "cve": "CVE-2026-0025",
     "summary": "Notification.java now reads the EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES extras through getParcelableArrayFromBundle(..., Bundle.class) instead of extras.getParcelableArray(..., Parcelable.class). Per the new test, an array that contains a non-Bundle element now causes an ArrayStoreException on enqueue, and no URI grant check happens for the embedded URIs.",
     "component": "platform/frameworks/base, core/java/android/app/Notification.java (URI visiting of MessagingStyle extras, hasImage, MessagingStyle extras restoration), used by NotificationManagerService",
     "weakness": "Insufficient type validation of parcelled input (message array elements not strictly typed as Bundle), leading to skipped URI permission checks",
     "root_cause": "The URI-visiting code, Notification.hasImage and the MessagingStyle code that restores from extras read the EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES arrays with extras.getParcelableArray(key, Parcelable.class), which accepts any Parcelable element type. Each array is then passed to MessagingStyle.Message.getMessagesFromBundleArray. The new test builds a sender-controlled array that mixes real message Bundles with a crafted non-Bundle element written as android.content.pm.ParceledListSlice. The test comment says the element is added 'to avoid visitUri check uris in EXTRA_MESSAGES value', and the commit message mentions class cast exceptions. The bulletin classifies the issue as information disclosure.",
     "fix_analysis": [
      "core/java/android/app/Notification.java, URI visiting (hunk near line 2994): EXTRA_MESSAGES is now read as Bundle[] via getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class). EXTRA_HISTORIC_MESSAGES is read via getParcelableArrayFromBundle(extras, EXTRA_HISTORIC_MESSAGES, Bundle.class). Per the new test, a non-Bundle element now causes an ArrayStoreException and checkGrantUriPermission is never called for the embedded URI.",
      "core/java/android/app/Notification.java:hasImage: the same change for EXTRA_MESSAGES.",
      "core/java/android/app/Notification.java, MessagingStyle extras restoration (hunk near line 9065, which sets mMessages and mHistoricMessages): EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES are both read with getParcelableArrayFromBundle(..., Bundle.class).",
      "services/tests/uiservicestests/.../NotificationManagerServiceTest.java: adds testNoUriGrantsForBadMessagesList. The test enqueues a MessagingStyle notification whose EXTRA_MESSAGES array contains a crafted non-Bundle Parcelable (MyParceledListSlice). It expects an ArrayStoreException and verifies that UriGrantsManagerInternal.checkGrantUriPermission is never called for the contact photo URI."
     ],
     "completeness": [
      "The diff converts all five read sites of EXTRA_MESSAGES / EXTRA_HISTORIC_MESSAGES that it shows in Notification.java: URI visiting x2, hasImage x1, MessagingStyle restoration x2. The fix relies on the existing getParcelableArrayFromBundle helper, which the diff neither adds nor modifies.",
      "The fix only hardens Notification.java. The material does not show any other consumer that reads these extras directly, and this commit does not touch any.",
      "In the URI-visiting code, the historic variable keeps the static type Parcelable[] even though it is assigned from the Bundle.class call. This is cosmetic.",
      "The new test method testNoUriGrantsForBadMessagesList has no @Test annotation in any of the three branch diffs, so the JUnit4 runner may not execute it. This does not affect the production fix."
     ],
     "behavior_changes": [
      "A notification whose EXTRA_MESSAGES or EXTRA_HISTORIC_MESSAGES extra holds a Parcelable[] with a non-Bundle element now raises an ArrayStoreException. Per the new test, the enqueue fails.",
      "Notifications built through MessagingStyle, which stores Bundle elements, should be unaffected."
     ],
     "preconditions": [
      "A caller posts a notification through NotificationManagerService (the test uses enqueueNotificationWithTag on the binder service). EXTRA_MESSAGES or EXTRA_HISTORIC_MESSAGES is set to a Parcelable array that mixes message Bundles referencing content URIs with a non-Bundle Parcelable element.",
      "Per the test, the enqueue path checks the URIs in the notification with UriGrantsManagerInternal.checkGrantUriPermission."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 5e1623a880d2 in android-security-14.0.0_r25. The Notification.java hunks are identical, at different line offsets (2912, 7121, 8332). The test diff lacks the static imports and the later EXTRA_MESSAGES/EXTRA_HISTORIC_MESSAGES test hunk that 15 and 16 have. 6/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 5ffc92d00244 in android-security-15.0.0_r13 (cherry-pick of 71d4afae00c7, Change-Id I3022e010de95f14dcd0d09d123684ee265101e0a). At android-security-15.0.0_r17 the URI-visiting code shows the getParcelableArrayFromBundle(..., Bundle.class) calls in place (6/8 lines of the patched region present).",
      "16": "same fix: platform/frameworks/base 014dea279c49 in android-security-16.0.0_r4. The Notification.java hunks are identical, at lines 3152, 8203 and 9485. 6/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 6/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The production change touches only Notification.java and depends on the existing Notification.getParcelableArrayFromBundle helper, which the diff uses but does not add. Apart from line offsets, the Notification.java hunks are identical across 14, 15 and 16."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the bodies of MessagingStyle.Message.getMessagesFromBundleArray and getParcelableArrayFromBundle. So the exact pre-fix behaviour on a non-Bundle element, and exactly where the URI check was skipped, are inferred only from the commit message and the test.",
      "The material does not show whether the notification could still be processed or displayed after the URI check was skipped before the fix.",
      "Whether other platform components read EXTRA_MESSAGES directly from the extras Bundle is not shown.",
      "The upstream commit 71d4afae00c7 referenced in the cherry-pick line is not included.",
      "The name of the MessagingStyle method in the hunk near line 9065 is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0034": {
     "cve": "CVE-2026-0034",
     "summary": "Caps the per-user number of approved managed-service entries (notification listeners, condition providers, assistants) at 100 in ManagedServices.setPackageOrComponentEnabled, makes that method return whether the change was applied, and has NotificationManagerService skip follow-up actions (secondary approvals, broadcasts, policy save) when it was not.",
     "component": "platform/frameworks/base, system_server notification service (ManagedServices / NotificationManagerService)",
     "weakness": "Missing upper bound on a caller-growable collection (unbounded resource allocation): no limit on approved/user-set managed service entries per user, each approved entry leading to a service binding",
     "root_cause": "ManagedServices.setPackageOrComponentEnabled always added the package/component to the per-user approved set in mApproved and to mUserSetServices, then called rebindServices(false, userId). Nothing limited how many entries a user could accumulate. The new comment says the cap \"effectively limits the number of services (e.g. NLSes) that will be bound per user\", so before the fix the number of bound managed services and the size of these lists were unbounded. The method also returned void. As a result, setNotificationPolicyAccessGrantedForUser always sent ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED and called handleSavePolicyFile. The listener access-grant path always enabled the secondary condition-provider entry and broadcast. The NotificationListeners override always sent ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED. All of this happened whether or not anything had changed.",
     "fix_analysis": [
      "ManagedServices.java: adds private static final int MAX_SERVICE_ENTRIES = 100, documented as the maximum number of entries in the per-user lists inside mApproved or mUserSetServices.",
      "ManagedServices.java:setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet): the return type changes from void to boolean and a local 'changed' flag is added. When enabling, the item is added to 'approved' only if approved.size() < MAX_SERVICE_ENTRIES. Otherwise a Slog.w 'Failed to allow ... because there are too many already' is logged and changed stays false. Disabling always removes the item and sets changed = true.",
      "ManagedServices.java:setPackageOrComponentEnabled: the mUserSetServices update moves inside 'if (changed)'. Adding to userSetServices is also capped at MAX_SERVICE_ENTRIES and is silently skipped when full; removal is unconditional. rebindServices(false, userId) now runs only when changed is true, and changed is returned.",
      "ManagedServices.java: the 4-arg overload setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled) now returns the result of the 5-arg overload.",
      "NotificationManagerService.java:setNotificationPolicyAccessGrantedForUser: stores the boolean from mConditionProviders.setPackageOrComponentEnabled and returns early when it is false, skipping the broadcast and handleSavePolicyFile.",
      "NotificationManagerService.java, listener access-grant path (reached via setNotificationListenerAccessGrantedForUser in the test): the calls are reordered so mListeners.setPackageOrComponentEnabled (isPrimary=true) runs first. If it returns false, the method returns before mConditionProviders.setPackageOrComponentEnabled (isPrimary=false) and before the broadcast.",
      "NotificationManagerService.java:NotificationAssistants.setPackageOrComponentEnabled override: now returns boolean. The 'already the current component' early exit returns false; otherwise it returns super's result.",
      "NotificationManagerService.java:NotificationListeners.setPackageOrComponentEnabled override: now returns boolean. If super returns false it returns false immediately, skipping the redactSensitiveNotificationsFromUntrustedListeners handling and the ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED broadcast. Otherwise it returns true.",
      "Tests: ManagedServicesTest adds setPackageOrComponentEnabled_tooManyPackages_stopsAdding (the 101st enable is rejected; a disable frees a slot) and setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet (user-set tracking stops at 100; a removal through onPackagesChanged frees a slot). NotificationManagerServiceTest stubs the mocks to return true and adds testSetListenerAccessForUser_tooManyListeners_skipsFollowups."
     ],
     "completeness": [
      "The cap is enforced only in ManagedServices.setPackageOrComponentEnabled. Other code that writes mApproved or mUserSetServices (e.g. XML load/migration paths, default-component population) is not in the diff, so the material does not show whether those paths are bounded.",
      "The limit is per user and per ManagedServices instance: listeners, condition providers and assistants each have their own 100-entry lists. The fix does not bound the total across users or across service types.",
      "The commit message lists Flag: com.android.server.notification.limit_managed_services_count, but the diff contains no flag check. In the code shown, the cap is unconditional.",
      "The diff does not modify setNotificationAssistantAccessGrantedForUserInternal, so the material does not show how that path uses the new return value.",
      "When approved.size() is at the cap, the userSet state for the rejected item is not updated at all (changed is false). When userSetServices is at the cap but approved is not, the item is approved but not tracked as user-set (the tooManyChanges test shows this), so the two lists silently diverge."
     ],
     "behavior_changes": [
      "Attempts to approve a 101st package/component per user for a given managed-service type are silently rejected: only a Slog.w is logged and no exception is thrown. This matches 'silently rejected' in the commit message.",
      "setPackageOrComponentEnabled now returns boolean. On rejection, rebindServices is not called and no ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED or ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED broadcast is sent. setNotificationPolicyAccessGrantedForUser also skips handleSavePolicyFile.",
      "In the listener access-grant path, the listener approval now runs before the secondary condition-provider approval. The secondary approval is skipped when the listener change is rejected.",
      "When approvedItem is null, rebindServices and the mUserSetServices update no longer run. Before the fix, both ran unconditionally.",
      "The NotificationAssistants override returns false when the requested component is already the current assistant.",
      "After 100 entries per user, mUserSetServices stops recording user-set state. Slots are freed only by removal: a call with userSet=false or a package removal via onPackagesChanged."
     ],
     "preconditions": [
      "The code is reached through NotificationManagerService entry points: setNotificationPolicyAccessGrantedForUser (guarded by checkCallerIsSystemOrShell and mAllowedManagedServicePackages.test) and the listener access-grant path (guarded by mAllowedManagedServicePackages.test), plus the NotificationAssistants and NotificationListeners overrides.",
      "The new rejection triggers when the user's approved set for that managed-service type already holds 100 entries.",
      "The material does not show how an unprivileged caller reaches these paths in practice. The bulletin rates the issue High / EoP."
     ],
     "per_major": {
      "14": "Adapted fix: the same ManagedServices change and NMS caller changes (cb1ce8020b43 in android-security-14.0.0_r25). The NotificationListeners override in 14 has no redactSensitiveNotificationsFromUntrustedListeners block, so the early return there is simpler. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "Same fix: a68f6e5719ce in android-security-15.0.0_r13 (cherry-pick of a132684a093d). At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "Same fix: e363f8210456 in android-security-16.0.0_r4. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The return type of setPackageOrComponentEnabled changes from void to boolean in ManagedServices and in every subclass override (NotificationAssistants and NotificationListeners in NotificationManagerService.java). Any other ManagedServices subclass or OEM override must be updated to return boolean. The NotificationManagerServiceTest mocks must also be stubbed to return true.",
      "The commit message names an aconfig flag (limit_managed_services_count), but the diff has no flag guard. On 14, take the 14 variant of the NotificationListeners override (no redaction block)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the concrete path by which unbounded approvals were abused (which caller, which privilege level) or the concrete EoP consequence.",
      "The material does not show whether other writers of mApproved / mUserSetServices (XML read, defaults, migration) are bounded.",
      "The material does not explain why the commit message references a flag while the diff contains no flag check.",
      "The name of the NMS method containing the listener access-grant change is not shown in the diff hunk.",
      "The material does not show how setNotificationAssistantAccessGrantedForUserInternal handles the new return value."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0035": {
     "cve": "CVE-2026-0035",
     "summary": "MediaProvider now rejects MediaStore create*Request calls (CREATE_WRITE_REQUEST, CREATE_DELETE_REQUEST and siblings) with IllegalArgumentException when any requested URI does not resolve to exactly one existing item at request time (the commit message says the Files table).",
     "component": "platform/packages/providers/MediaProvider, MediaStore create-request / PermissionActivity flow (MediaProvider.java)",
     "weakness": "Missing input validation: URIs passed to a media permission request were not checked for existence before the request (PermissionActivity intent) was built",
     "root_cause": "The create-request handler in src/com/android/providers/media/MediaProvider.java builds `new Intent(method, null, context, PermissionActivity.class)` and stores EXTRA_CALLING_PACKAGE_UID. The diff does not show the name of the enclosing method. This handler accepted the caller-supplied collection of URIs and went on to build the PermissionActivity request without checking that each URI matched an existing item. Per the commit message, a caller could request CREATE_WRITE/DELETE/..._REQUEST for a URI that did not exist in the Files table at the time of the request. The bulletin rates the issue as High severity EoP. The material does not show what happens downstream when a request contains a non-existent URI.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java, create-request handler (just before the PermissionActivity Intent is constructed): a new validation loop is inserted. It first calls clearLocalCallingIdentity(), then for each Uri in `uris` calls queryForSingleItem(uri, new String[]{FileColumns._ID}, null, null, null) in a try-with-resources.",
      "queryForSingleItem throws FileNotFoundException if it finds no item, more than one item, or hits a read error. The fix catches that and rethrows IllegalArgumentException(\"Invalid Uri: \" + uri, e), which aborts the request before the Intent is created.",
      "A finally block calls restoreLocalCallingIdentity(token), so the caller identity is restored whatever the outcome.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: testCreateRequest now inserts a real audio row (test.mp3) and requests a write request for that URI (assumeTrue(uri != null)). A new test, testCreateRequest_invalidUri_throwsException, asserts IllegalArgumentException for the hard-coded non-existent image id 42."
     ],
     "completeness": [
      "The check runs over every URI in the request, so a partly valid list is rejected as a whole, not just checked at the first entry.",
      "The validation sits in the shared code that builds the PermissionActivity Intent from `method`, so it applies to every request type routed through this handler (commit message: CREATE_WRITE/DELETE/.._REQUEST). The diff does not show how the method is dispatched, so this is inferred from the context lines.",
      "The existence check runs only when the request is created. The material does not show what PermissionActivity does when it later acts on the URIs, so it is not visible whether a row removed between request creation and confirmation is handled.",
      "queryForSingleItem also rejects URIs that match multiple rows, so the check is stricter than a pure existence check."
     ],
     "behavior_changes": [
      "MediaStore.createWriteRequest / createDeleteRequest / related create*Request calls now throw IllegalArgumentException (surfaced to the app) if any URI does not resolve to exactly one existing item. Before the fix, the old test got a non-null result for a fabricated id.",
      "Each create request now runs one extra query per URI, under cleared local calling identity.",
      "The existing test testCreateRequest no longer uses a fabricated id and instead inserts a real row. A new negative test was added."
     ],
     "preconditions": [
      "The caller invokes a MediaStore create*Request method (e.g. MediaStore.createWriteRequest) that reaches this handler in MediaProvider. The test does this through sIsolatedResolver.",
      "At least one URI in the supplied collection does not match exactly one existing item at the time of the call."
     ],
     "per_major": {
      "14": "same fix: commit 003dd55147bf in android-security-14.0.0_r25. It has the identical 16-line hunk at MediaProvider.java line ~6970 and the same test changes. 8/8 lines are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 56e6173dcc97 in android-security-15.0.0_r13, hunk at MediaProvider.java line ~7644. 8/8 lines are present at android-security-15.0.0_r17.",
      "16": "same fix: commit 119013a3d7e8 in android-security-16.0.0_r4, hunk at MediaProvider.java line ~8383. The test hunk context differs only in the name of the neighbouring test. 8/8 lines are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single self-contained hunk. It relies only on existing helpers (queryForSingleItem, clearLocalCallingIdentity/restoreLocalCallingIdentity, FileColumns._ID), and Google applied the identical hunk on 14, 15 and 16 with only line offsets changing. The commit lists two Cherrypick-From sources. The material does not show what the second internal commit contributed.",
      "MediaProvider is a Mainline module (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The name of the enclosing method in MediaProvider.java and the exact set of `method` values dispatched to it are not shown in the diff.",
      "What PermissionActivity or the later grant path did with a non-existent URI (the concrete EoP consequence) is not shown in the material.",
      "The content of the second Cherrypick-From commit (fdbfd5ddd808) and whether it changed anything beyond the hunks shown.",
      "What access the cleared local calling identity gives the query, and whether the new exception reveals to callers that an item exists.",
      "Which permissions sIsolatedResolver holds in the test."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "16": {
   "counts": {
    "announced": 41,
    "certain": 41,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2025-48631",
     "osv_id": "ASB-A-444671303",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a hard 4096 px per-dimension limit in LocalImageResolver.onHeaderDecoded so that images whose header reports a larger width or height are rejected with an exception before any pixel decoding is attempted.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. Its only context dependency is the existing onHeaderDecoded(ImageDecoder, ImageInfo, int, int) method and the DEFAULT_MAX_SAFE_ICON_SIZE_PX constant, so it applies with a small line offset on 14 and cleanly on 15/16. The test needs the binary test16000x16000.gif resource added to core/tests/coretests/res/drawable."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d6df825fda3aa29cff7af05357005322152210fd",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0006",
     "osv_id": "ASB-A-423894847",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix upgrades external/libopenapv as a whole, from upstream v0.1.13.1 to v0.2.0.0 (commit cf0a0e7a810e, bug 423894847). This replaces the APV bitstream/VLC code (src/oapv_vlc.c), the metadata handling (src/oapv_metadata.c), the bitstream reader/writer (src/oapv_bs.c) and the core codec (src/oapv.c) with newer upstream code, and changes the public API in inc/oapv.h.",
      "confidence": "low",
      "backport_notes": [
       "This library bump breaks the API/ABI (oapvm_set, oapvm_payload, oapv_version, oapve_param.csp). The commit message says 'All commits must go together' and lists two Cherrypick-From entries. Callers of the changed functions (per index search: the Codec2 APV components in frameworks/av) must be checked and adapted when taking it.",
       "The material contains no minimal patch. A tree that cannot take the full v0.2.0.0 import would need to review upstream v0.1.13.1..v0.2.0.0 for src/oapv_vlc.c, src/oapv_metadata.c and src/oapv_bs.c; the provided diff does not show those changes."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libopenapv",
       "sha": "cf0a0e7a810e5a0f6e50f433c1d723b12160e8dd",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libopenapv"
     ]
    },
    {
     "cve": "CVE-2024-43766",
     "osv_id": "ASB-A-288144143",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the Bluetooth SMP stack, smp_proc_srk_info now always calls smp_key_distribution_by_transport before it saves the peer CSRK to the security record, and the save_peer_csrk_after_ltk_gen flag is removed. The removed flag's description says this ordering makes the right security level get marked on the peer CSRK.",
      "confidence": "medium",
      "backport_notes": [
       "On a branch without the save_peer_csrk_after_ltk_gen aconfig flag, apply the Android 15 form: move smp_key_distribution_by_transport above the le_key construction and delete the trailing call. On a branch with the flag, follow the 16 commit: remove the flag from flags/pairing.aconfig and remove both flag checks.",
       "Android 14 used a separate change in btm_acl.cc (btm_establish_continue) that relies on btm_find_dev, is_le_link_key_known and btm_ble_set_encryption."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "769a3dea8016dd84bea05405d1d5c974755fdb21",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-32313",
     "osv_id": "ASB-A-399155883",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a bounds check in the NotificationHistory(Parcel) constructor so a position value read from parcel data is rejected (IllegalStateException) when it exceeds the rebuilt inner Parcel's data size, instead of being passed straight to Parcel.setDataPosition; on Android 14/15 the linked fix instead changes libbinder Parcel::appendFrom to base its capacity check on mDataPos rather than mDataSize.",
      "confidence": "high",
      "backport_notes": [
       "The two fixes are independent and complementary: the Java check (4 inserted lines right after mParcel.setDataSize(mParcel.dataPosition()) in NotificationHistory(Parcel)) applies wherever that constructor sequence exists, and the one-line libbinder change in Parcel::appendFrom applies to any branch whose appendFrom still tests (mDataSize+len) > mDataCapacity; applying both gives the broadest coverage.",
       "The Android 16 commit is marked Ignore-AOSP-First and was merged twice into the 16 security branches under one Change-Id; use Merged-In Ica98d82d7d7c1c9c0defcff8d96bf5f36e8f5d36 to detect it."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r6",
      "date": "2026-05-04",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "283bef4b25462b07c264935ba4b03f991b3693fc",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "59c15187e7ee299592c22a166528fb27b3a03867",
       "tag": "android-security-16.0.0_r6",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48544",
     "osv_id": "ASB-A-415783046",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the Bundle extra MediaProvider#INCLUDED_DEFAULT_DIRECTORIES and instead threads an explicit Optional<List<String>> parameter through getQueryBuilder/appendAccessCheckQuery/AccessChecker.getWhereForConstrainedAccess, so the list of default directories that widens access is supplied only internally by the FUSE directory rename path and is no longer read from a Bundle.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit, no dependency. When porting to a tree with a different set of getQueryBuilder call sites, rely on the signature change to find every caller. All must pass Optional.empty() except the FUSE directory rename loop, which passes Optional.of(getIncludedDefaultDirectories()). In trees with a single-branch appendAccessCheckQuery (as in Android 14) only one getWhereForConstrainedAccess call needs updating."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r1",
      "date": "2025-11-20",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r1",
      "date": "2025-11-20",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "a5f1f182733953ef5919311d3d8d2087d9451f1f",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 101,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48567",
     "osv_id": "ASB-A-377888957",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now NFD-normalizes a file path and strips Unicode default-ignorable code points (new FileUtils.normalizeAndFilterDefaultIgnorableCodepoints) before running the private-app-directory checks in isPrivatePackagePathNotAccessibleByCaller and the FUSE entry point isUidAllowedAccessToDataOrObbPathForFuse. The goal is that invisible characters the lower file system ignores can no longer make a path miss the Android/data, Android/obb and Android/media protections.",
      "confidence": "medium",
      "backport_notes": [
       "For trees without the aconfig Flags library, follow the 14/15 variant: drop the Flags.enablePathSanitization() guard and the aconfig entry, and remove the @EnableFlags and Flags references from the test. The 15 backport's test references Flags.FLAG_ENABLE_PATH_SANITIZATION without the diff defining it, so check that it compiles. The helper depends on android.icu.lang.UCharacter/UProperty and java.text.Normalizer.",
       "Keep the split between the public sanitizing isUidAllowedAccessToDataOrObbPathForFuse (@Keep) and the private isUidAllowedAccessToDataOrObbPath. Check whether shouldBypassFuseRestrictions or other callers in your tree pass unsanitized paths, since this commit does not sanitize them."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "237fb89f532504aeef0c3ee2038aad89a8a66180",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48574",
     "osv_id": "ASB-A-428700812",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DisplayPolicy.validateAddingWindowLw now checks MANAGE_ACTIVITY_TASKS against the callingPid/callingUid passed in explicitly when a window requests PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP. Before, it called ActivityTaskManagerService.enforceTaskPermission(), which uses the Binder calling identity, and that identity has already been cleared at this point.",
      "confidence": "high",
      "backport_notes": [
       "The change replaces one call inside validateAddingWindowLw with a three-line call. The same hunks appear on 14, 15 and 16 at different line offsets. On 16 the hunk is followed by a systemUiPermission block, while on 14 and 15 it is followed directly by 'switch (attrs.type)'. The test hunk verifies on mWm.mContext, so that object must be a mock or spy in the test base."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1cfd8237b5a8e9fa64367e3d0dfff525d63821e1",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48577",
     "osv_id": "ASB-A-413380719",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "KeyguardViewMediator now records which user a pending keyguard dismissal belongs to and re-validates it against the currently selected user at deferred steps (biometric auth success, hide-animation completion, the going-away runnable), hardens the exit-animation user-mismatch path, and cancels pending delayed dismiss/HIDE work on user switch, so a dismissal started for the previous user no longer completes after a user switch.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. The user-lookup helper differs by release (KeyguardUpdateMonitor.getCurrentUser() on 14, mSelectedUserInteractor.getSelectedUserId() on 15/16), and keyguardDonePending takes an extra boolean parameter on 14. Pick the per-major variant that matches the target tree. On 14, the handleBeforeUserSwitching token/HIDE cancellation is absent.",
       "The 16 hunk in handleStartKeyguardExitAnimation references KeyguardWmStateRefactor and a 3-argument setShowingLocked. On trees without these, the 15 form (unconditional, 2-argument) is the matching variant."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "78760f098fab75ae8e952978768d3c7df6312dbd",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48578",
     "osv_id": "ASB-A-418225717",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds the missing restoreCallingIdentity/restoreLocalCallingIdentity calls on two exception paths in MediaProvider (unsupported authority in getResultForGetMediaUri, NumberFormatException from ContentUris.parseId in updateInternal) so the cleared calling identity is restored when the method throws.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained in MediaProvider.java and applies at shifted line offsets across 14/15/16. The two sites use different identity mechanisms: getResultForGetMediaUri uses clearCallingIdentity()/restoreCallingIdentity, while updateInternal uses clearLocalCallingIdentity()/restoreLocalCallingIdentity. Pair each restore with its matching clear.",
       "For Android 14 based trees, Google's commit only patched updateInternal. Check whether getResultForGetMediaUri on that tree has the same unsupported-authority throw after clearCallingIdentity(); if it does, consider adding restoreCallingIdentity(token) before the throw."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "7ea0a039ec32fcd6477355431b953f4689a2fa61",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48579",
     "osv_id": "ASB-A-417195606",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restores the cleared calling identity on two exception paths in MediaProvider, so the binder thread does not continue with a cleared identity after the exception: a NumberFormatException from ContentUris.parseId in updateInternal, and an IllegalArgumentException for an unsupported document URI authority in getResultForGetMediaUri.",
      "confidence": "high",
      "backport_notes": [
       "Both hunks are self-contained and depend only on the existing token variables (CallingIdentity token in getResultForGetMediaUri, LocalCallingIdentity token in updateInternal). Only line offsets need adjusting. When backporting to a 14-based tree, check whether getResultForGetMediaUri there has the same unprotected authority check: the Android 14 commit omitted that hunk and the material does not show why."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "7ea0a039ec32fcd6477355431b953f4689a2fa61",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48582",
     "osv_id": "ASB-A-369105011",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now records the Binder calling uid of the app that created a bulk create request in the PermissionActivity intent extras, and PermissionActivity uses that uid instead of Activity.getCallingPackage() to decide whether a consent dialog is needed and to whom URI permissions are granted.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. On branches older than 15, check that MediaStore.EXTRA_CALLING_PACKAGE_UID exists in apex/framework/java/android/provider/MediaStore.java and add it as the 14 patch does. MediaProvider ships as a mainline module (APEX), so the framework constant and the provider code must move together (general platform knowledge).",
       "Make sure the create-request PendingIntent in MediaProvider is created with FLAG_IMMUTABLE on the target branch; the fix depends on the holder not being able to rewrite the uid extra."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "1b7d4ce446d149a34d8b317d41127379f017165d",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48585",
     "osv_id": "ASB-A-425360742",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ProfilingService now verifies, via PackageManager.getPackagesForUid, that the package name supplied by the client to addProfilingTriggers, removeProfilingTriggers and clearProfilingTriggers actually belongs to Binder.getCallingUid(), throwing SecurityException otherwise.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained single-file change. It needs only mContext (already a field of ProfilingService) and Binder.getCallingUid(). Apply it to any branch that contains the addProfilingTriggers, removeProfilingTriggers and clearProfilingTriggers methods. Profiling is a mainline module, so updated builds may ship through module updates rather than the platform tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Profiling",
       "sha": "1215317dfc1d88a4b5ea47185fb8f10afd1f78b0",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Profiling"
     ]
    },
    {
     "cve": "CVE-2025-48587",
     "osv_id": "ASB-A-425360073",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds server-side validation of the trigger type in ProfilingService.addTrigger so that triggers with an unsupported type are rejected (IllegalArgumentException) instead of being stored, and makes the persisted-trigger load path skip such triggers.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit fix confined to ProfilingService.java. It calls ProfilingTrigger.isValidRequestTriggerType, which the diff does not add, so that method must already exist in the target tree."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Profiling",
       "sha": "ceaaf1330d3a0697e52691d223ee700824a5b4fa",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Profiling"
     ]
    },
    {
     "cve": "CVE-2025-48602",
     "osv_id": "ASB-A-407562568",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On user switch, KeyguardViewMediator now cancels any in-flight keyguard exit animation: it drops pending START_KEYGUARD_EXIT_ANIM messages and sets mIsKeyguardExitAnimationCanceled. When the deferred exit-animation completion runnable finds the cancel flag set, it restores the keyguard with finishSurfaceBehindRemoteAnimation(true) and a forced setShowingLocked(true) instead of finishing the exit.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, two files. To adapt it, find the user-switch code (handleBeforeUserSwitching on 15/16; the block around line ~2431 on 14) and check the setShowingLocked/onKeyguardExitFinished call forms: the 16 diff passes reason strings, the 14/15 diffs do not. The 14 diff does not set mIsKeyguardExitAnimationCanceled on user switch, so check separately whether your tree sets it.",
       "The test needs the postAfterTraversal(Runnable) seam to be overridable in the anonymous subclass in KeyguardViewMediatorTest. The constructor argument list differs per release and must match your tree."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c69e16c3efda87ee6435184949549b2f088589b3",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48609",
     "osv_id": "ASB-A-414388731",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsProvider.deleteDataRows now checks that the canonical path stored in a part/drm row's _data column lies under the provider's \"parts\" directory before deleting the file. It does this through a new isFilePathCanonical helper, which openFile now uses as well.",
      "confidence": "high",
      "backport_notes": [
       "A single self-contained commit to MmsProvider.java plus its test. It relies on PARTS_DIR_NAME and the existing openFile canonical-path check, both of which appear as context in the diffs for 14, 15 and 16. The hunk offsets differ slightly per branch (the delete() hunk starts at line 769 on 14, 799 on 15 and 805 on 16), but the logic is the same."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "37babc702849821595634b6ecfda6f04404e2cf1",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2025-48619",
     "osv_id": "ASB-A-414387646",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a validateFileMode() sanitizer to ContentProvider's Binder Transport so that openFile and openAssetFile strip the truncate ('t') and append ('a') mode characters when the write ('w') character is absent, and use the sanitized mode for both the permission check and the provider call.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching only ContentProvider.java. The only per-branch context difference is the trace tag constant used in traceBegin on Android 14 (TRACE_TAG_DATABASE), which is outside the changed lines. Forks that customized Transport.openFile/openAssetFile must apply validateFileMode to both the enforceFilePermission argument and the mInterface call."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "31e77d73c5e6439bc942a92676c4076ebd8295ff",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48630",
     "osv_id": "ASB-A-455563813",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SkiaRenderEngine::drawLayersInternal now clips the canvas to the layer bounds before drawing blur regions when no rounded-corner clip is present, so a client-supplied blur region larger than the layer can no longer draw blurred content outside the layer.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk: find the `if (!roundRectClip.isEmpty()) { canvas->clipRRect(roundRectClip, true); }` block inside the `SkAutoCanvasRestore acr(canvas, true);` scope of SkiaRenderEngine::drawLayersInternal and add the else branch. Google's hunks on 14, 15 and 16 have identical text and differ only in line offsets."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "c81cf361489e3a3cd764c0a0c85c84958e25d63c",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48634",
     "osv_id": "ASB-A-406243581",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowManagerService now strips privileged LayoutParams.privateFlags (PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) that a caller tries to add during window relayout without holding INTERNAL_SYSTEM_WINDOW or MANAGE_ACTIVITY_TASKS.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the visible context dependency is the name of the neighbouring inputFeatures sanitizer (sanitizeSpyWindow on 14, sanitizeInputFeatures on 15/16). For the test, older trees may need the PackageManager import and the newWindowBuilder helper (or keep createWindow)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d550a457e65ccbbf252ec2e60d93e8bcd111de0e",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 90,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48641",
     "osv_id": "ASB-A-392699284",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes the static NFC HAL callbacks Nfc::eventCallback and Nfc::dataCallback in hardware/st/nfc aidl/Nfc.h take a mutex-protected local std::shared_ptr copy of the static Nfc::mCallback before dereferencing it, so the INfcClientCallback object cannot be destroyed between the null check and the sendEvent()/sendData() call.",
      "confidence": "medium",
      "backport_notes": [
       "The change is self-contained in aidl/Nfc.h and was applied verbatim on 14, 15 and 16. When backporting, also check whether the code that writes mCallback (Nfc::open per the commit message) takes sCallbackLock, because this commit does not show the writer side."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/st/nfc",
       "sha": "c6da9eeb710c6690d189cb2d1b80b44755860b55",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/st/nfc"
     ]
    },
    {
     "cve": "CVE-2025-48642",
     "osv_id": "ASB-A-455777515",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes pvmfw and libvmbase aarch64 data cache maintenance from \"dc cvau\" (clean to Point of Unification) to \"dc cvac\" (clean to Point of Coherency) so that memory zeroed by pvmfw before handing off to the payload is written back to main memory on SoCs with a unified cache before the PoC.",
      "confidence": "high",
      "backport_notes": [
       "The change is a simple mnemonic swap (cvau -> cvac), but the file layout differs per release. 14 uses pvmfw/src/entry.rs and pvmfw/src/helpers.rs. 15 uses pvmfw/src/entry.rs and vmbase/src/memory/util.rs. 16 uses guest/pvmfw/src/arch/aarch64/payload.rs and libs/libvmbase/src/arch.rs (dc! macro). The 14 and 15 backports change two flush loops; 16 changes three. Grep for every \"cvau\" in pvmfw and vmbase rather than relying on hunk positions."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Virtualization",
       "sha": "f0271f36388ec9630d89ff8b3ee4cb22e2ca3eaf",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Virtualization"
     ]
    },
    {
     "cve": "CVE-2025-48644",
     "osv_id": "ASB-A-449181366",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Bounds the total size of resolved IME metadata attributes read in the InputMethodInfo constructor with a new TypedArrayWrapper and MetadataReadBytesTracker (200KB cumulative, XmlPullParserException when exceeded), and changes IInputMethodManager.getEnabledInputMethodSubtypeList to return a blob-backed InputMethodSubtypeSafeList so it avoids TransactionTooLargeException.",
      "confidence": "high",
      "backport_notes": [
       "The parsing commit relies on MAX_METADATA_SIZE_BYTES, COMPONENT_NAME_MAX_LENGTH and the InputStream.skip() file size validation already existing in InputMethodInfo: the diff references them but does not add them. Adapt the <input-method> attribute list and the getMaxLength switch to the attributes present on the target branch (14 lacks languageSettingsActivity, isVirtualDeviceOnly and supportsConnectionlessStylusHandwriting).",
       "The SafeList commit changes an AIDL return type and removes InputMethodInfoSafeList.empty(). The client (IInputMethodManagerGlobalInvoker) and the server (IInputMethodManagerImpl, ZeroJankProxy, InputMethodManagerService) must be updated together, and any out-of-tree users of empty() or of the raw IInputMethodManager method will need adjusting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2bca2265ff3e26b09f9b31c31063147a94e4c5aa",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "a438ce172b441c8297eadde8d990ab292f5aa7d1",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48645",
     "osv_id": "ASB-A-443062265",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DeviceAdminInfo.loadDescription now catches an OutOfMemoryError thrown while loading the device admin's description resource through PackageManager.getText and rethrows it as Resources.NotFoundException, the exception type the method already declares.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained 5-line change with no dependencies: wrap pm.getText in DeviceAdminInfo.loadDescription in the same try/catch. On a fork, check that the method still declares NotFoundException and that its callers handle it. If you want a size limit rather than only OOM handling, the hidden loadDescriptionSafe, shown in context, already trims through ComponentInfo.loadDescription."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cee45869c491d4e39877918ee881eb60dec7d6e5",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48646",
     "osv_id": "ASB-A-457742426",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In ActivityStarter's FLAG_ACTIVITY_FORWARD_RESULT handling, the fix stops blindly copying sourceRecord.launchedFromPackage into callingPackage. It now copies it only after resolving that package name through PackageManagerInternal.getPackageUid and confirming UserHandle.isSameApp with the calling uid.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The ActivityStarter.java hunk is identical across 14/15/16; only the line offset differs. It relies on mService.getPackageManagerInternalLocked(), PackageManagerInternal.getPackageUid and UserHandle.isSameApp, which the unmodified 14 hunk also uses.",
       "The new test depends on ActivityBuilder.setLaunchedFromPackage/setLaunchedFromUid and on mMockPackageManager in WindowTestsBase. On older or forked trees, check that these test helpers exist before carrying the test over."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c148b4fae6347652231d1c4a633f5cc9a8f057f8",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48654",
     "osv_id": "ASB-A-442392902",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "CompanionDeviceManagerService now disassociates every association that the AssociationStore reports as revoked during service start-up, so revoked associations no longer survive a reboot.",
      "confidence": "medium",
      "backport_notes": [
       "The patch depends on AssociationStore.getRevokedAssociations(), DisassociationProcessor.REASON_REVOKED and the mDisassociationProcessor field already existing in CompanionDeviceManagerService. Forks with older companion code (Android 14 shows 1/8 and Android 15 shows 6/8 of the patched region) must check that these exist before applying the change."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "924df83d73d9f938fde025c2e793ca12646207e0",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-64783",
     "osv_id": "ASB-A-483074175",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Google fixed CVE-2025-64783 by replacing the Android copy of the Adobe DNG SDK with upstream release 1.7.1 build 2410 in a single 61-file commit (\"Update to DNG SDK 1.7.1 2410\"), rather than with a targeted patch; the specific vulnerable code path is not identified in the material.",
      "confidence": "low",
      "backport_notes": [
       "This is a full SDK snapshot replacement, not a cherry-pickable hunk. It changes public headers (dng_fingerprint::data made private, dng_md5_printer::Process renamed ProcessPtr, the WriteTIFF/WriteDNG parameter lists, the dng_exception base class), so consumers of those APIs must be rebuilt and possibly adjusted. The new dng_jxl.cpp code also calls libjxl APIs such as JxlEncoderUseBoxes and output-processor-style callbacks, so the bundled libjxl must provide them.",
       "An Android-local edit marked \"BEGIN GOOGLE MODIFICATION\" appears in dng_misc_opcodes.cpp (the dng_opcode_MapTable::ProcessArea plane loop) at r8 and should be preserved when importing. Google applied the same commit (linked by change_id) to 14, 15 and 16, so taking the whole commit is the intended route."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "6b5cf2a88ebd2b099e56d0d4717e962772ff9067",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2025-64784",
     "osv_id": "ASB-A-483074618",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Google fixed this High-severity information-disclosure bug in external/dng_sdk by syncing the whole library to Adobe DNG SDK 1.7.1 build 2410 (61 files, +2721/-694). This is a bulk update that bundles many parser and buffer changes rather than a single targeted patch.",
      "confidence": "low",
      "backport_notes": [
       "This is not a cherry-pickable point fix. It replaces the SDK snapshot across 61 files and includes API-breaking changes: dng_fingerprint::data becomes private, Process is renamed ProcessPtr, the WriteTIFF/WriteDNG signatures change, and dng_exception gets a new base class. Take the whole commit rather than individual hunks, update out-of-tree consumers, and check that Android-local GOOGLE MODIFICATION code (for example in dng_misc_opcodes.cpp) is still present after the pick.",
       "Google applied the same full-update commit to the 14, 15 and 16 security branches, and the diff index hashes shown match. This suggests the pre-fix snapshot was the same on all three."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "6b5cf2a88ebd2b099e56d0d4717e962772ff9067",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2025-64893",
     "osv_id": "ASB-A-483075215",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Google replaced the Android copy of external/dng_sdk with Adobe DNG SDK 1.7.1 build 2410 (single commit, 61 files, +2721/-694), a wholesale upstream update rather than a targeted fix.",
      "confidence": "low",
      "backport_notes": [
       "This is a whole-SDK version bump, not a cherry-pickable hunk: downstream forks must re-apply any local GOOGLE MODIFICATION blocks and adjust callers for the API changes (dng_fingerprint::data now private, dng_md5_printer::Process -> ProcessPtr, new WriteTIFF/WriteDNG parameters, dng_exception deriving from std::exception). Google applied the same commit to 14, 15 and 16.",
       "Out-of-project consumers of dng_sdk (e.g. Skia's SkRawCodec, general platform knowledge) should be rebuilt and checked against the changed dng_fingerprint and dng_md5_printer APIs."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "6b5cf2a88ebd2b099e56d0d4717e962772ff9067",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 6b5cf2a88ebd",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2026-0005",
     "osv_id": "ASB-A-366405211",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a permission-gated hidden IActivityTaskManager.rebuildSystemLockTaskPinnedMode() that re-applies screen-pinning (LOCK_TASK_MODE_PINNED) status bar, keyguard, recents and task-listener state. SystemUI's KeyguardViewMediator now calls it when the lock task state is still pinned, and the keyguard-enabled flag is kept across a KeyguardService disconnect, so app pinning stays in effect after a SystemUI crash (per the commit title).",
      "confidence": "medium",
      "backport_notes": [
       "Apply all five source files together: the SystemUI caller depends on the new AIDL method and on the ActivityTaskManagerService/LockTaskController implementation.",
       "On trees where KeyguardViewMediator has no mActivityTaskManagerService field, use ActivityTaskManager.getService() as the 14 variant does. Where ArrayList.getFirst() is unavailable, use mLockTaskModeTasks.get(0), also as in the 14 variant."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "92b109b6041452f6e713dcce512fa8de18556360",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0011",
     "osv_id": "ASB-A-454062218",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes Settings.enableSystemPackageLPw able to reuse the disabled PackageSetting of a shared-UID system app when its update is uninstalled. It does this by letting addPackageLPw accept the package even when registerExistingAppId fails for shared uid, and by copying the sharedUserAppId. As a result, the preloaded version is no longer re-scanned and installed as a new app with a possibly changed UID.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, no dependency shown. On 14, addPackageLPw has the longer legacy signature and no isSdkLibrary / Flags.disallowSdkLibsToBeApps clause. Append the hasSharedUser parameter, OR it into the registerExistingAppId condition, pass false from the settings-reading call site, and add one nullable() to the MockSystem.kt stub, otherwise the mocking tests will not compile."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "51368785bd09cd652ed9851727b16545cb92c4e5",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0012",
     "osv_id": "ASB-A-392614656",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In SystemUI's ExpandableNotificationRow.setHideSensitive, the hide-sensitive animation is now skipped (non-animated branch taken, animators cancelled) exactly when the row is showing the public/redacted layout and that state did not change, instead of being gated on Flags.skipHideSensitiveNotifAnimation() (Android 16) or never skipped unless animated == false (Android 14/15).",
      "confidence": "medium",
      "backport_notes": [
       "For branches without Flags.skipHideSensitiveNotifAnimation() or NotificationContentAlphaOptimization (14/15 shape), apply the 14/15 variant: add the two locals and extend `if (!animated)` to `if (!animated || shouldSkipHideSensitiveAnimation)`; for branches with the flag-gated condition (16 shape), replace the flag call with mShowingPublic. After the fix setHideSensitive no longer reads the flag."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ef2a8e20316405ce6b704e800b1808c3aaf5ee43",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0013",
     "osv_id": "ASB-A-447135012",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PickActivity.setupLayout in DocumentsUI now calls setSelector(null) on the copied \"more apps\" intent so that a selector supplied by the calling app cannot steer which activity DocumentsUI launches.",
      "confidence": "high",
      "backport_notes": [
       "The patch is self-contained: insert `moreApps.setSelector(null);` right after `moreApps.setPackage(null);` in PickActivity.setupLayout. Only the cross-profile context lines that follow differ between branches. 14 calls supportsCrossProfile() and makes a single getCrossProfileResolveInfo call; 15 and 16 have the private-space/getCanForwardToProfileIdMap branch. Anchor the hunk on the setComponent/setPackage lines."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/DocumentsUI",
       "sha": "9f2d3f09f8fdc099d5a2d4c8bf3e8ec460bb9233",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2026-0014",
     "osv_id": "ASB-A-443742082",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early `return false` for SDK sandbox UIDs (Process.isSdkSandboxUid) in the AppOpsService trust helper used for attribution tag validation, so sandboxed callers are no longer treated as trusted/system.",
      "confidence": "medium",
      "backport_notes": [
       "The hunk context is the same on 14 to 16 (anchor: `if (packageName == null) { return true; }` followed by `int appId = UserHandle.getAppId(uid);`), so it applies with only offset changes. Process.isSdkSandboxUid exists in android.os.Process since Android 13 (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b51a58ecec96558e1c6b1d47728f45a8795dc7ab",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0015",
     "osv_id": "ASB-A-445917646",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In AppOpsService, an untrusted proxy (OP_FLAG_UNTRUSTED_PROXIED) can no longer get a proxied attribution tag accepted from its own manifest or through the system-proxy allowance. isPackageNullOrSystem now checks that the named package belongs to the uid. The dry-run start path and the finish path no longer create per-tag AppOps state. A new client-id based lookup lets finishes of untrusted proxied starts find their in-progress event, falling back to the null tag.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. Branches differ in dry-run and per-device structure. Where dry run is a flag of the start function, use the Android 14 form (dryRun ? null : attributionTag, op.mAttributions). Where mDeviceAttributedOps exists, use the matching persistent-device helper (getPersistentId or getPersistentDeviceIdForOp). The 5-arg verifyAndGetBypass overload is replaced, so every call site must be updated for the new isProxyTrusted parameter."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a4523e227733ae20eafe4ec3e85474a5b7ebf7c6",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0017",
     "osv_id": "ASB-A-444673089",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "When the new per-modality biometric settings keys (FACE_/FINGERPRINT_KEYGUARD_ENABLED, FACE_/FINGERPRINT_APP_ENABLED) are unset after an OTA, BiometricService and the Settings preference controllers now inherit the value of the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED keys instead of falling back to the hard-coded default.",
      "confidence": "high",
      "backport_notes": [
       "Apply both commits together. The frameworks/base change gives the service the correct default when it reads the keys; the Settings change persists the migration. In Settings, insert the migration block after super(context, key). Constructor bodies may differ between branches: the android-16.0.0_r4 copy of FaceSettingsAppsPreferenceController in the index has a sensor-strength loop setting mIsAllowed that the security-branch diff context does not show. Expect context conflicts."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cea235f00865ff73344f1efa9494e47beecc3fd5",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "7d8fbee887fc9577337c2a80513ae4399bf60111",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-0020",
     "osv_id": "ASB-A-453649815",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix makes the manifest parser trim leading and trailing whitespace from the android:name of <permission>, <permission-tree> and <permission-group> declarations in ParsedPermissionUtils.",
      "confidence": "medium",
      "backport_notes": [
       "The file sits at different paths: services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java on 14 and core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java on 15+. The hunks are small and self-contained (three setName(...trim()) calls).",
       "In parsePermissionTree, the trim is placed differently on 14 and on 15/16. On 15/16 it comes before the indexOf('.') check. On 14 it comes after a validation block and before setProtectionLevel. Check which name-based checks run before the trim in your tree."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e770e9f0234158f4631c7147b64a1d70e0843d0b",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0021",
     "osv_id": "ASB-A-430047417",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On multi-pane (activity-embedding) devices, AppInfoBase now checks INTERACT_ACROSS_USERS_FULL against the package returned by SettingsActivity.getInitialCallingPackage() instead of using the getLaunchedFromUid()-based check, and the AppUsageAccessSettingsActivity intent-filter gains android:priority=\"1\".",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. It depends on com.android.settings.activityembedding.ActivityEmbeddingUtils.isEmbeddingActivityEnabled(Activity), SettingsActivity.getInitialCallingPackage(), and the existing mPm PackageManager field in AppInfoBase. The identical diff applied on 14, 15 and 16. A tree lacking getInitialCallingPackage() would need that helper first.",
       "OEM trees that customize the Settings$AppUsageAccessSettingsActivity manifest entry should still add android:priority=\"1\" to its USAGE_ACCESS_SETTINGS intent-filter, because this is part of the upstream fix."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "48af8a13dd12ecbd0569c328a56d1a7b61a59ca3",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-0023",
     "osv_id": "ASB-A-459461121",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PackageInstallerService now unconditionally clears PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE from caller-supplied SessionParams.installFlags during install session creation, before re-adding it only when DevicePolicyManagerInternal.isUserOrganizationManaged(userId) is true.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit change: insert the mask line immediately before the `if (dpmi != null && dpmi.isUserOrganizationManaged(userId))` block in PackageInstallerService; it only requires that this DevicePolicyManagerInternal-based conditional and PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE already exist in the target tree."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "09055276288a68cf35b0f84ba32e28822f74ecf9",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0024",
     "osv_id": "ASB-A-326211886",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now forces redaction when a file is opened via the ContentResolver open path with an EXTRA_MEDIA_CAPABILITIES_UID whose identity holds PERMISSION_IS_REDACTION_NEEDED, so a picker acting on behalf of another app respects that app's redaction requirements (ACCESS_MEDIA_LOCATION bypass per the commit message).",
      "confidence": "high",
      "backport_notes": [
       "Single, self-contained commit. It relies on existing symbols that must exist in the target branch: LocalCallingIdentity.fromExternal (called as fromExternal(getContext(), mUserCache, uid, null, null)), PERMISSION_IS_REDACTION_NEEDED, mUserCache and MediaStore.EXTRA_MEDIA_CAPABILITIES_UID. Only the surrounding redaction-range type differs (RedactionInfo on 14 vs long[] on 15/16)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "69a25763cdb46c8f23fe9eb976132acbe2af82d6",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-0025",
     "osv_id": "ASB-A-433746973",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Notification.java now reads EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES through getParcelableArrayFromBundle(extras, KEY, Bundle.class) instead of extras.getParcelableArray(KEY, Parcelable.class), in visitUris, in hasImage and in the MessagingStyle code that restores mMessages/mHistoricMessages from extras. The message arrays are therefore read as Bundle-typed arrays before being passed to MessagingStyle.Message.getMessagesFromBundleArray.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit, mechanical change: replace extras.getParcelableArray(KEY, Parcelable.class) with getParcelableArrayFromBundle(extras, KEY, Bundle.class) at the five reads in Notification.java. The change depends on the getParcelableArrayFromBundle helper existing on the target branch, which the material does not show; check this before backporting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "014dea279c49d532bc4fbbdebbc024133967b6a8",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0034",
     "osv_id": "ASB-A-428701593",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Caps the per-user lists of approved and user-set managed services (notification listeners, assistants, condition providers) in ManagedServices at 100 entries. setPackageOrComponentEnabled now reports whether the change was applied, and NotificationManagerService skips follow-up actions (secondary approvals, broadcasts, policy save) when it was not.",
      "confidence": "high",
      "backport_notes": [
       "The change alters the return type of the protected setPackageOrComponentEnabled overloads. Every subclass override (NotificationAssistants, NotificationListeners, and any vendor subclasses of ManagedServices) must be updated to return boolean. The listener grant path must also be reordered so mListeners is updated before mConditionProviders.",
       "Mockito mocks return false by default for boolean methods (general platform knowledge). Existing NotificationManagerServiceTest cases therefore need the added thenReturn(true) stubs for mListeners/mAssistants/mConditionProviders; without them, the follow-up broadcasts those tests verify will be skipped."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e363f82104566378b4b9936d6caf27c3ee631d80",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0035",
     "osv_id": "ASB-A-418773439",
     "source": "osv",
     "announced": "2026-03-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now rejects MediaStore CREATE_*_REQUEST calls (createWriteRequest/createDeleteRequest and siblings) with an IllegalArgumentException when any requested URI does not resolve to exactly one existing row in the Files table at request time, instead of building a PermissionActivity intent for it.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The MediaProvider.java hunk is identical across 14/15/16; only the line offset differs. It depends on the existing helpers queryForSingleItem and clearLocalCallingIdentity/restoreLocalCallingIdentity (LocalCallingIdentity), and on FileColumns._ID. Place it immediately before the `new Intent(method, null, context, PermissionActivity.class)` construction in the createRequest handler."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "119013a3d7e8f1eab671bce4c6a85748752081ed",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    }
   ],
   "tags": [
    "android-security-16.0.0_r4"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "442272360"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d17f6214cd67705baf1f15089adcba382df1abda",
       "subject": "Block overriding satellite carrier config keys on user builds",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      },
      {
       "project": "platform/packages/services/Telephony",
       "sha": "20949e75b704f9533923cc55e7b5136e66d926cd",
       "subject": "Block overriding satellite carrier config keys on user builds",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "378087531"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1e6af3296d50e3786f33afbbdcc80aa115a18172",
       "subject": "Prevent long press on profile notifs when locked",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "319101158",
      "419106561"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a819a9650c52cc2fe3eed0ff9fdca466fdd39299",
       "subject": "Use target sdk for background check on Wear remote intents.",
       "tag": "android-security-16.0.0_r4",
       "merged": "2025-12-01",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "433251166"
     ],
     "commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "8ec74c568b5881901cad0f1147fdc607702101e0",
       "subject": "Truncate WindowInfo.name during parcelling.",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "435737668"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "9bcd460fc3fd43abfd65ce57d8b62dc0a76c7bf0",
       "subject": "Update RuntimePermissionsUpgradeController to accept null PermGroups",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "435737668",
      "456903810"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "ad0244102357e8ef9ce08b397c78df0e4c558ec9",
       "subject": "V2 Merge all permissions in a shared UID for every package in that UID",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "315320090"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "70953c41c68f56296045fa577892c914d3da146b",
       "subject": "Add SystemGalleryRoleBehavior",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "315320090",
      "418085016"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "0809fe6f9ae8fdeb63b4c6fb71d90456ad38cf72",
       "subject": "Revert^2 \"Ensure Gallery always shows as system fixed\"",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      },
      {
       "project": "platform/packages/modules/Permission",
       "sha": "4f55f5fae1ffe3c766638edeaf19a117d9f16670",
       "subject": "Revert \"Display the system gallery storage permission as system fixed\"",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      },
      {
       "project": "platform/packages/modules/Permission",
       "sha": "e11410da0c73b8860c77e69bb14a09bb0851d181",
       "subject": "Revert \"Ensure Gallery always shows as system fixed\"",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    },
    {
     "bug_ids": [
      "388530367"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "0d45a6a15770ca3ec5d0d2264983c057aae41939",
       "subject": "Fix SQL injection vulnerability in SMS/MMS providers",
       "tag": "android-security-16.0.0_r4",
       "merged": "2026-01-07",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r4"
     ],
     "tag": "android-security-16.0.0_r4"
    }
   ],
   "coverage": {
    "n_ingested_tags": 9,
    "first_ingested_tag": "android-security-16.0.0_r1",
    "latest_ingested_tag": "android-security-16.0.0_r9",
    "window_base_tag": "android-16.0.0_r2",
    "window_start": "2025-06-10",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {
    "CVE-2025-48631": {
     "cve": "CVE-2025-48631",
     "summary": "Adds a hard 4096 px per-dimension limit in LocalImageResolver.onHeaderDecoded so that images whose header reports a larger width or height are rejected with an exception before any pixel decoding is attempted.",
     "component": "platform/frameworks/base, core/java/com/android/internal/widget/LocalImageResolver (notification MessagingStyle/ConversationStyle image loading)",
     "weakness": "Missing upper-bound check on decoded image dimensions (uncontrolled memory allocation / resource exhaustion)",
     "root_cause": "LocalImageResolver.onHeaderDecoded received the image size from ImageDecoder.ImageInfo and used it to compute a downscale ratio toward maxWidth/maxHeight (resolveImage(Uri, Context) passes DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480). Nothing rejected images whose declared dimensions are far beyond what the device can hold in memory (formats such as JPEG allow up to 65,535x65,535). Decoding such an image was still attempted. Per the commit message this could take up all device memory, and a notification carrying such an image caused a crashloop.",
     "fix_analysis": [
      "core/java/com/android/internal/widget/LocalImageResolver.java: adds private static final int DEFAULT_DECODE_HARD_LIMIT_PX = 4096, with a comment that images larger than this are not decoded at all.",
      "LocalImageResolver.onHeaderDecoded: right after reading info.getSize(), if size.getWidth() > 4096 or size.getHeight() > 4096, it throws a RuntimeException with the dimensions in the message. This happens before the existing ratio/target-size logic runs. Because this is the ImageDecoder.OnHeaderDecodedListener callback, the throw aborts the decode at the header stage.",
      "LocalImageResolver.resolveImage(Uri, Context) (unchanged) catches Exception around ImageDecoder.decodeDrawable and rethrows it as IOException, so API users see an IOException for oversized images.",
      "core/tests/coretests: adds res/drawable/test16000x16000.gif and LocalImageResolverTest.resolveImage_veryLargeResource_throwsException, which expects an IOException from resolveImage(Uri, Context) for that resource."
     ],
     "completeness": [
      "The check sits in onHeaderDecoded, so every LocalImageResolver path that passes this callback to ImageDecoder gets the limit. The material shows resolveImage(Uri, Context) using it; other overloads are not shown.",
      "The limit is per dimension (width or height > 4096), not on total pixel count; a 4096x4096 image is still accepted and decoded.",
      "The exception thrown from inside the decoder callback is a RuntimeException. Only resolveImage(Uri, Context) is shown converting it to IOException; whether other callers of onHeaderDecoded in this class wrap or propagate it is not visible in the material.",
      "The fix is specific to LocalImageResolver; this commit does not change ImageDecoder itself or other image loading paths in the platform."
     ],
     "behavior_changes": [
      "Images with width or height above 4096 px now fail to load through LocalImageResolver instead of being decoded and downscaled. resolveImage(Uri, Context) reports this as an IOException wrapping a RuntimeException with the message 'Image dimensions (WxH) exceed the maximum allowed size.'",
      "The stricter check applies regardless of the requested maxWidth/maxHeight; oversized sources are rejected even when the caller requests a small target size."
     ],
     "preconditions": [
      "An image reaches LocalImageResolver.onHeaderDecoded, i.e. it is loaded via LocalImageResolver. Per the class doc, the class is used for MessagingStyle/ConversationStyle notification messages; the commit message cites a notification causing a crashloop.",
      "The image header declares dimensions large enough that attempting to decode it exhausts device memory (the test uses a 16000x16000 GIF)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 34926e40b0d8 in android-security-14.0.0_r24, identical changes applied with a 3-line hunk offset (onHeaderDecoded at line 249 vs 252); 8/8 patched lines present at android-security-14.0.0_r29",
      "15": "same fix: platform/frameworks/base b645abd5b328 in android-security-15.0.0_r12, identical diff; 8/8 patched lines present at android-security-15.0.0_r17",
      "16": "same fix: platform/frameworks/base d6df825fda3a in android-security-16.0.0_r3 (cherry-pick of 449f35b532d5); 8/8 patched lines present at android-security-16.0.0_r8",
      "17": "not affected per the bulletin; the patched region (8/8 lines) is present at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single self-contained commit. Its only context dependency is the existing onHeaderDecoded(ImageDecoder, ImageInfo, int, int) method and the DEFAULT_MAX_SAFE_ICON_SIZE_PX constant, so it applies with a small line offset on 14 and cleanly on 15/16. The test needs the binary test16000x16000.gif resource added to core/tests/coretests/res/drawable."
     ],
     "confidence": "high",
     "unknowns": [
      "Which process crashloops (the commit message only says a notification causes it) and which notification field carries the image.",
      "Whether the other LocalImageResolver overloads that use onHeaderDecoded catch the new RuntimeException or let it propagate to their callers.",
      "Why 4096 was chosen, and whether the pre-fix decode allocated at full source size despite the target size set in onHeaderDecoded (the diff does not show the decoder internals)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0006": {
     "cve": "CVE-2026-0006",
     "summary": "The fix upgrades external/libopenapv as a whole, from upstream v0.1.13.1 to v0.2.0.0 (commit cf0a0e7a810e, bug 423894847). This replaces the APV bitstream/VLC code (src/oapv_vlc.c), the metadata handling (src/oapv_metadata.c), the bitstream reader/writer (src/oapv_bs.c) and the core codec (src/oapv.c) with newer upstream code, and changes the public API in inc/oapv.h.",
     "component": "platform/external/libopenapv (OpenAPV codec library). Per index search, it is used by the Codec2 software APV components in frameworks/av/media/codec2/components/apv.",
     "weakness": "unknown",
     "root_cause": "The material does not isolate the vulnerable code. The fix commit is a version bump ('Upgrade libopenapv to v0.2.0.0') that touches 47 files, with 2651 insertions and 2383 deletions. The diff provided covers the build files, the sample apps, inc/oapv.h, the AVX/NEON transform code and the start of src/oapv.c. The diffs for src/oapv_vlc.c (1458 lines changed), src/oapv_metadata.c (367) and src/oapv_bs.c (28) are in the truncated part. The bulletin only gives the severity (Critical) and type (RCE). Visible changes: the oapvm_payload metadata structure is reordered (data_size renamed to size and moved before data, uuid moved to the end); oapvm_set loses its uuid parameter; new error codes are added (OAPV_ERR_INVALID_PROFILE, OAPV_ERR_INVALID_WIDTH, OAPV_ERR_INVALID_HEIGHT, OAPV_ERR_INVALID_QP, OAPV_ERR_INVALID_FAMILY). The specific missing check in v0.1.13.1 cannot be identified from the provided diff.",
     "fix_analysis": [
      "The fix is not a targeted patch. It imports upstream openapv v0.2.0.0 through external_updater (METADATA: version v0.1.13.1 -> v0.2.0.0, last_upgrade_date 2025-07-28).",
      "inc/oapv.h, public API/ABI changes:\n- oapvm_payload field layout changed (size before data, uuid last).\n- oapvm_set(mid, group_id, type, data, size) loses the uuid argument.\n- oapv_version() becomes oapv_version(unsigned int *ver_num).\n- New error codes OAPV_ERR_INVALID_PROFILE/WIDTH/HEIGHT/QP/FAMILY.\n- New profile constants (422-12, 444-10, 444-12, 4444-10, 4444-12, 400-10).\n- OAPV_CS_YCBCR400_14LE and OAPV_CS_YCBCR420_14LE removed; 12-bit 422/444/4444 color-space constants added.\n- oapve_param.csp and OAPV_CDESC_THREADS_AUTO removed.\n- OAPV_CFG_SET_AU_BS_FMT/OAPV_CFG_GET_AU_BS_FMT and oapve_family_bitrate() added.\n- OAPV_EXPORT is empty when ANDROID is defined.",
      "src/oapv.c: the pixel-conversion helpers now take a bit-depth parameter instead of being fixed at 10 bits (imgb_to_blk, imgb_to_blk_16, imgb_to_blk_p21x_*, blk_to_imgb_16, blk_to_imgb_p21x_*). imgb_pad and imgb_pad_p210 are new, and plus_mid_val is removed. The diff for the decoder functions (dec_frm_prepare, dec_tile_comp, dec_tile, oapvd_decode, oapvd_info) is in the truncated portion.",
      "src/oapv_vlc.c, src/oapv_metadata.c, src/oapv_bs.c: the bitstream and metadata parsers are replaced by the v0.2.0.0 versions, and their diffs are not in the material. In the context shown at android-security-16.0.0_r8, oapv_bsw_sink asserts bs->cur + BSW_GET_SINK_BYTE(bs) < bs->end and oapvd_vlc_metadata takes pbu_size. The material does not show whether either is new.",
      "src/avx/oapv_tq_avx.c, src/neon/oapv_tq_neon.c, src/oapv_tq.c: on the encoder side, the forward transform signature changes to oapv_fn_tx_t(src, shift1, shift2, line), with a two-stage transform written back into src (oapv_tx_avx, oapv_tx_pb8b_neon, oapv_tx). The AVX inverse transform and dequantization code (decoder side) is moved into macros (ITX_PROCESSING_ODD/EVEN, ITX_POSTPROCESSING, DQUANT_POSTPROCESSING). The visible hunks reorganize existing code and are not evidently security related."
     ],
     "completeness": [
      "Because the fix is a full library upgrade, it covers every change between upstream v0.1.13.1 and v0.2.0.0, not only the vulnerable path. The hunk that fixes the defect cannot be identified from the provided (truncated) diff.",
      "The upgrade changes the public API/ABI (oapvm_set signature, oapvm_payload layout, oapv_version signature, removed oapve_param.csp). The commit message says 'All commits must go together' and lists two Cherrypick-From changes. The content of the other change is not in the material.",
      "Per index search, frameworks/av/media/codec2/components/apv (C2SoftApvDec.cpp, C2SoftApvEnc.h) uses this library. The material does not show whether those callers were updated in the same security tag."
     ],
     "behavior_changes": [
      "Callers can receive new library error codes: OAPV_ERR_INVALID_PROFILE (-400), OAPV_ERR_INVALID_WIDTH (-405), OAPV_ERR_INVALID_HEIGHT (-406), OAPV_ERR_INVALID_QP (-410), OAPV_ERR_INVALID_FAMILY (-501).",
      "New profile constants and 12-bit color-space constants are defined (422-12, 444-10/12, 4444-10/12, 400-10). The 14-bit 400/420 color-space constants are removed.",
      "oapvm_set no longer takes a uuid parameter. The new oapvm_payload field order and the data_size -> size rename break source and binary compatibility for code that uses metadata.",
      "The oapv_version() signature changes to oapv_version(unsigned int *ver_num). The CMake build now takes the version from the OAPV_VER_* macros in inc/oapv.h instead of git tags or version.txt.",
      "oapve_param.csp is removed. OAPV_CFG_SET_AU_BS_FMT is added to select raw_bitstream_access_unit framing."
     ],
     "preconditions": [
      "An APV bitstream is passed to libopenapv's decoder (oapvd_decode / oapvd_info). Per index search, the library is wrapped by the Codec2 software decoder C2SoftApvDec in frameworks/av/media/codec2/components/apv.",
      "Which bitstream construct triggers the defect is not known from the material."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin; external/libopenapv is not present in android-security-14.0.0_r29.",
      "15": "not affected: not listed by the bulletin; external/libopenapv is not present in android-security-15.0.0_r17.",
      "16": "same fix: the upstream upgrade to v0.2.0.0 landed as cf0a0e7a810e in android-security-16.0.0_r4. At android-security-16.0.0_r8 the files shown (METADATA v0.2.0.0, inc/oapv.h OAPV_VER_MAJOR 2) match the upgraded state; the resolver reports 2/8 lines of the patched region present.",
      "17": "not affected: not listed by the bulletin. At android-security-17.0.0_r1 the resolver finds 2/8 lines of the patched region; the library version there is not shown."
     },
     "backport_notes": [
      "This library bump breaks the API/ABI (oapvm_set, oapvm_payload, oapv_version, oapve_param.csp). The commit message says 'All commits must go together' and lists two Cherrypick-From entries. Callers of the changed functions (per index search: the Codec2 APV components in frameworks/av) must be checked and adapted when taking it.",
      "The material contains no minimal patch. A tree that cannot take the full v0.2.0.0 import would need to review upstream v0.1.13.1..v0.2.0.0 for src/oapv_vlc.c, src/oapv_metadata.c and src/oapv_bs.c; the provided diff does not show those changes."
     ],
     "confidence": "low",
     "unknowns": [
      "The specific vulnerable function and missing check in v0.1.13.1. The diffs for src/oapv_vlc.c, src/oapv_metadata.c, src/oapv_bs.c and the decoder half of src/oapv.c are truncated in the material.",
      "The trigger (which PBU, metadata or frame-header construct) and the memory-safety class of the defect.",
      "What the second Cherrypick-From change (45025ca521e7) contains, and whether frameworks/av callers needed changes.",
      "Whether the new profiles and 12-bit formats are accepted by the decoder, or only defined in the header.",
      "Which library version Android 17's tree carries."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43766": {
     "cve": "CVE-2024-43766",
     "summary": "In the Bluetooth SMP stack, smp_proc_srk_info now always calls smp_key_distribution_by_transport before it saves the peer CSRK to the security record, and the save_peer_csrk_after_ltk_gen flag is removed. The removed flag's description says this ordering makes the right security level get marked on the peer CSRK.",
     "component": "platform/packages/modules/Bluetooth, system/stack/smp (SMP key distribution); system/stack/acl for the Android 14 fix",
     "weakness": "Incorrect ordering of operations: the peer CSRK security record could be written with a sec_level taken before key distribution / LTK generation had finished (improper security-state tracking)",
     "root_cause": "In system/stack/smp/smp_act.cc smp_proc_srk_info, the flag-off path copied the peer CSRK into tBTM_LE_KEY_VALUE le_key with .sec_level = p_cb->sec_level and saved it with btm_sec_save_le_key(BTM_LE_KEY_PCSRK) before calling smp_key_distribution_by_transport(p_cb, NULL). The removed flag's description reads \"save peer csrk after ltk generated so that right security level is marked as part of peer csrk\". This indicates that the security level is only correct after that step, so the stored peer CSRK record could carry an incorrect security level. In the pre-fix Android 16 code the corrected ordering existed only behind the aconfig flag save_peer_csrk_after_ltk_gen. With the flag off, the old ordering was used.",
     "fix_analysis": [
      "system/stack/smp/smp_act.cc:smp_proc_srk_info (Android 16, 769a3dea8016): the flag-guarded block is collapsed. smp_key_distribution_by_transport(p_cb, NULL) now always runs right after smp_update_key_mask(p_cb, SMP_SEC_KEY_TYPE_CSRK, true) and before le_key is built and saved. The flag-off call to smp_key_distribution_by_transport at the end of the function is deleted.",
      "flags/pairing.aconfig: the save_peer_csrk_after_ltk_gen flag definition (bug 365567905) is removed, so flag state can no longer turn off the corrected ordering.",
      "Android 15 (d259085c6103, 'Save peer CSRK after ltk generation'): the same reorder, applied directly without a flag. smp_key_distribution_by_transport is inserted before le_key is built, and the call after btm_sec_save_le_key is removed.",
      "Android 14 (796182896347, 'Encrypt LE link immediately on reconnection'): a different change in system/stack/acl/btm_acl.cc StackAclBtmAcl::btm_establish_continue. For a BLE-transport ACL it looks up the tBTM_SEC_DEV_REC with btm_find_dev. If a record exists and is_le_link_key_known() is true, it calls btm_ble_set_encryption(remote_addr, BTM_BLE_SEC_ENCRYPT, role) before NotifyAclLinkUp."
     ],
     "completeness": [
      "The Android 15/16 change only touches smp_proc_srk_info. The sec_level copied into pcsrk_key is now read after smp_key_distribution_by_transport. The condition for saving the key (SMP_AUTH_BOND set in both peer_auth_req and loc_auth_req) is unchanged.",
      "The Android 16 commit message calls it a backport 'required to complete security fix for b#288144143', so it builds on earlier work for this bug (at least the flag-guarded ordering already in 16). This commit by itself only removes the flag gate.",
      "The Android 14 fix takes a different approach (encryption on LE reconnection in btm_acl.cc) and does not change the CSRK save ordering in smp_act.cc. The material does not show whether 14 also received the smp_act.cc reorder.",
      "The preceding function in the code context (the one that handles the peer identity address; its name is not shown in the excerpt) also calls smp_key_distribution_by_transport after saving BTM_LE_KEY_PID. The excerpt does not show a sec_level being set on pid_key, so it is not clearly the same pattern."
     ],
     "behavior_changes": [
      "smp_key_distribution_by_transport now runs before the peer CSRK is saved, in every configuration. The flag save_peer_csrk_after_ltk_gen no longer exists, so behavior changes on devices that had the flag off.",
      "The pcsrk_key.sec_level stored in the security record is read after key distribution.",
      "Android 14 only: when an LE ACL is set up with a known LE link key, encryption is requested immediately (btm_ble_set_encryption with BTM_BLE_SEC_ENCRYPT). A warning is logged when no security record exists."
     ],
     "preconditions": [
      "An LE SMP pairing in which the peer distributes its CSRK, so that smp_proc_srk_info runs with valid parameters.",
      "For the key to be saved, both peer_auth_req and loc_auth_req must have SMP_AUTH_BOND set.",
      "For the Android 14 path: an LE ACL comes up to a device whose security record reports is_le_link_key_known()."
     ],
     "per_major": {
      "14": "adapted fix: a different commit, 796182896347 in android-security-14.0.0_r15, adds immediate LE encryption on reconnection in btm_acl.cc btm_establish_continue. 3/3 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "adapted fix: d259085c6103 in android-security-15.0.0_r12 applies the smp_proc_srk_info reorder directly, without a flag. 8/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: 769a3dea8016 in android-security-16.0.0_r3 removes the save_peer_csrk_after_ltk_gen flag and makes the reorder unconditional. 5/8 lines of the patched region are present at android-security-16.0.0_r8, and the code context there shows the unconditional ordering in place.",
      "17": "not affected per the bulletin. 4/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "On a branch without the save_peer_csrk_after_ltk_gen aconfig flag, apply the Android 15 form: move smp_key_distribution_by_transport above the le_key construction and delete the trailing call. On a branch with the flag, follow the 16 commit: remove the flag from flags/pairing.aconfig and remove both flag checks.",
      "Android 14 used a separate change in btm_acl.cc (btm_establish_continue) that relies on btm_find_dev, is_le_link_key_known and btm_ble_set_encryption."
     ],
     "confidence": "medium",
     "unknowns": [
      "What smp_key_distribution_by_transport changes in p_cb->sec_level is inferred only from the removed flag's description. The function body is not in the material.",
      "The earlier or companion change(s) for bug 288144143 on Android 16, implied by the commit message ('required to complete security fix'), are not shown.",
      "The material does not show whether Android 14 also received the smp_act.cc CSRK reorder, or why Android 17 is not affected.",
      "The material does not describe the concrete information-disclosure impact behind the bulletin's High rating."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-32313": {
     "cve": "CVE-2025-32313",
     "summary": "Adds a bounds check in the NotificationHistory(Parcel) constructor so a position value read from parcel data is rejected (IllegalStateException) when it exceeds the rebuilt inner Parcel's data size, instead of being passed straight to Parcel.setDataPosition; on Android 14/15 the linked fix instead changes libbinder Parcel::appendFrom to base its capacity check on mDataPos rather than mDataSize.",
     "component": "platform/frameworks/base, core/java/android/app/NotificationHistory.java (notification history parceling); on Android 14/15 platform/frameworks/native libs/binder/Parcel.cpp (libbinder Parcel)",
     "weakness": "missing bounds check on untrusted parcel data (improper input validation of a data position)",
     "root_cause": "NotificationHistory's private constructor NotificationHistory(Parcel in) reads a blob, unmarshalls it into a Parcel, and when mHistoryCount > 0 reads listByteLength and positionInParcel directly from that data. It copies listByteLength bytes into a fresh mParcel via appendFrom, sets the data size, and then called mParcel.setDataPosition(positionInParcel) with no validation, so a sender-controlled position beyond the actual data could be installed as the Parcel's current position. The Android 14/15 linked fix addresses a lower-layer issue: Parcel::appendFrom decided whether to grow the buffer using (mDataSize+len) > mDataCapacity, ignoring that the copy is written at mDataPos, so a Parcel whose position had been moved past its size could be written beyond its capacity.",
     "fix_analysis": [
      "core/java/android/app/NotificationHistory.java : NotificationHistory(Parcel in) (frameworks/base 283bef4b2546 / 59c15187e7ee, same Change-Id Ica98d82d): after mParcel.appendFrom(...) and mParcel.setDataSize(mParcel.dataPosition()), a new check `if (positionInParcel > mParcel.dataSize()) throw new IllegalStateException(...)` is inserted before mParcel.setDataPosition(positionInParcel). The position is therefore bounded by the size of the data actually copied, and unparceling aborts with an exception on a bad value.",
      "libs/binder/Parcel.cpp : Parcel::appendFrom (frameworks/native 4e28321ba198 on 14, dc54178de228 on 15): the grow condition changes from `(mDataSize+len) > mDataCapacity` to `(mDataPos + len) > mDataCapacity`, so the capacity decision accounts for the real write offset. A new unit test AppendWithBadDataPos in libs/binder/tests/binderParcelUnitTest.cpp sets capacity 8 and position 10000 and expects appendFrom to return BAD_VALUE."
     ],
     "completeness": [
      "The Java check only rejects positionInParcel greater than mParcel.dataSize(); it does not reject negative values, and no validation is added for listByteLength, mHistoryCount or mIndex read from the same blob (listByteLength is still passed directly to appendFrom).",
      "The Java fix is confined to the parcel constructor; the write side (writeToParcel) and the per-notification read path (getNextNotification) are untouched in the diff.",
      "On Android 16 the material shows only the frameworks/base change; whether the libbinder appendFrom hardening is also present on 16 is not shown. On Android 14/15 the material shows only the libbinder change; whether the Java check was also applied there is not shown.",
      "The native change hardens appendFrom generically for any Parcel whose mDataPos exceeds mDataSize, which covers more callers than the single NotificationHistory site."
     ],
     "behavior_changes": [
      "Unparceling a NotificationHistory whose embedded position value exceeds the copied list length now throws IllegalStateException (\"Obtained an invalid position value(... from Parcel.\") instead of succeeding with an out-of-range data position.",
      "On 14/15, Parcel::appendFrom now grows or rejects based on mDataPos + len; per the added test, appending into a Parcel whose position is far beyond its capacity returns BAD_VALUE."
     ],
     "preconditions": [
      "A NotificationHistory Parcelable is reconstructed via its Parcel constructor from data controlled by another party, with mHistoryCount > 0 so the inner mParcel is built and the position value is consumed.",
      "Which IPC entry points deliver a NotificationHistory into a privileged process is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: the linked change is a lower-layer one in platform/frameworks/native (4e28321ba198, android-security-14.0.0_r25) that changes Parcel::appendFrom's grow check to use mDataPos and adds test AppendWithBadDataPos; at android-security-14.0.0_r29, 5/6 lines of that patched region are present. Whether the Java NotificationHistory check exists on 14 is not shown.",
      "15": "adapted fix: same libbinder change as 14 (dc54178de228, android-security-15.0.0_r13) in Parcel::appendFrom plus the unit test; at android-security-15.0.0_r17, 5/6 lines of the patched region are present. Whether the Java NotificationHistory check exists on 15 is not shown.",
      "16": "same fix: the 4-line bounds check in frameworks/base NotificationHistory.java landed as 283bef4b2546 (android-security-16.0.0_r4) and again as 59c15187e7ee (android-security-16.0.0_r6, same Change-Id, identical diff); at android-security-16.0.0_r8 all 4/4 lines are present.",
      "17": "not affected (not listed by the bulletin); at android-security-17.0.0_r1 the NotificationHistory check is present (4/4 lines)."
     },
     "backport_notes": [
      "The two fixes are independent and complementary: the Java check (4 inserted lines right after mParcel.setDataSize(mParcel.dataPosition()) in NotificationHistory(Parcel)) applies wherever that constructor sequence exists, and the one-line libbinder change in Parcel::appendFrom applies to any branch whose appendFrom still tests (mDataSize+len) > mDataCapacity; applying both gives the broadest coverage.",
      "The Android 16 commit is marked Ignore-AOSP-First and was merged twice into the 16 security branches under one Change-Id; use Merged-In Ica98d82d7d7c1c9c0defcff8d96bf5f36e8f5d36 to detect it."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the libbinder appendFrom hardening is present on Android 16/17, and whether the Java NotificationHistory check is present on Android 14/15 (the per-major facts list only one project per branch).",
      "Which binder interfaces or callers hand a NotificationHistory parcel to a privileged process (not shown in the material).",
      "The exact consequence of installing an oversized position in the vulnerable sequence (the material shows only the missing check and the native capacity logic).",
      "Which one of the 6 patched lines differs on 14/15 at the latest tags (5/6 present); not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48544": {
     "cve": "CVE-2025-48544",
     "summary": "Removes the Bundle extra MediaProvider#INCLUDED_DEFAULT_DIRECTORIES and instead threads an explicit Optional<List<String>> parameter through getQueryBuilder/appendAccessCheckQuery/AccessChecker.getWhereForConstrainedAccess, so the list of default directories that widens access is supplied only internally by the FUSE directory rename path and is no longer read from a Bundle.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider access checks (AccessChecker, getQueryBuilder)",
     "weakness": "Improper access control: trusted internal access-widening control carried in a Bundle extra that callers could set (CWE-284 / untrusted input used in authorization decision)",
     "root_cause": "AccessChecker.getWhereForDefaultDirectoryMatch(Bundle) read the string array list under the key INCLUDED_DEFAULT_DIRECTORIES (\"android:included-default-directories\") from the extras Bundle passed down from getQueryBuilder. For each directory it appended \"relative_path LIKE '<dir>/%'\" as an OR option to the access-check WHERE clause built by getWhereForConstrainedAccess, with the directory string concatenated into the SQL. The extra was meant to be set only internally by MediaProvider: the FUSE directory rename loop filled it with getIncludedDefaultDirectories() for the System Gallery case. MediaProvider tried to defend against outside use by removing the key from incoming extras at four sites (comment: 'should only be set inside MediaProvider'). The commit message states the extra 'can be misused by setting it in the bundle'. The material does not show which path let a caller-set key reach the access check.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java: the constant INCLUDED_DEFAULT_DIRECTORIES is deleted entirely, so the code no longer honors any such Bundle key.",
      "src/com/android/providers/media/AccessChecker.java:getWhereForConstrainedAccess: the signature changes from (callingIdentity, uriType, forWrite, Bundle extras) to (callingIdentity, uriType, forWrite, Optional<List<String>> includedDefaultDirectoriesOptional). The default directory SQL is added only when the Optional isPresent().",
      "src/com/android/providers/media/AccessChecker.java:getWhereForDefaultDirectoryMatch: now takes a List<String> instead of reading the Bundle. The LIKE clause construction is otherwise unchanged.",
      "src/com/android/providers/media/MediaProvider.java:getQueryBuilder, getQueryBuilderInternal, appendAccessCheckQuery: a new Optional<List<String>> parameter is added and passed through to getWhereForConstrainedAccess in place of `extras`. In 15/16 both branches of appendAccessCheckQuery are updated; in 14 there is one call.",
      "src/com/android/providers/media/MediaProvider.java:updateDatabaseForFuseRename (all three overloads) and deleteForFuseRename: gain the Optional parameter and forward it to getQueryBuilder.",
      "src/com/android/providers/media/MediaProvider.java: the FUSE directory rename loop no longer builds a qbExtras Bundle with the key. It passes new Bundle() plus Optional.of(getIncludedDefaultDirectories()). Every other getQueryBuilder / getWhereForConstrainedAccess call site in the diff passes Optional.empty(), including getWhereClauseForMatchableVisibleFromFilePath.",
      "src/com/android/providers/media/MediaProvider.java: the four extras/queryArgs.remove(INCLUDED_DEFAULT_DIRECTORIES) calls are removed because the key no longer exists.",
      "tests/src/com/android/providers/media/AccessCheckerTest.java: tests are updated to the new signature, and a new assertion checks that Optional.of(DCIM, Pictures, Movies) yields the three RELATIVE_PATH LIKE clauses for FILES."
     ],
     "completeness": [
      "The fix is structural. The Bundle key no longer exists and the access-widening list is now a typed parameter, so a Bundle can no longer affect the default directory match. All getQueryBuilder call sites in the diff were updated; the signature change forces every caller to be adapted.",
      "The only producer of a non-empty Optional is the FUSE directory rename loop using getIncludedDefaultDirectories(), which matches the original intent.",
      "getWhereForDefaultDirectoryMatch still builds the LIKE clause by string concatenation without escaping. It is now fed only by internal getIncludedDefaultDirectories() values, so the fix relies on that list being trusted rather than on sanitizing it.",
      "Other Bundle extras stripped at entry points (QUERY_ARG_REDACTED_URI, QUERY_ARG_RELATED_URI) keep the strip-at-entry pattern; the diff does not change their handling."
     ],
     "behavior_changes": [
      "A Bundle containing the key android:included-default-directories no longer has any effect on access checks; the key is no longer read from any Bundle.",
      "System Gallery directory renames keep their default-directory access, now passed via Optional.of(getIncludedDefaultDirectories()). The removed constant's comment describes this as DCIM, Pictures and Movies.",
      "Internal method signatures changed (getQueryBuilder, getQueryBuilderInternal, appendAccessCheckQuery, updateDatabaseForFuseRename, deleteForFuseRename, AccessChecker.getWhereForConstrainedAccess). No public API changes in the diff."
     ],
     "preconditions": [
      "The caller must be able to get a Bundle containing the INCLUDED_DEFAULT_DIRECTORIES key to a MediaProvider path where it reaches the access check. The material does not show which path that is.",
      "The targeted operation must fall in the getWhereForConstrainedAccess branch that includes the default directory match (the files branch; the test exercises FILES), and the caller must not already have full collection access."
     ],
     "per_major": {
      "14": "Adapted fix (f79b709905cf, android-security-14.0.0_r22): same change, but only one appendAccessCheckQuery -> getWhereForConstrainedAccess call site is updated, because 14 lacks the branching present in 15/16. 7 of 8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Same fix (c34a7f642548, android-security-15.0.0_r10), with the same changes as the 16 commit, including both appendAccessCheckQuery branches. 7 of 8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "Same fix (a5f1f1827339, android-security-16.0.0_r1), cherry-pick of a631dca1a8c0. 7 of 8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). 7 of 8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit, no dependency. When porting to a tree with a different set of getQueryBuilder call sites, rely on the signature change to find every caller. All must pass Optional.empty() except the FUSE directory rename loop, which passes Optional.of(getIncludedDefaultDirectories()). In trees with a single-branch appendAccessCheckQuery (as in Android 14) only one getWhereForConstrainedAccess call needs updating."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which concrete entry point or path let a caller-set INCLUDED_DEFAULT_DIRECTORIES key reach getWhereForConstrainedAccess despite the entry-point stripping.",
      "Whether the unescaped SQL concatenation in getWhereForDefaultDirectoryMatch was part of the reported impact or only the directory-based access widening.",
      "Why one of the 8 patched-region lines is not found at the latest tags of each major."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48567": {
     "cve": "CVE-2025-48567",
     "summary": "MediaProvider now NFD-normalizes a file path and strips Unicode default-ignorable code points (new FileUtils.normalizeAndFilterDefaultIgnorableCodepoints) before running the private-app-directory checks in isPrivatePackagePathNotAccessibleByCaller and the FUSE entry point isUidAllowedAccessToDataOrObbPathForFuse. The goal is that invisible characters the lower file system ignores can no longer make a path miss the Android/data, Android/obb and Android/media protections.",
     "component": "platform/packages/providers/MediaProvider, FUSE path access checks (MediaProvider.java, util/FileUtils.java)",
     "weakness": "Path equivalence / insufficient canonicalization before a regex-based access check: Unicode default-ignorable code points (e.g. zero-width space) are ignored by the lower file system but were not removed before the regex checks protecting private app directories.",
     "root_cause": "MediaProvider's checks that protect private app directories (Android/data, Android/obb, Android/media) ran on the caller-supplied path string. According to the commit message, these checks are regex-based. The commit message also states that the lower file system ignores certain characters when saving files. So a path with such characters inside a protected segment could fail to match the regexes while still referring to the private directory. Before the fix, nothing normalized or filtered these characters ahead of the checks in isPrivatePackagePathNotAccessibleByCaller or isUidAllowedAccessToDataOrObbPathForFuse.",
     "fix_analysis": [
      "src/com/android/providers/media/util/FileUtils.java: adds public static normalizeAndFilterDefaultIgnorableCodepoints(String path). It returns the input unchanged if the aconfig flag enable_path_sanitization is off (Android 16 version only) or if path is null or empty. Otherwise it applies java.text.Normalizer NFD, scans the code points with android.icu.lang.UCharacter.hasBinaryProperty(cp, UProperty.DEFAULT_IGNORABLE_CODE_POINT), and if any is found rebuilds the string without them. If none is found, it returns the NFD string.",
      "src/com/android/providers/media/MediaProvider.java: the @Keep public isUidAllowedAccessToDataOrObbPathForFuse(int uid, String path) becomes a thin wrapper. It sanitizes the path with FileUtils.normalizeAndFilterDefaultIgnorableCodepoints and delegates to a new private isUidAllowedAccessToDataOrObbPath(int uid, String path), which keeps the previous body. The diff shows only the start of that body (clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid))).",
      "src/com/android/providers/media/MediaProvider.java: isPrivatePackagePathNotAccessibleByCaller(String path) now sanitizes path first. As a result, extractPathOwnerPackageName(path), isExternalMediaDirectory(path) and the final isUidAllowedAccessToDataOrObbPath(uid, path) call all see the filtered path. That final call uses the private, non-sanitizing variant.",
      "src/com/android/providers/media/MediaProvider.java: shouldBypassFuseRestrictions switches its call from the public wrapper to the private isUidAllowedAccessToDataOrObbPath with the original filePath. This diff adds no sanitization of filePath at that site.",
      "mediaprovider_flags.aconfig (Android 16 only): adds the flag enable_path_sanitization (namespace mediaprovider, is_fixed_read_only: true, is_exported: false, bug 377888957), which gates the new sanitization.",
      "FileUtils.extractRelativePath: the only change is `final String path;` becoming `String path;`. No functional change in this diff.",
      "tests/src/com/android/providers/media/util/FileUtilsTest.java: adds testNormalizeAndFilterDefaultIgnorableCodepoints, covering a zero-width space inside 'Android' and the U+FE0F variation selector, under @EnableFlags(Flags.FLAG_ENABLE_PATH_SANITIZATION) and @SdkSuppress(minSdkVersion = S)."
     ],
     "completeness": [
      "Covered: isUidAllowedAccessToDataOrObbPathForFuse (the public @Keep entry) and isPrivatePackagePathNotAccessibleByCaller now work on an NFD-normalized path with default-ignorable code points removed before their checks run.",
      "Not covered at the call-site level: shouldBypassFuseRestrictions now calls the non-sanitizing private isUidAllowedAccessToDataOrObbPath with the raw filePath, and this diff adds no sanitization there. Whether its callers sanitize the path earlier is not shown.",
      "Sanitization happens at call sites, not inside the FileUtils path helpers. isDataOrObbPath, isDataOrObbRelativePath, isObbOrChildRelativePath, extractPathOwnerPackageName, isExternalMediaDirectory and extractRelativePath are functionally unchanged in this diff, so other direct users of those helpers are not covered by this commit.",
      "On Android 16 the whole mitigation does nothing when the fixed-read-only flag enable_path_sanitization is disabled. The aconfig entry in the diff sets no default value. The 14 and 15 backports apply the sanitization unconditionally.",
      "Only code points with the ICU DEFAULT_IGNORABLE_CODE_POINT property are removed, plus NFD normalization. No other canonicalization differences are addressed.",
      "The commit message lists the tests testIsDataOrObbRelativePathWithZwsChars_flagEnabled and _flagDisabled, which are not in this diff. Parts of the original CLs may therefore be missing from this combined cherry-pick."
     ],
     "behavior_changes": [
      "Paths checked by isPrivatePackagePathNotAccessibleByCaller and isUidAllowedAccessToDataOrObbPathForFuse are NFD-normalized and stripped of default-ignorable code points before evaluation.",
      "Normalization applies only to the string used for the access decision. The diff does not change the path used for the actual file operation.",
      "On Android 16, the new build-time (fixed read-only) flag enable_path_sanitization controls whether sanitization runs. When it is off, behavior is the same as before.",
      "Small extra cost (NFD normalization and codePoints().toArray()) on the sanitized call paths."
     ],
     "preconditions": [
      "Caller is an app performing file operations on FUSE-mounted external storage whose paths reach MediaProvider's path checks. isUidAllowedAccessToDataOrObbPathForFuse is @Keep public, i.e. intended to be called from native FUSE code (general platform knowledge). isPrivatePackagePathNotAccessibleByCaller and shouldBypassFuseRestrictions are internal MediaProvider helpers.",
      "The caller-supplied path contains Unicode default-ignorable code points inside a protected segment.",
      "Caller does not already qualify for an earlier bypass in shouldBypassFuseRestrictions (legacy storage, package manager identity).",
      "On Android 16, the mitigation only applies if enable_path_sanitization is enabled in the build."
     ],
     "per_major": {
      "14": "adapted fix (e599e8d934f2, android-security-14.0.0_r25): same MediaProvider.java and FileUtils.java changes, but normalizeAndFilterDefaultIgnorableCodepoints has no flag check (sanitization is unconditional), there is no aconfig change, and the test lacks @EnableFlags. 6/8 patched-region lines are present at android-security-14.0.0_r29.",
      "15": "adapted fix (2f45f53ce92c, android-security-15.0.0_r13): like 14, sanitization is unconditional and there is no aconfig change. The test still carries @EnableFlags(Flags.FLAG_ENABLE_PATH_SANITIZATION), but the diff adds no flag definition or import for it. 6/8 patched-region lines are present at android-security-15.0.0_r17.",
      "16": "same fix (237fb89f5325, android-security-16.0.0_r4): combined cherry-pick, with the aconfig flag enable_path_sanitization gating the sanitization. 7/8 patched-region lines are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 7/8 patched-region lines are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "For trees without the aconfig Flags library, follow the 14/15 variant: drop the Flags.enablePathSanitization() guard and the aconfig entry, and remove the @EnableFlags and Flags references from the test. The 15 backport's test references Flags.FLAG_ENABLE_PATH_SANITIZATION without the diff defining it, so check that it compiles. The helper depends on android.icu.lang.UCharacter/UProperty and java.text.Normalizer.",
      "Keep the split between the public sanitizing isUidAllowedAccessToDataOrObbPathForFuse (@Keep) and the private isUidAllowedAccessToDataOrObbPath. Check whether shouldBypassFuseRestrictions or other callers in your tree pass unsanitized paths, since this commit does not sanitize them."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the filePath passed to shouldBypassFuseRestrictions is sanitized by its callers (not shown in the material).",
      "The build-time value of enable_path_sanitization in Android 16 release builds; the aconfig snippet sets no value.",
      "Contents of the original CLs ag/33577707 and ag/33920560. The commit message names tests (testIsDataOrObbRelativePathWithZwsChars_*) that do not appear in this diff.",
      "The full body of isUidAllowedAccessToDataOrObbPath, including which regex helpers it calls.",
      "Which patched-region lines are missing on 14, 15, 16 and 17.",
      "Exactly which lower file system behavior ignores these characters; the commit message only states that it does.",
      "Why the test uses @SdkSuppress(minSdkVersion = S)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48574": {
     "cve": "CVE-2025-48574",
     "summary": "DisplayPolicy.validateAddingWindowLw now checks MANAGE_ACTIVITY_TASKS against the callingPid/callingUid passed in explicitly when a window requests PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP. Before, it called ActivityTaskManagerService.enforceTaskPermission(), which uses the Binder calling identity, and that identity has already been cleared at this point.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/DisplayPolicy)",
     "weakness": "Ineffective permission check: the permission is checked against the cleared Binder calling identity instead of the real caller",
     "root_cause": "In DisplayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid), the branch for PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP in attrs.privateFlags called ActivityTaskManagerService.enforceTaskPermission(\"DisplayPolicy\"). The commit message says this helper takes the caller from the Binder calling uid/pid, and that this identity has been cleared before validateAddingWindowLw() is called. So the check did not test the client that adds the window. The method already receives callingPid/callingUid explicitly and uses them for the nearby PRIVATE_FLAG_TRUSTED_OVERLAY check, but did not use them for this flag.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/DisplayPolicy.java:validateAddingWindowLw: replaces ActivityTaskManagerService.enforceTaskPermission(\"DisplayPolicy\") with mContext.enforcePermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS, callingPid, callingUid, \"DisplayPolicy\"). The check now uses the pid/uid passed into validateAddingWindowLw, the same way the method already handles PRIVATE_FLAG_TRUSTED_OVERLAY (INTERNAL_SYSTEM_WINDOW) and the TYPE_STATUS_BAR / TYPE_NOTIFICATION_SHADE cases.",
      "services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java:testPrivateInterceptGlobalDragDropFlagChecksPermission: the test no longer verifies verify(mWm.mAtmService).enforceTaskPermission(any()). It now verifies verify(mWm.mContext).enforcePermission(eq(MANAGE_ACTIVITY_TASKS), anyInt(), anyInt(), any())."
     ],
     "completeness": [
      "The fix covers the PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP branch of validateAddingWindowLw. The other permission checks visible in the context (TRUSTED_OVERLAY, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE) already use the explicit callingPid/callingUid. The rest of the method is not shown.",
      "The fix is one commit touching two files, with no extra hardening (for example, the private flag is not stripped). The window is rejected only through the SecurityException that enforcePermission throws.",
      "The material does not show whether ActivityTaskManagerService.enforceTaskPermission is still called from other paths that run with a cleared Binder identity. The fix does not change that helper.",
      "The new check accepts only MANAGE_ACTIVITY_TASKS. The material does not show which permissions enforceTaskPermission accepted, so any difference in accepted permissions is unknown."
     ],
     "behavior_changes": [
      "Adding a window whose LayoutParams.privateFlags include PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP now throws SecurityException (through Context.enforcePermission, message tag \"DisplayPolicy\") unless the passed-in calling pid/uid holds android.permission.MANAGE_ACTIVITY_TASKS.",
      "The unit test testPrivateInterceptGlobalDragDropFlagChecksPermission now expects a call to mWm.mContext.enforcePermission instead of mWm.mAtmService.enforceTaskPermission. Fork tests that mock mAtmService for this path need the same change."
     ],
     "preconditions": [
      "A client adds a window with PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP set in WindowManager.LayoutParams.privateFlags, which reaches DisplayPolicy.validateAddingWindowLw.",
      "The commit message says validateAddingWindowLw runs after the Binder calling identity has been cleared. validateAddingWindowLw is reached from WindowManagerService.addWindow (general platform knowledge).",
      "Before the fix, the identity the check tested was not the caller's (per the commit message)."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/base 04b124bf59a8 in android-security-14.0.0_r24. The DisplayPolicy.java and DragDropControllerTests.java hunks are identical to the 16 fix. The hunk is at line 1009 and is followed directly by 'switch (attrs.type)'. Per the facts, 5 of the 7 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: platform/frameworks/base e6e8c87c3c99 in android-security-15.0.0_r12, with identical hunks (DisplayPolicy.java at line 1060, followed by 'switch (attrs.type)'). Per the facts, 5 of the 7 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/frameworks/base 1cfd8237b5a8 in android-security-16.0.0_r3 (cherry-pick of 467b18585de4, Change-Id Ic4de10db891c79130bb6e2f4d6e519ca6e4c0f73). The code context at android-security-16.0.0_r8 shows the fixed enforcePermission call.",
      "17": "Not affected per the bulletin (not listed). At android-security-17.0.0_r1, 5 of the 7 lines of the patched region are present. The material does not say which lines."
     },
     "backport_notes": [
      "The change replaces one call inside validateAddingWindowLw with a three-line call. The same hunks appear on 14, 15 and 16 at different line offsets. On 16 the hunk is followed by a systemUiPermission block, while on 14 and 15 it is followed directly by 'switch (attrs.type)'. The test hunk verifies on mWm.mContext, so that object must be a mock or spy in the test base."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of ActivityTaskManagerService.enforceTaskPermission, and which permissions it accepts, are not in the material.",
      "The call site in WindowManagerService that clears the Binder identity before calling validateAddingWindowLw is only described in the commit message, not shown, and neither is the identity the old check actually tested.",
      "Which 5 of the 7 patched-region lines are present at each security tag, including android-security-17.0.0_r1.",
      "What a caller gains when a window with PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP is accepted is not shown. The bulletin rates it EoP / High."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48577": {
     "cve": "CVE-2025-48577",
     "summary": "KeyguardViewMediator now records which user a pending keyguard dismissal belongs to and re-validates it against the currently selected user at deferred steps (biometric auth success, hide-animation completion, the going-away runnable), hardens the exit-animation user-mismatch path, and cancels pending delayed dismiss/HIDE work on user switch, so a dismissal started for the previous user no longer completes after a user switch.",
     "component": "platform/frameworks/base, SystemUI keyguard (packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java)",
     "weakness": "Race condition / missing re-validation of user identity across asynchronous keyguard dismissal steps (TOCTOU between user switch and deferred dismiss callbacks)",
     "root_cause": "Keyguard dismissal in KeyguardViewMediator is a multi-step asynchronous chain (keyguardDonePending -> startPreHideAnimation -> hide-animation-finished runnable -> tryKeyguardDone -> handleKeyguardDone). Alongside it run a going-away runnable on mUiBgExecutor and a 500 ms delayed dismiss() that is posted for a secure user (the enclosing method is not shown in the diff). The pending state was a boolean that did not record a user (mKeyguardDonePending). The hide-animation completion callback (mHideAnimationFinishedRunnable) and tryKeyguardDone() carried no user id and did not compare against the selected user. mUpdateCallback.onBiometricAuthenticated accepted any userId. The goingAwayRunnable's background lambda did not re-check mGoingAwayRequestedForUserId. handleBeforeUserSwitching did not cancel the delayed dismiss or HIDE messages. Per the commit message, these chains could validate the prior user before the user switch completed, and a runnable scheduled after the dismiss animations could then dismiss the prior keyguard. The existing mismatch branch in handleStartKeyguardExitAnimation only called doKeyguardLocked(null) and did not force the keyguard back to showing.",
     "fix_analysis": [
      "KeyguardViewMediator.java: replace boolean mKeyguardDonePending with int mKeyguardDonePendingForUser, initialised to the new sentinel NO_KEYGUARD_DONE_PENDING (-10000). mViewMediatorCallback.keyguardDonePending stores targetUserId. readyForKeyguardDone reads the stored user, clears it and calls tryKeyguardDone(user). hideWithAnimation, resetKeyguardDonePendingLocked and dump() now use the new field.",
      "KeyguardViewMediator.java: remove the lambda mHideAnimationFinishedRunnable and add an inner class OnHideAnimationFinished(int userId) implementing Runnable. run() clears mHideAnimationRunning, then compares mUserId with mSelectedUserInteractor.getSelectedUserId(). On a mismatch it logs, calls mShadeController.get().instantCollapseShade() and resetStateLocked(), and returns without calling tryKeyguardDone. On a match it calls tryKeyguardDone(mUserId). Both startPreHideAnimation call sites (keyguardDonePending and tryKeyguardDone) now pass new OnHideAnimationFinished(userId).",
      "KeyguardViewMediator.java:tryKeyguardDone now takes int userId (passed in from keyguardDone(targetUserId) and readyForKeyguardDone) and checks the sentinel instead of the boolean.",
      "KeyguardViewMediator.java:mUpdateCallback.onBiometricAuthenticated: returns early with Log.w when userId != mSelectedUserInteractor.getSelectedUserId(), before reportSuccessfulBiometricAttempt and the rest of the handler.",
      "KeyguardViewMediator.java:handleBeforeUserSwitching: additionally calls mHandler.removeCallbacksAndMessages(mDismissToken) and mHandler.removeMessages(HIDE). The 500 ms delayed dismiss() is now posted with the new token object mDismissToken so that it can be cancelled.",
      "KeyguardViewMediator.java:goingAwayRunnable mUiBgExecutor lambda: reads the selected user again. If it differs from mGoingAwayRequestedForUserId, the lambda logs, calls mUpdateMonitor.setKeyguardGoingAway(false) and mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false), and returns before startKeyguardTransition / keyguardGoingAway.",
      "KeyguardViewMediator.java:handleStartKeyguardExitAnimation user-mismatch branch for a secure user: after doKeyguardLocked(null), when !KeyguardWmStateRefactor.isEnabled(), it calls mKeyguardInteractor.showKeyguard(), mShadeController.get().instantCollapseShade() and setShowingLocked(true, true, \"handleStartKeyguardExitAnimation - mismatch\") to force the keyguard back to showing.",
      "KeyguardViewMediatorTest.java: adds biometricAuthSuccessForCurrentUserDoesProceed, biometricAuthSuccessForDifferentUserDoesNotProceed, testDonePendingExecutesOnHideAnimationFinished and testDonePendingExecutesOnHideAnimationFinishedButTerminatesWhenUserHasChanged."
     ],
     "completeness": [
      "Covers the deferred paths visible in the diff: the biometric auth callback, the hide-animation-finished runnable, the going-away background runnable, and the delayed dismiss()/HIDE messages at user switch. It also hardens the exit-animation mismatch branch.",
      "tryKeyguardDone(userId) does not itself compare userId with the selected user; the check is only in OnHideAnimationFinished.run(). The readyForKeyguardDone -> tryKeyguardDone(user) -> handleKeyguardDone path relies on handleBeforeUserSwitching calling resetKeyguardDonePendingLocked() to clear the stored user. handleKeyguardDone is not shown in the material.",
      "On Android 16 the force-show in handleStartKeyguardExitAnimation is gated by !KeyguardWmStateRefactor.isEnabled(). This diff does not cover how that mismatch path behaves when the refactor flag is enabled.",
      "The Android 14 backport does not include the mDismissToken / removeMessages(HIDE) changes in handleBeforeUserSwitching, and its mismatch branch lacks mKeyguardInteractor.showKeyguard()."
     ],
     "behavior_changes": [
      "Biometric authentication success reported for a user other than the selected user is ignored (Log.w), including DevicePolicyManager.reportSuccessfulBiometricAttempt.",
      "When the pre-hide animation finishes after a user switch, the shade is collapsed and keyguard state is reset instead of completing the dismissal (Log.e).",
      "On a user mismatch, the going-away runnable aborts with Log.e and clears the going-away state in KeyguardUpdateMonitor and in the keyguard view controller.",
      "On user switch (handleBeforeUserSwitching), any queued delayed dismiss() and HIDE messages are dropped (15/16).",
      "In the exit-animation user-mismatch case for a secure user, the keyguard is forced to showing and the shade is collapsed (on 16 only when KeyguardWmStateRefactor is disabled).",
      "The dump output field is renamed from mKeyguardDonePending to mKeyguardDonePendingForUser. The keyguardDone, keyguardDonePending and tryKeyguardDone logs now include the user id and, on 14/15, are no longer DEBUG-gated. The Trace section in keyguardDonePending now starts after the user check."
     ],
     "preconditions": [
      "Multi-user device where a user switch happens while a keyguard dismissal is in flight for the previous user.",
      "A deferred dismissal step is queued. This can be: keyguardDonePending with its pre-hide animation (called from KeyguardSecurityContainerController.finish when a dismiss action defers keyguard done, per index search); a biometric auth success delivered through the KeyguardUpdateMonitor callback; the going-away runnable on mUiBgExecutor; or the 500 ms delayed dismiss() posted for a secure user."
     ],
     "per_major": {
      "14": "adapted fix (platform/frameworks/base 2acf4a1801c1 in android-security-14.0.0_r24). It has the same mKeyguardDonePendingForUser, OnHideAnimationFinished, onBiometricAuthenticated, goingAwayRunnable and exit-animation changes, with these differences: it uses KeyguardUpdateMonitor.getCurrentUser() instead of mSelectedUserInteractor; keyguardDonePending has the signature (boolean primaryAuth, int targetUserId); it calls ActivityTaskManager.getService().keyguardGoingAway directly; handleBeforeUserSwitching has no mDismissToken / removeMessages(HIDE); and the mismatch branch has no mKeyguardInteractor.showKeyguard() and uses a 2-argument setShowingLocked. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "adapted fix (platform/frameworks/base 3b6cc0893907 in android-security-15.0.0_r12). Same as 16, including mDismissToken and HIDE removal, except that the goingAwayRunnable calls mActivityTaskManagerService.keyguardGoingAway, and the handleStartKeyguardExitAnimation force-show is unconditional (no KeyguardWmStateRefactor gate) with a 2-argument setShowingLocked. At android-security-15.0.0_r17, 8/8 lines are present.",
      "16": "same fix (platform/frameworks/base 78760f098fab in android-security-16.0.0_r3, cherry-pick of 3395c83be381). At android-security-16.0.0_r8, 8/8 lines are present.",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 8/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit. The user-lookup helper differs by release (KeyguardUpdateMonitor.getCurrentUser() on 14, mSelectedUserInteractor.getSelectedUserId() on 15/16), and keyguardDonePending takes an extra boolean parameter on 14. Pick the per-major variant that matches the target tree. On 14, the handleBeforeUserSwitching token/HIDE cancellation is absent.",
      "The 16 hunk in handleStartKeyguardExitAnimation references KeyguardWmStateRefactor and a 3-argument setShowingLocked. On trees without these, the 15 form (unconditional, 2-argument) is the matching variant."
     ],
     "confidence": "high",
     "unknowns": [
      "The bodies of handleKeyguardDone and resetStateLocked are not shown, so it is unknown whether they do any further user validation.",
      "The diff does not show the name of the method that posts the 500 ms delayed dismiss().",
      "The material does not show why the Android 14 backport omits the handleBeforeUserSwitching changes, or whether the 15 hunk would apply to a 14-based tree.",
      "The material does not show how the exit-animation mismatch path behaves on 16 when KeyguardWmStateRefactor is enabled."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48578": {
     "cve": "CVE-2025-48578",
     "summary": "Adds the missing restoreCallingIdentity/restoreLocalCallingIdentity calls on two exception paths in MediaProvider (unsupported authority in getResultForGetMediaUri, NumberFormatException from ContentUris.parseId in updateInternal) so the cleared calling identity is restored when the method throws.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (updateInternal, getResultForGetMediaUri)",
     "weakness": "Improper cleanup on exception path: calling identity cleared but not restored when an exception is thrown (identity/privilege state leak)",
     "root_cause": "In MediaProvider.java, two methods clear the calling identity and then run code that can throw before the identity is restored. In getResultForGetMediaUri, clearCallingIdentity() is called, then the authority check throws IllegalArgumentException(\"Provider for this Uri is not supported.\") for a documentUri whose authority is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY. restoreCallingIdentity(token) is otherwise called only in the finally block of the later try, so it was skipped. In updateInternal, clearLocalCallingIdentity() is called and then MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)) is evaluated outside any try. If parsing the uri throws NumberFormatException, the calling identity is not restored (per the commit message).",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:getResultForGetMediaUri: inserts restoreCallingIdentity(token) immediately before the existing throw new IllegalArgumentException(\"Provider for this Uri is not supported.\") in the unsupported-authority branch, so the identity cleared by clearCallingIdentity() is restored on that early exit.",
      "src/com/android/providers/media/MediaProvider.java:updateInternal: replaces the direct initialization of genericUri with a try block around MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)). A catch (NumberFormatException e) calls restoreLocalCallingIdentity(token) and rethrows the same exception. The exception type seen by the caller is unchanged; only the identity restore is added.",
      "Single commit, two hunks; no new helper or dependency introduced."
     ],
     "completeness": [
      "Covers exactly the two throw sites named in the commit message: the unsupported-authority IllegalArgumentException in getResultForGetMediaUri and the NumberFormatException from ContentUris.parseId in updateInternal.",
      "In updateInternal only NumberFormatException is caught; any other unchecked exception from that expression would still skip the restore (none is evident from the shown code).",
      "In getResultForGetMediaUri, documentUri.getAuthority() is evaluated after clearCallingIdentity() and then dereferenced with authority.equals(...) before the try/finally (android-security-16.0.0_r8 lines 7735-7741). A null authority would throw there before any restore. The fix does not address this.",
      "The Android 14 commit (12fbdbc6beda) contains only the updateInternal hunk; the getResultForGetMediaUri hunk is absent. The material does not show whether that code path exists or differs on 14."
     ],
     "behavior_changes": [
      "No change in exception types or results returned to callers; IllegalArgumentException and NumberFormatException are still thrown from the same places.",
      "On those exception paths the calling identity (clearCallingIdentity/restoreCallingIdentity in getResultForGetMediaUri, clearLocalCallingIdentity/restoreLocalCallingIdentity in updateInternal) is now restored before the exception propagates."
     ],
     "preconditions": [
      "updateInternal path: execution reaches the block after clearLocalCallingIdentity() with a uri for which ContentUris.parseId throws NumberFormatException.",
      "getResultForGetMediaUri path: extras carry MediaStore.EXTRA_URI set to a Uri whose authority is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY. getContext().enforceCallingUriPermission(documentUri, FLAG_GRANT_READ_URI_PERMISSION, TAG) must pass first, because it runs before clearCallingIdentity()."
     ],
     "per_major": {
      "14": "adapted fix: commit 12fbdbc6beda in android-security-14.0.0_r25 applies only the updateInternal NumberFormatException hunk (6/6 lines present at android-security-14.0.0_r29); the getResultForGetMediaUri restoreCallingIdentity hunk is not in that commit.",
      "15": "same fix: commit 2a8f3f27275c in android-security-15.0.0_r13 with both hunks at different line offsets (around 7097 and 8225); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 7ea0a039ec32 in android-security-16.0.0_r4, both hunks; 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (8/8 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change is self-contained in MediaProvider.java and applies at shifted line offsets across 14/15/16. The two sites use different identity mechanisms: getResultForGetMediaUri uses clearCallingIdentity()/restoreCallingIdentity, while updateInternal uses clearLocalCallingIdentity()/restoreLocalCallingIdentity. Pair each restore with its matching clear.",
      "For Android 14 based trees, Google's commit only patched updateInternal. Check whether getResultForGetMediaUri on that tree has the same unsupported-authority throw after clearCallingIdentity(); if it does, consider adding restoreCallingIdentity(token) before the throw."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the Android 14 commit omits the getResultForGetMediaUri hunk (whether that code differs on 14 or the hunk was dropped) is not shown.",
      "The exact downstream consequence of the leaked identity (what later operations run with the cleared identity) is not shown beyond the bulletin's EoP classification.",
      "Which public entry points (for example update() or call() and the method name) lead to updateInternal and getResultForGetMediaUri, whether ordinary apps can reach them, and the exact conditions guarding the patched block in updateInternal are not shown.",
      "How updateInternal restores the token on its normal path after the patched block is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48579": {
     "cve": "CVE-2025-48579",
     "summary": "Restores the cleared calling identity on two exception paths in MediaProvider, so the binder thread does not continue with a cleared identity after the exception: a NumberFormatException from ContentUris.parseId in updateInternal, and an IllegalArgumentException for an unsupported document URI authority in getResultForGetMediaUri.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java (ContentProvider update and call() paths)",
     "weakness": "Improper cleanup on a thrown exception: the calling identity is cleared and not restored on an error path (identity leak; the bulletin rates it EoP)",
     "root_cause": "In MediaProvider#updateInternal, clearLocalCallingIdentity() is called and then ContentUris.parseId(uri) is evaluated outside any try/finally. If the URI's id segment is not numeric, parseId throws NumberFormatException and restoreLocalCallingIdentity(token) never runs. In MediaProvider#getResultForGetMediaUri, clearCallingIdentity() is called and then the authority of the caller-supplied documentUri is checked. If it is neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, an IllegalArgumentException is thrown before the try/finally that would call restoreCallingIdentity(token). In both cases the cleared identity is not restored when the exception propagates.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:getResultForGetMediaUri: adds restoreCallingIdentity(token) immediately before the throw of IllegalArgumentException(\"Provider for this Uri is not supported.\") in the unsupported-authority branch. That branch sits between clearCallingIdentity() and the existing try/finally.",
      "src/com/android/providers/media/MediaProvider.java:updateInternal: wraps MediaStore.Files.getContentUri(volumeName, ContentUris.parseId(uri)) in a try/catch (NumberFormatException e) that calls restoreLocalCallingIdentity(token) and rethrows. genericUri becomes a final declared without an initializer and assigned inside the try. The following try-with-resources block that uses genericUri is unchanged.",
      "Single commit touching one file. It adds no new helper and does not change the success path."
     ],
     "completeness": [
      "Covers exactly the two reported paths: the NumberFormatException from ContentUris.parseId in updateInternal, and the unsupported-authority IllegalArgumentException in getResultForGetMediaUri.",
      "The fix is targeted (restore before throw, plus a narrow catch). It does not move the identity-clearing calls into a try/finally, so it covers only the specific exception type and branch it handles.",
      "In getResultForGetMediaUri, authority.equals(...) runs on the result of documentUri.getAuthority() after clearCallingIdentity(). A null authority would raise NullPointerException in this same unprotected segment (see potential_variants). Whether such a URI gets past the preceding enforceCallingUriPermission check is not shown.",
      "The Android 14 commit contains only the updateInternal hunk, not the getResultForGetMediaUri hunk."
     ],
     "behavior_changes": [
      "No change to returned results or exception types: NumberFormatException and IllegalArgumentException still propagate to the caller as before.",
      "After a failing update()/call() request, the calling identity is now restored instead of staying cleared. In updateInternal this is the LocalCallingIdentity; in getResultForGetMediaUri it is the CallingIdentity."
     ],
     "preconditions": [
      "updateInternal path: a caller able to invoke MediaProvider update() with a content URI that reaches the clearLocalCallingIdentity() block and whose id path segment is non-numeric, so ContentUris.parseId throws NumberFormatException.",
      "getResultForGetMediaUri path: a caller able to invoke the corresponding MediaStore call() method with EXTRA_URI set to a document URI. The caller must hold read URI permission on it (enforceCallingUriPermission is checked first), and its authority must be neither MediaDocumentsProvider.AUTHORITY nor DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY.",
      "MediaProvider is reachable from any app via the MediaStore content provider (general platform knowledge)."
     ],
     "per_major": {
      "14": "adapted fix: commit 12fbdbc6beda in android-security-14.0.0_r25 contains only the updateInternal NumberFormatException hunk; the getResultForGetMediaUri restoreCallingIdentity hunk is not in that commit. Patched region (6/6 lines) present at android-security-14.0.0_r29.",
      "15": "same fix: commit 2a8f3f27275c in android-security-15.0.0_r13, both hunks, at different line offsets (around 7097 and 8225). Patched region (8/8 lines) present at android-security-15.0.0_r17.",
      "16": "same fix: commit 7ea0a039ec32 in android-security-16.0.0_r4 (Change-Id Ia295e56530a44c437db3a159e97c12d1a239b547), both hunks. Patched region (8/8 lines) present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Both hunks are self-contained and depend only on the existing token variables (CallingIdentity token in getResultForGetMediaUri, LocalCallingIdentity token in updateInternal). Only line offsets need adjusting. When backporting to a 14-based tree, check whether getResultForGetMediaUri there has the same unprotected authority check: the Android 14 commit omitted that hunk and the material does not show why."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the Android 14 commit omits the getResultForGetMediaUri hunk (a code difference on 14, or a separate change) is not shown.",
      "Beyond the bulletin's EoP/High rating, the material does not describe what a later request on the same binder thread can do with the leaked cleared identity.",
      "The exact MediaStore call() method name that dispatches to getResultForGetMediaUri is not shown in the provided context.",
      "Whether other exceptions in updateInternal after clearLocalCallingIdentity() are covered by a later restore is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48582": {
     "cve": "CVE-2025-48582",
     "summary": "MediaProvider now records the Binder calling uid of the app that created a bulk create request in the PermissionActivity intent extras, and PermissionActivity uses that uid instead of Activity.getCallingPackage() to decide whether a consent dialog is needed and to whom URI permissions are granted.",
     "component": "platform/packages/providers/MediaProvider, bulk create-request consent flow (MediaProvider create-request PendingIntent and PermissionActivity)",
     "weakness": "Incorrect identity used for an authorization decision (permission check and URI grant keyed on the activity caller rather than the original requester)",
     "root_cause": "MediaProvider builds an intent targeting PermissionActivity for MediaStore create requests. PermissionActivity identified the requesting app via Activity.getCallingPackage() (in resolveCallingAppInfo, both shouldShowActionDialog calls, grantUriPermission and metrics). getCallingPackage() identifies the caller of the activity, which is not necessarily the uid that called MediaProvider to create the request (general platform knowledge). The decision to skip the user consent dialog (shouldShowActionDialog) and the target of grantUriPermission on positive action were therefore computed against that caller rather than against the package that originally requested access. The requester's uid was not captured when the request was created.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java (create-request PendingIntent builder, at the `new Intent(method, null, context, PermissionActivity.class)` construction): adds `extras.putInt(EXTRA_CALLING_PACKAGE_UID, getCallingUidOrSelf())` before `intent.putExtras(extras)`, so the intent carries the Binder uid of the app calling MediaProvider. The Android 14 diff context shows this PendingIntent is created with FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, so the app holding it cannot change the extra.",
      "src/com/android/providers/media/PermissionActivity.java:resolveCallingAppInfo: if the intent has extras, reads EXTRA_CALLING_PACKAGE_UID, resolves the package name via `getPackageManager().getPackagesForUid(callingUid)[0]` (Objects.requireNonNull on the array) and the user via `UserHandle.getUserHandleForUid(callingUid)`, then returns `getApplicationInfoAsUser(callingPackage, 0, userHandle)` instead of `getApplicationInfo(callingPackage, 0)`. getCallingPackage() and getUser() are used only as fallbacks when extras is null.",
      "src/com/android/providers/media/PermissionActivity.java (both shouldShowActionDialog call sites, the media-permissions variant and the simple variant): passes `appInfo.packageName` instead of `getCallingPackage()`, so the explicit-consent decision is made for the stored requester.",
      "src/com/android/providers/media/PermissionActivity.java (user-allowed AsyncTask doInBackground): `grantUriPermission(appInfo.packageName, uri, FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION)` for CREATE_WRITE_REQUEST_CALL now targets the requester package; Metrics.logPermissionGranted also uses appInfo.packageName.",
      "src/com/android/providers/media/PermissionActivity.java (user-declined AsyncTask doInBackground): Metrics.logPermissionDenied uses appInfo.packageName (telemetry only).",
      "Android 14 only: apex/framework/java/android/provider/MediaStore.java adds the hidden constant `EXTRA_CALLING_PACKAGE_UID = \"calling_package_uid\"`. The 15 and 16 diffs import it without adding it, so it already existed there."
     ],
     "completeness": [
      "All getCallingPackage() uses visible in the diff hunks (resolveCallingAppInfo, both shouldShowActionDialog calls, grantUriPermission, two Metrics calls) are switched to the stored identity. The material does not show whether PermissionActivity uses getCallingPackage() anywhere else.",
      "The stored uid can only be trusted because the PendingIntent is immutable. FLAG_IMMUTABLE is visible in the Android 14 context; on 16 the PendingIntent flags are outside the shown hunk.",
      "resolveCallingAppInfo takes `getPackagesForUid(callingUid)[0]`: for a shared uid, the first package name is used for grantUriPermission and the dialog check, and it may not be the specific package that made the request (the uid is the same). If extras exist but the key is absent, getInt returns 0 (general platform knowledge) and the lookup proceeds for uid 0 instead of falling back to getCallingPackage(). requireNonNull throws if no package maps to the uid.",
      "The fallback to getCallingPackage()/getUser() is kept for intents without extras; whether that path can be reached in practice is not shown."
     ],
     "behavior_changes": [
      "Create-request PendingIntents carry a new int extra `calling_package_uid`.",
      "PermissionActivity resolves ApplicationInfo with getApplicationInfoAsUser for the requester's user, so resolution follows the requester uid's user.",
      "If the stored uid has no packages, Objects.requireNonNull throws a NullPointerException in resolveCallingAppInfo. Before the fix, an empty calling package led to a NameNotFoundException.",
      "The consent dialog decision and URI grants are now tied to the app that made the MediaStore create request rather than to the activity caller; metrics are attributed to the same package."
     ],
     "preconditions": [
      "An app makes a MediaStore bulk create request (CREATE_WRITE_REQUEST_CALL is the case shown in the diff) and receives the PendingIntent for PermissionActivity.",
      "The PendingIntent is launched in a way that makes Activity.getCallingPackage() in PermissionActivity differ from the original requester (the material does not describe the exact launch scenario).",
      "The bulletin rates it High severity, type EoP."
     ],
     "per_major": {
      "14": "adapted fix: commit 45db544a1f1a in android-security-14.0.0_r25; the same MediaProvider.java and PermissionActivity.java changes, plus the hidden MediaStore.EXTRA_CALLING_PACKAGE_UID constant added in apex/framework/java/android/provider/MediaStore.java; 8/8 patched lines present at android-security-14.0.0_r29",
      "15": "same fix: commit 6b652db8a6d3 in android-security-15.0.0_r13; the same two-file change (constant already present); 8/8 lines present at android-security-15.0.0_r17",
      "16": "same fix: commit 1b7d4ce446d1 in android-security-16.0.0_r4; two-file change; 8/8 lines present at android-security-16.0.0_r8",
      "17": "not affected (not listed by the bulletin); patched region (8/8 lines) present at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single commit. On branches older than 15, check that MediaStore.EXTRA_CALLING_PACKAGE_UID exists in apex/framework/java/android/provider/MediaStore.java and add it as the 14 patch does. MediaProvider ships as a mainline module (APEX), so the framework constant and the provider code must move together (general platform knowledge).",
      "Make sure the create-request PendingIntent in MediaProvider is created with FLAG_IMMUTABLE on the target branch; the fix depends on the holder not being able to rewrite the uid extra."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact scenario in which getCallingPackage() differs from the requester is not described in the material.",
      "Whether other create request types (e.g. trash, delete, favorite) go through the same path; only CREATE_WRITE_REQUEST_CALL is shown.",
      "Whether PermissionActivity uses getCallingPackage() outside the shown hunks.",
      "The names of the MediaProvider method that builds the PendingIntent and of the PermissionActivity methods containing the changed call sites are not shown in the hunk headers.",
      "The PendingIntent flags on Android 15/16 (only visible in the 14 diff context)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48585": {
     "cve": "CVE-2025-48585",
     "summary": "ProfilingService now verifies, via PackageManager.getPackagesForUid, that the package name supplied by the client to addProfilingTriggers, removeProfilingTriggers and clearProfilingTriggers actually belongs to Binder.getCallingUid(), throwing SecurityException otherwise.",
     "component": "platform/packages/modules/Profiling, ProfilingService (system-triggered profiling triggers, IProfilingService binder)",
     "weakness": "Missing validation of caller-supplied identity (package name trusted without checking it against the calling UID)",
     "root_cause": "In service/java/com/android/os/profiling/ProfilingService.java, addProfilingTriggers(List<ProfilingTriggerValueParcel>, String packageName), removeProfilingTriggers(int[], String packageName) and clearProfilingTriggers(String packageName) take the package name as a plain binder argument. The manager side (ProfilingManager in framework/java/android/os/ProfilingManager.java, per index search) passes mContext.getPackageName(). The service never checked that this string belonged to the calling UID. removeProfilingTriggers and clearProfilingTriggers use it directly, together with Binder.getCallingUid(), as a key into mAppTriggers, and addProfilingTriggers processes triggers for it. A binder client could therefore supply an arbitrary, empty or null package name and have trigger state handled under that name. The bulletin classifies the result as a High severity DoS. The material does not show the exact DoS mechanism.",
     "fix_analysis": [
      "service/java/com/android/os/profiling/ProfilingService.java: adds a new private helper, enforceCallerMatchesPackageName(String packageName). It throws SecurityException in three cases: (1) the name is null or empty; (2) mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid()) returns null or an empty array; (3) packageName does not equal any of the returned package names.",
      "ProfilingService.addProfilingTriggers: calls enforceCallerMatchesPackageName(packageName) as its first statement, before reading Binder.getCallingUid() and iterating over the supplied triggers.",
      "ProfilingService.removeProfilingTriggers: calls enforceCallerMatchesPackageName(packageName) before mAppTriggers.get(packageName, Binder.getCallingUid()).",
      "ProfilingService.clearProfilingTriggers: calls enforceCallerMatchesPackageName(packageName) before mAppTriggers.remove(packageName, Binder.getCallingUid()).",
      "Single commit (1215317dfc1d) adding 39 lines, with no other files touched."
     ],
     "completeness": [
      "Covers the three trigger-management entry points that take a caller-supplied package name: addProfilingTriggers, removeProfilingTriggers and clearProfilingTriggers.",
      "The check accepts any package returned by getPackagesForUid for the calling UID, so validation works at UID granularity. This is consistent with mAppTriggers being keyed by both package name and calling UID.",
      "The commit message calls for an audit of IProfilingService for other cases of callers supplying incorrect information. The diff changes only these three methods, and the material does not show whether other IProfilingService methods take caller-supplied identity data.",
      "The fix does not touch server-side trigger delivery (ActivityManagerService.sendProfilingTrigger -> ProfilingServiceHelper.onProfilingTriggerOccurred(uid, packageName, triggerType), per index search). That path starts in ActivityManagerService, not in an app caller of IProfilingService."
     ],
     "behavior_changes": [
      "addProfilingTriggers, removeProfilingTriggers and clearProfilingTriggers now throw SecurityException when the package name is null, empty, or not associated with the calling UID.",
      "Calls from a UID for which PackageManager returns no packages are now rejected with SecurityException, even if the package name is otherwise well-formed.",
      "Legitimate callers going through ProfilingManager, which passes mContext.getPackageName(), should be unaffected as long as that package name belongs to the calling UID."
     ],
     "preconditions": [
      "The caller can reach the IProfilingService binder interface, normally through ProfilingManager.addProfilingTriggers, removeProfilingTriggersByType or clearProfilingTriggers in framework/java/android/os/ProfilingManager.java (per index search).",
      "The ProfilingManager APIs are gated by Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW (@FlaggedApi), per index search. The material does not show whether the service-side methods are also flag-gated.",
      "ProfilingService must be running and obtainable (on the manager path, getOrCreateIProfilingServiceLocked returns non-null)."
     ],
     "per_major": {
      "14": "not affected per the bulletin (not listed). packages/modules/Profiling is not present in android-security-14.0.0_r29.",
      "15": "not affected per the bulletin (not listed). At android-security-15.0.0_r17, 5/8 lines of the patched region are present. The material does not show which of the trigger methods exist there or whether any change was applied.",
      "16": "same fix: commit 1215317dfc1d in android-security-16.0.0_r4, a cherry-pick of internal changes (Cherrypick-From aef103928d0380e0a4bb24e13768a844e9698413, Change-Id I8a9a663776c7d4ea8bbb6ca789d805256625674c). 8/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed). At android-security-17.0.0_r1, 6/8 lines of the patched region are present. The material does not show how, or whether, the fix landed there."
     },
     "backport_notes": [
      "Self-contained single-file change. It needs only mContext (already a field of ProfilingService) and Binder.getCallingUid(). Apply it to any branch that contains the addProfilingTriggers, removeProfilingTriggers and clearProfilingTriggers methods. Profiling is a mainline module, so updated builds may ship through module updates rather than the platform tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete DoS consequence of a spoofed, null or empty package name (what breaks downstream in trigger storage or delivery) is not shown in the material.",
      "How addProfilingTriggers stores triggers (the loop body) is not shown in the diff.",
      "Whether other IProfilingService methods accept caller-supplied identity data, and whether the audit mentioned in the commit message led to further changes.",
      "How the fix reached Android 17 (6/8 lines present), and what the partial region on Android 15 (5/8 lines) contains."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48587": {
     "cve": "CVE-2025-48587",
     "summary": "Adds server-side validation of the trigger type in ProfilingService.addTrigger so that triggers with an unsupported type are rejected (IllegalArgumentException) instead of being stored, and makes the persisted-trigger load path skip such triggers.",
     "component": "platform/packages/modules/Profiling, ProfilingService (system service side of the Profiling module)",
     "weakness": "Missing input validation on the service side (trust in client-side validation only)",
     "root_cause": "Per the commit message, profiling triggers were only validated on the manager (client) side. ProfilingService.addTrigger(ProfilingTriggerData, boolean) inserted the trigger into mAppTriggers (a ProcessMap<SparseArray<ProfilingTriggerData>>) without checking that trigger.getTriggerType() is a supported type. As a result, a trigger that did not pass through manager-side validation, or a persisted trigger with an unsupported type, could be placed into the service's in-memory trigger store. The bulletin classifies the result as a High severity DoS. The material does not show the exact failure that an invalid stored trigger causes.",
     "fix_analysis": [
      "service/java/com/android/os/profiling/ProfilingService.java:addTrigger(ProfilingTriggerData, boolean): after the existing early-return check, adds a check `ProfilingTrigger.isValidRequestTriggerType(trigger.getTriggerType())`. If the check fails, it logs a warning and throws IllegalArgumentException(\"Trigger type is not supported\") before the trigger is looked up in or inserted into mAppTriggers.",
      "service/java/com/android/os/profiling/ProfilingService.java: the loop that fills mAppTriggers from the persisted ProfilingTriggersWrapper (addTrigger(new ProfilingTriggerData(triggerProto), false) for each entry) is wrapped in a try/catch for IllegalArgumentException. A persisted trigger with an invalid type is ignored (logged only when DEBUG), loading continues, and mAppTriggersLoaded is still set to true.",
      "service/java/com/android/os/profiling/ProfilingService.java: the comment on the mAppTriggers field now states that additions must only go through addTrigger(ProfilingTriggerData, boolean), which performs the validation."
     ],
     "completeness": [
      "The validation applies to every caller of addTrigger. The load-from-storage path (persist=false) now drops invalid entries. Other callers, which the diff does not show, now receive the exception.",
      "Only the trigger type is validated (ProfilingTrigger.isValidRequestTriggerType). This change does not validate other ProfilingTriggerData fields.",
      "The guarantee depends on every writer to mAppTriggers using addTrigger, as the new field comment states. The material does not show whether any other code path writes to mAppTriggers directly.",
      "The diff does not show the Binder entry point that leads to addTrigger, so it is not visible whether that entry point performs any additional validation."
     ],
     "behavior_changes": [
      "ProfilingService.addTrigger now throws IllegalArgumentException for an unsupported trigger type instead of storing the trigger.",
      "Triggers persisted to storage with an unsupported type are skipped on load (a warning is logged only if DEBUG) rather than being placed into mAppTriggers.",
      "A new Log.w(TAG, \"Attempted to add invalid profiling trigger.\") is emitted on rejection."
     ],
     "preconditions": [
      "A trigger with a type not accepted by ProfilingTrigger.isValidRequestTriggerType must reach ProfilingService.addTrigger. Two routes are implied: a path that skips the manager-side validation mentioned in the commit message (presumably the IProfilingService interface; the exact entry point is not shown), or a previously persisted ProfilingTriggersWrapper on disk.",
      "The Profiling service with the trigger code (mAppTriggers, addTrigger) must be present. Per the per-major facts, most of the patched region is present in 16 and 17 but only 2/8 lines in 15."
     ],
     "per_major": {
      "14": "not affected per the bulletin; the Profiling project is not present in android-security-14.0.0_r29.",
      "15": "not affected per the bulletin; at android-security-15.0.0_r17 only 2/8 lines of the patched region exist, which suggests most of the trigger-handling code this fix targets is absent in 15.",
      "16": "same fix: ceaaf1330d3a cherry-picked into android-security-16.0.0_r4 (Change-Id I422171ef7cb8519f5c138f1b39ae9a008a0df7ae); at android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "not affected per the bulletin; at android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-commit fix confined to ProfilingService.java. It calls ProfilingTrigger.isValidRequestTriggerType, which the diff does not add, so that method must already exist in the target tree."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact failure caused by an invalid trigger type in mAppTriggers (what causes the DoS) is not shown in the material.",
      "The IProfilingService Binder entry point that leads to addTrigger, and the checks it already performs, are not shown.",
      "Which of the 8 patched lines is missing at android-security-16.0.0_r8 and android-security-17.0.0_r1 is not shown.",
      "The implementation of ProfilingTrigger.isValidRequestTriggerType, the set of accepted types, and where that class lives are not shown.",
      "Whether the fix is also delivered through a module update is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48602": {
     "cve": "CVE-2025-48602",
     "summary": "On user switch, KeyguardViewMediator now cancels any in-flight keyguard exit animation: it drops pending START_KEYGUARD_EXIT_ANIM messages and sets mIsKeyguardExitAnimationCanceled. When the deferred exit-animation completion runnable finds the cancel flag set, it restores the keyguard with finishSurfaceBehindRemoteAnimation(true) and a forced setShowingLocked(true) instead of finishing the exit.",
     "component": "platform/frameworks/base, SystemUI Keyguard (KeyguardViewMediator)",
     "weakness": "Race condition / improper state handling between keyguard exit animation completion and user switch",
     "root_cause": "In KeyguardViewMediator.exitKeyguardAndFinishSurfaceBehindRemoteAnimation, the work that actually unlocks (onKeyguardExitFinished) is deferred via DejankUtils.postAfterTraversal. On Android 16 the mIsKeyguardExitAnimationCanceled check ran before the runnable was posted. On Android 14 it sat at the top of the runnable with a bare return. The Android 15 diff shows no such check in the runnable. Also, handleBeforeUserSwitching (the BEFORE_USER_SWITCHING handler) removed DISMISS/HIDE messages but neither removed a pending START_KEYGUARD_EXIT_ANIM message nor set mIsKeyguardExitAnimationCanceled. So if a user switch arrived while an exit animation was running (in the added test, from an insecure user to a secure user), the deferred runnable could run after the switch and hide the keyguard. If the cancel was noticed, the surface-behind animation was left unfinished and the showing state was not re-asserted.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java:handleBeforeUserSwitching (Android 15/16): adds mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM) next to the existing DISMISS/HIDE removals, so a queued exit-animation start is dropped. It also sets mIsKeyguardExitAnimationCanceled = true before notifyTrustedChangedLocked/resetKeyguardDonePendingLocked, so an exit animation that is already running is marked canceled.",
      "KeyguardViewMediator.java:exitKeyguardAndFinishSurfaceBehindRemoteAnimation: on Android 16 the check of mIsKeyguardExitAnimationCanceled made before posting is removed. A check is now placed inside the postAfterTraversal runnable, after the existing !mPM.isInteractive() && !mPendingLock early return, so the flag is evaluated when the deferred work runs rather than when it was scheduled. On 15 this in-runnable check is newly added. On 14 the existing top-of-runnable check is moved.",
      "KeyguardViewMediator.java:exitKeyguardAndFinishSurfaceBehindRemoteAnimation: when the flag is set, the runnable no longer just returns. It calls finishSurfaceBehindRemoteAnimation(true /* showKeyguard */) and setShowingLocked(true /* showing */, true /* force */, \"exitKeyguardAndFinishSurfaceBehindRemoteAnimation - canceled\"), then returns without calling onKeyguardExitFinished.",
      "KeyguardViewMediator.java: adds a @VisibleForTesting postAfterTraversal(Runnable) wrapper around DejankUtils.postAfterTraversal so tests can capture the deferred runnable. KeyguardViewMediatorTest adds testGoingAwayFollowedByBeforeUserSwitchWithDelayedExitAnimationDoesNotHideKeyguard. After the race, it verifies notifyFinishedKeyguardExitAnimation(true), that ATMS reports the lock screen showing, and isShowingAndNotOccluded()."
     ],
     "completeness": [
      "Covers the BEFORE_USER_SWITCHING path (handleBeforeUserSwitching) and the deferred completion runnable of exitKeyguardAndFinishSurfaceBehindRemoteAnimation. Both a queued START_KEYGUARD_EXIT_ANIM message and an animation that is already running are handled.",
      "handleUserSwitching (USER_SWITCHING) and the USER_SWITCH_COMPLETE handler are not modified.",
      "The cancel check sits after the !mPM.isInteractive() && !mPendingLock early return, so in the non-interactive case the existing branch handles the exit rather than the new restore branch.",
      "The material does not show where mIsKeyguardExitAnimationCanceled is reset to false.",
      "On Android 14 the backport adds removeMessages(START_KEYGUARD_EXIT_ANIM) and the in-runnable restore branch. The diff shown does not set mIsKeyguardExitAnimationCanceled = true in the user-switch code, and the material does not show whether another path sets it on 14."
     ],
     "behavior_changes": [
      "On every user switch (handleBeforeUserSwitching), any queued START_KEYGUARD_EXIT_ANIM message is dropped and mIsKeyguardExitAnimationCanceled is set to true (15/16).",
      "When the exit-animation completion runnable finds the animation canceled, it now calls finishSurfaceBehindRemoteAnimation(true) and forces setShowingLocked(true, force=true), re-showing the keyguard instead of returning silently. On 16 it passes the reason string \"exitKeyguardAndFinishSurfaceBehindRemoteAnimation - canceled\".",
      "On 16, blockPanelExpansionFromCurrentTouch() and InteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION) now run even when the animation was already canceled, because the early return before posting was removed.",
      "New overridable method KeyguardViewMediator.postAfterTraversal(Runnable). It is a test seam only; by default it delegates to DejankUtils.postAfterTraversal."
     ],
     "preconditions": [
      "The keyguard is showing and WindowManager has started an exit (going-away) animation via startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, ...), with a keyguardDonePending/readyForKeyguardDone request in flight (per the added test).",
      "A user switch reaches KeyguardViewMediator (UserTracker.Callback.onBeforeUserSwitching -> BEFORE_USER_SWITCHING -> handleBeforeUserSwitching) before the postAfterTraversal runnable of exitKeyguardAndFinishSurfaceBehindRemoteAnimation runs. The test models a switch from an insecure user to a secure user.",
      "A multi-user device with at least one secure user to switch to (from the test scenario)."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base 1c3c01a414a0 in android-security-14.0.0_r24. In the user-switch code at line ~2431 it adds only removeMessages(START_KEYGUARD_EXIT_ANIM); the diff has no explicit mIsKeyguardExitAnimationCanceled = true. The existing in-runnable cancel check (top of the runnable, bare return) is moved after the interactive check and given the restore actions, using setShowingLocked(true, true) and onKeyguardExitFinished() without reason strings. The test also adjusts testRegisterDumpable. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "adapted fix: platform/frameworks/base 67769a49e360 in android-security-15.0.0_r12. Same handleBeforeUserSwitching changes and postAfterTraversal seam as 16. The in-runnable cancel check with the restore branch is added; no earlier check is removed. Uses setShowingLocked(true, true) and onKeyguardExitFinished() without reason-string arguments. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c69e16c3efda in android-security-16.0.0_r3 (cherry-pick of f17b66e73122, Change-Id Ib262129422e6198f019482547a6b129cc60f0865). 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; the patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit, two files. To adapt it, find the user-switch code (handleBeforeUserSwitching on 15/16; the block around line ~2431 on 14) and check the setShowingLocked/onKeyguardExitFinished call forms: the 16 diff passes reason strings, the 14/15 diffs do not. The 14 diff does not set mIsKeyguardExitAnimationCanceled on user switch, so check separately whether your tree sets it.",
      "The test needs the postAfterTraversal(Runnable) seam to be overridable in the anonymous subclass in KeyguardViewMediatorTest. The constructor argument list differs per release and must match your tree."
     ],
     "confidence": "high",
     "unknowns": [
      "Where mIsKeyguardExitAnimationCanceled is reset to false, and whether Android 14 sets it on user switch via another path.",
      "Which method encloses the Android 14 user-switch hunk.",
      "Whether the handleUserSwitching / USER_SWITCH_COMPLETE paths needed any companion change (none shown).",
      "The exact user-visible trigger sequence beyond what the added test models."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48609": {
     "cve": "CVE-2025-48609",
     "summary": "MmsProvider.deleteDataRows now checks that the canonical path stored in a part/drm row's _data column lies under the provider's \"parts\" directory before deleting the file. It does this through a new isFilePathCanonical helper, which openFile now uses as well.",
     "component": "platform/packages/providers/TelephonyProvider, MmsProvider (MMS part and DRM table deletion)",
     "weakness": "Missing path validation before file deletion (externally controlled file path used in File.delete)",
     "root_cause": "In src/com/android/providers/telephony/MmsProvider.java, deleteDataRows is reached through deleteParts and deleteTempDrmData, which are called from delete() and deleteMessages(). It read the _data column of each matching part/drm row and called new File(path).delete() for any non-null path. openFile already restricted _data paths to context.getDir(PARTS_DIR_NAME, 0). The delete path had no such check and trusted whatever path was stored in the database, so a row whose _data pointed outside the parts directory made the provider delete that file. The commit message says the change was added 'to err on the side of caution' and 'may be redundant given the recent sql injection protection changes'.",
     "fix_analysis": [
      "MmsProvider.java:delete(): captures Context context = getContext() and passes it into deleteMessages, deleteParts and deleteTempDrmData.",
      "MmsProvider.java:deleteMessages(): passes context to the per-message deleteParts call for Part.MSG_ID rows.",
      "MmsProvider.java:deleteParts()/deleteTempDrmData()/deleteDataRows(): each signature gains a Context parameter.",
      "MmsProvider.java:deleteDataRows(): for each row, if _data is null it continues. Otherwise it builds File file = new File(path) and calls isFilePathCanonical(context, file). If that returns false, it logs 'deleteFile: path ... does not start with ...' and continues without deleting. Only after that does it call file.delete(). All of this is still inside the existing per-row try/catch Throwable, so an IOException from getCanonicalPath in the log statement is caught and logged.",
      "MmsProvider.java:isFilePathCanonical(Context, File) (new, @VisibleForTesting, public static): returns file.getCanonicalPath().startsWith(context.getDir(PARTS_DIR_NAME, 0).getCanonicalPath()). On IOException it logs and returns false.",
      "MmsProvider.java:openFile(): its inline canonical-path prefix check is replaced with isFilePathCanonical(getContext(), filePath). The logic is the same.",
      "tests/.../MmsProviderTest.java: setUp creates a temp testDir with a 'parts' subdirectory. Two new tests, testDeleteDataRows_pathStartsWithPartsDirectory_canDeleteFile and testDeleteDataRows_invalidPath_cannotDeleteFile, call isFilePathCanonical with context.getDir(PARTS_DIR_NAME, anyInt()) mocked. tearDown removes the temp files."
     ],
     "completeness": [
      "Covers every file deletion in MmsProvider that goes through deleteDataRows: TABLE_PART deletions (direct deletes on part URIs and cascading deletes from deleteMessages) and TABLE_DRM deletions through deleteTempDrmData.",
      "openFile already had the same check. It is only refactored to share the helper, so it gains no new coverage.",
      "The check is a string prefix comparison on canonical paths with no trailing separator, so a path under a sibling directory whose name starts with the parts directory path would pass. openFile already used the same comparison.",
      "The fix does not stop disallowed _data values from being inserted or updated. The TODO in openFile about rejecting such paths at insert/update time is still in the code. The commit message mentions separate SQL injection protection changes, but those are not in this material.",
      "DRM rows are now held to the same parts-directory requirement. The material does not show where DRM temp files are stored, so it cannot be determined from the diff whether any legitimate DRM file deletion is now skipped."
     ],
     "behavior_changes": [
      "Files referenced by part/drm _data columns whose canonical path is not under the provider's parts directory are no longer deleted. An error log 'deleteFile: path ... does not start with ...' is written instead. The database row is still deleted.",
      "If canonicalization throws IOException, the file is not deleted (isFilePathCanonical returns false).",
      "Rows with null _data are now skipped explicitly. This matches the previous effective behavior.",
      "deleteParts, deleteTempDrmData and deleteDataRows gain a Context parameter, and isFilePathCanonical is a new public static helper."
     ],
     "preconditions": [
      "A caller able to invoke MmsProvider.delete on a part, message or drm URI. The provider's normal subId/user filtering in delete() still applies.",
      "A row in the part or drm table whose _data column points to a file outside context.getDir(\"parts\", 0). The material does not show how such a value gets into the database."
     ],
     "per_major": {
      "14": "Same fix: commit 1552b67718fc in android-security-14.0.0_r25. The logic is identical; line offsets differ, and the test file also adds an mUserManager field. 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: commit e40b30ca3fa3 in android-security-15.0.0_r13. 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix: commit 37babc702849 in android-security-16.0.0_r4 (cherry-pick of fce8998a1770). 7/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected: the bulletin does not list Android 17. 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "A single self-contained commit to MmsProvider.java plus its test. It relies on PARTS_DIR_NAME and the existing openFile canonical-path check, both of which appear as context in the diffs for 14, 15 and 16. The hunk offsets differ slightly per branch (the delete() hunk starts at line 769 on 14, 799 on 15 and 805 on 16), but the logic is the same."
     ],
     "confidence": "high",
     "unknowns": [
      "How a _data path outside the parts directory gets into the part/drm table in the first place. The referenced bug comment and the 'recent sql injection protection changes' are not in the material.",
      "Where DRM temp files (TABLE_DRM _data) are stored, and so whether the new check changes legitimate DRM file cleanup.",
      "Which of the 8 patched lines differs on the latest security tags (the resolver reports 7/8 on every branch, including 16), and whether that difference is only whitespace."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48619": {
     "cve": "CVE-2025-48619",
     "summary": "Adds a validateFileMode() sanitizer to ContentProvider's Binder Transport so that openFile and openAssetFile strip the truncate ('t') and append ('a') mode characters when the write ('w') character is absent, and use the sanitized mode for both the permission check and the provider call.",
     "component": "platform/frameworks/base, core/java/android/content/ContentProvider.java (ContentProvider.Transport, content provider file open IPC)",
     "weakness": "Insufficient validation of the caller-supplied file mode string, allowing file-modifying open flags (truncate/append) without the write bit (improper authorization)",
     "root_cause": "In ContentProvider.Transport.openFile and Transport.openAssetFile, the caller-controlled mode string was passed unchanged to enforceFilePermission(attributionSource, uri, mode) and then to mInterface.openFile/openAssetFile. A mode such as \"rt\" or \"ra\" contains no 'w' but still carries truncation or append characters that were forwarded to the provider implementation. The commit message and fix comment indicate this could allow unintended changes to files when the write bit was not specified (the exact handling of the mode by enforceFilePermission is not shown). The fix comment states that only r, w, wt, wa, rw, rwt are supported modes.",
     "fix_analysis": [
      "core/java/android/content/ContentProvider.java: new private method Transport.validateFileMode(String mode). If mode is non-null and contains no 'w', it removes every 't' (no truncation without write) and every 'a' (no appending without write) via String.replace, then returns the possibly modified string. If 'w' is present, or mode is null, the string is returned unchanged.",
      "core/java/android/content/ContentProvider.java: Transport.openFile computes updatedMode = validateFileMode(mode) after validateIncomingUri/maybeGetUriWithoutUserId, and passes updatedMode both to enforceFilePermission and to mInterface.openFile, so the permission decision and the actual open use the same sanitized mode.",
      "core/java/android/content/ContentProvider.java: Transport.openAssetFile gets the identical change, passing updatedMode to enforceFilePermission and mInterface.openAssetFile.",
      "The commit deliberately sanitizes instead of rejecting: the comment says throwing SecurityException for unsupported modes was avoided for app-compat reasons."
     ],
     "completeness": [
      "Covers the two Binder entry points changed in Transport: openFile and openAssetFile. Both the permission check and the forwarded mode are made consistent.",
      "Only the case where 'w' is absent is sanitized; when 'w' is present the mode string is forwarded unchanged, so any other unexpected characters are still passed through (consistent with the comment that strict allow-listing was avoided for compatibility).",
      "Sanitization is only applied on the Transport (IPC) path. Direct in-process calls to ContentProvider.openFile/openAssetFile are not affected by this change; the material does not show whether that matters.",
      "Transport.openTypedAssetFile is not touched by the diff; the material does not show whether it takes a caller-supplied mode, so no gap can be asserted.",
      "Per-major facts report 7/8 patched-region lines present at the latest tags for all majors; the material does not show which line differs."
     ],
     "behavior_changes": [
      "Mode strings containing 't' or 'a' but no 'w' (for example \"rt\", \"ra\", \"rat\") are silently rewritten to drop those characters before permission enforcement and before reaching the provider; no exception or error is returned to the caller.",
      "Providers now receive the sanitized mode in openFile/openAssetFile rather than the raw caller string in those cases.",
      "Modes containing 'w', and null modes, are unchanged."
     ],
     "preconditions": [
      "Caller reaches a ContentProvider over Binder via ContentProvider.Transport.openFile or Transport.openAssetFile and supplies a mode string containing 't' or 'a' without 'w'.",
      "Likely the caller must pass enforceFilePermission for the non-write mode (presumably read access to the URI); enforceFilePermission's behavior is not shown in the material.",
      "Target provider implementation honours the mode string when opening the underlying file so that truncate/append characters take effect; the material does not identify specific providers."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base f54544f81d7d in android-security-14.0.0_r24; identical validateFileMode and call-site changes, only surrounding context differs (TRACE_TAG_DATABASE instead of TRACE_TAG_ACTIVITY_MANAGER). 7/8 patched-region lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base e623c490905c in android-security-15.0.0_r12; identical diff. 7/8 patched-region lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 31e77d73c5e6 in android-security-16.0.0_r3 (cherry-pick of f8099b069367). 7/8 patched-region lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); 7/8 patched-region lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit touching only ContentProvider.java. The only per-branch context difference is the trace tag constant used in traceBegin on Android 14 (TRACE_TAG_DATABASE), which is outside the changed lines. Forks that customized Transport.openFile/openAssetFile must apply validateFileMode to both the enforceFilePermission argument and the mInterface call."
     ],
     "confidence": "high",
     "unknowns": [
      "Exact behavior of enforceFilePermission with respect to the mode string is not shown in the material (inferred from the diff and commit message that it keys off 'w').",
      "Which specific providers or files were reachable in the original report is not shown.",
      "Which of the 8 patched-region lines is missing at the latest security tags, and why, is not shown.",
      "Whether Transport.openTypedAssetFile or other paths accept a caller-supplied mode is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48630": {
     "cve": "CVE-2025-48630",
     "summary": "SkiaRenderEngine::drawLayersInternal now clips the canvas to the layer bounds before drawing blur regions when no rounded-corner clip is present, so a client-supplied blur region larger than the layer can no longer draw blurred content outside the layer.",
     "component": "platform/frameworks/native, libs/renderengine (Skia RenderEngine, SurfaceFlinger compositing)",
     "weakness": "Missing clip/bounds restriction when drawing client-controlled blur regions (improper restriction of rendered region)",
     "root_cause": "In SkiaRenderEngine::drawLayersInternal (libs/renderengine/skia/SkiaRenderEngine.cpp), the blur path computed `bounds` and `roundRectClip` with getBoundsAndClip, but it applied a canvas clip only when `roundRectClip` was non-empty. For layers without rounded corners, no clip was applied inside the SkAutoCanvasRestore scope, so blur regions supplied by the client were not restricted to the layer's geometry. The commit message says that a 1-2 pixel layer with a suitably crafted blur region could make the blur \"escape\" the layer bounds and fill the entire screen. The bulletin classifies this as information disclosure (ID), severity High.",
     "fix_analysis": [
      "libs/renderengine/skia/SkiaRenderEngine.cpp:SkiaRenderEngine::drawLayersInternal: an `else` branch is added to the existing `if (!roundRectClip.isEmpty()) canvas->clipRRect(roundRectClip, true);`. When there is no rounded-corner clip, `canvas->clipRRect(bounds, true)` is applied instead. `bounds` is the layer's own geometry returned by getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop, layer.geometry.roundedCornersRadius).",
      "The clip is applied inside the existing `SkAutoCanvasRestore acr(canvas, true)` scope. It therefore applies to the blur drawing that follows and is restored before shadows are drawn, as the existing code comment describes.",
      "Single commit, 4 added lines. Nothing changes in how blur regions are received or validated before they reach RenderEngine."
     ],
     "completeness": [
      "Covers the only unclipped branch in the blur path. With the fix, every layer that reaches the blur drawing code is clipped either to roundRectClip or to bounds.",
      "The fix is only a rendering-side clip in SkiaRenderEngine. The diff adds no validation of blur region coordinates where they are received from the client (that code is not in the material), so oversized blur regions are still accepted and processed, just clipped.",
      "The clip uses `bounds` as computed by getBoundsAndClip from layer.geometry.boundaries. The fix is only correct if that geometry reflects the real layer area, which the material does not show in detail.",
      "No other render paths are modified, for example non-Skia engines or the shadow drawing after the SkAutoCanvasRestore scope. The material gives no indication that they share the issue."
     ],
     "behavior_changes": [
      "Blur output for layers without rounded corners is now confined to the layer bounds. Any blur region that extends beyond the layer is cut off at the layer edge.",
      "One additional clipRRect call per blurred layer without rounded corners. No new error codes or API changes."
     ],
     "preconditions": [
      "The device uses SkiaRenderEngine with a blur filter available (mBlurFilter non-null), and the layer satisfies layerHasBlur(layer, ctModifiesAlpha).",
      "The layer has no rounded-corner clip (roundRectClip empty), so the previously unclipped branch is taken.",
      "A client supplies a blur region larger than the layer's own bounds (commit message: 'a client sending a bigger blur region').",
      "Reached through RenderEngineThreaded::drawLayers, which queues a call to instance.drawLayersInternal on the render thread (frameworks/native/libs/renderengine/threaded/RenderEngineThreaded.cpp, per index search)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/native 6906658b06e3 in android-security-14.0.0_r25, the same 4-line else branch in SkiaRenderEngine::drawLayersInternal (hunk at line 833); 4/4 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/native 4deb93b73906 in android-security-15.0.0_r13, the same 4-line else branch (hunk at line 864); 4/4 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/native c81cf361489e in android-security-16.0.0_r4 (hunk at line 907), cherry-picked from internal changes 123f8fec995a and f3fecb029780; 4/4 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched lines are present (4/4) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained hunk: find the `if (!roundRectClip.isEmpty()) { canvas->clipRRect(roundRectClip, true); }` block inside the `SkAutoCanvasRestore acr(canvas, true);` scope of SkiaRenderEngine::drawLayersInternal and add the else branch. Google's hunks on 14, 15 and 16 have identical text and differ only in line offsets."
     ],
     "confidence": "high",
     "unknowns": [
      "How blur regions are accepted from clients before RenderEngine (SurfaceFlinger/SurfaceControl path), and whether any range validation happens there; the material only shows the RenderEngine side.",
      "The exact contents of the two internal cherry-picked changes (123f8fec995a, f3fecb029780) beyond the 4 lines that landed in AOSP.",
      "Whether the original internal fix included a test; the AOSP commit cites only 'PoC app'.",
      "The exact conditions checked by layerHasBlur."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48634": {
     "cve": "CVE-2025-48634",
     "summary": "WindowManagerService now strips privileged LayoutParams.privateFlags (PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) that a caller tries to add during window relayout without holding INTERNAL_SYSTEM_WINDOW or MANAGE_ACTIVITY_TASKS.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/WindowManagerService.java)",
     "weakness": "Missing permission check on caller-supplied window private flags during relayout",
     "root_cause": "In the relayout path of WindowManagerService (the body around win.mRelayoutSeq, where attrs supplied by the client are applied), the caller-provided attrs.privateFlags were accepted after displayPolicy.adjustWindowParamsLw with no permission check. attrs.flags (sanitizeFlagSlippery), attrs.inputFeatures (sanitizeInputFeatures / sanitizeSpyWindow) and the system UI disable flags (hasStatusBarPermission) were checked, but privateFlags were not. A client could therefore add PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP on an existing window via relayout without holding INTERNAL_SYSTEM_WINDOW / MANAGE_ACTIVITY_TASKS.",
     "fix_analysis": [
      "WindowManagerService.java, relayout path: immediately after displayPolicy.adjustWindowParamsLw(win, attrs) and before sanitizeFlagSlippery, the fix assigns attrs.privateFlags = sanitizePrivateFlags(attrs.privateFlags, win.mAttrs.privateFlags, win.getName(), uid, pid), where uid/pid are Binder.getCallingUid()/getCallingPid() captured before clearCallingIdentity.",
      "WindowManagerService.java, new sanitizePrivateFlags(newPrivateFlags, oldPrivateFlags, windowName, callingUid, callingPid): computes addedPrivateFlags = ~oldPrivateFlags & newPrivateFlags (only flags not already present on win.mAttrs are considered). If any of PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY | PRIVATE_FLAG_TRUSTED_OVERLAY is being added and the caller lacks android.Manifest.permission.INTERNAL_SYSTEM_WINDOW, both bits are cleared from the result and a Slog.w is emitted. If PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP is being added and the caller lacks android.Manifest.permission.MANAGE_ACTIVITY_TASKS, that bit is cleared with a Slog.w.",
      "WindowManagerService.java, new helpers hasFlags(int flags, int mask) and hasPermission(String permission, int callingPid, int callingUid), the latter using mContext.checkPermission(permission, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED.",
      "WindowManagerServiceTests.java: adds testRelayout_add{TrustedOverlay,RoundedCornersOverlay,InterceptGlobalDragAndDrop}_permission{Denied,Granted} and extends testRelayoutFlagChanges with internalSystemWindowGranted/manageActivityTasksGranted parameters that stub mWm.mContext.checkPermission for the two permissions; when nothing changes on a non-first relayout it verifies keepActivityOnWindowFlagsChanged is never called."
     ],
     "completeness": [
      "Covers only the relayout path: the new check is applied where attrs != null in the relayout body. The window-add path is not touched by this diff; whether it already validates these private flags is not shown in the material.",
      "Only three private flags are sanitized (PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP). Any other privateFlags bits in attrs.privateFlags remain unvalidated on relayout by this change.",
      "Only newly added bits trigger a check (addedPrivateFlags = ~old & new); bits already set on win.mAttrs.privateFlags are kept if nothing new is added. Conversely, when the INTERNAL_SYSTEM_WINDOW check fails, both TRUSTED_OVERLAY and ROUNDED_CORNERS_OVERLAY are cleared even if only one of them was newly added.",
      "The sanitization runs before sanitizeInputFeatures, which still receives win.isTrustedOverlay(); the diff does not change that call."
     ],
     "behavior_changes": [
      "Relayout requests that add PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY from a caller without INTERNAL_SYSTEM_WINDOW now have both bits silently removed (no exception), with a Slog.w warning naming the window.",
      "Relayout requests that add PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP from a caller without MANAGE_ACTIVITY_TASKS now have the bit removed with a Slog.w warning.",
      "Up to two additional mContext.checkPermission calls can occur per relayout when such bits are being added.",
      "For a rejected change on a non-first relayout, mDwpcHelper.keepActivityOnWindowFlagsChanged is not invoked (verified by the new tests)."
     ],
     "preconditions": [
      "Caller has an existing window registered with WindowManagerService (windowForClientLocked returns non-null) and issues a relayout with non-null attrs.",
      "The attrs carry one of the three privileged private flags not already set on the window.",
      "Caller lacks INTERNAL_SYSTEM_WINDOW (for the overlay flags) or MANAGE_ACTIVITY_TASKS (for the drag-and-drop flag)."
     ],
     "per_major": {
      "14": "adapted fix: commit c88804906997 in android-security-14.0.0_r24; identical sanitizePrivateFlags logic and call site, but the surrounding context calls sanitizeSpyWindow instead of sanitizeInputFeatures, and the test adds an android.content.pm.PackageManager import and switches createWindow to newWindowBuilder. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix (test lightly adapted): commit a54ba90ae134 in android-security-15.0.0_r12; service change identical, test switches createWindow to newWindowBuilder. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit d550a457e65c in android-security-16.0.0_r3 (cherry-pick of ec6a92be6c93). 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit; the visible context dependency is the name of the neighbouring inputFeatures sanitizer (sanitizeSpyWindow on 14, sanitizeInputFeatures on 15/16). For the test, older trees may need the PackageManager import and the newWindowBuilder helper (or keep createWindow)."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the window-add path already validates these private flags; the material only shows the relayout change.",
      "The concrete impact of each flag beyond the bulletin's High / EoP rating.",
      "How win.isTrustedOverlay() is derived and whether it reflects the sanitized attrs at the time sanitizeInputFeatures is called.",
      "The exact name of the enclosing relayout method is not printed in the diff hunk header; the context body shows the relayout logic (win.mRelayoutSeq, attrs handling)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48641": {
     "cve": "CVE-2025-48641",
     "summary": "Makes the static NFC HAL callbacks Nfc::eventCallback and Nfc::dataCallback in hardware/st/nfc aidl/Nfc.h take a mutex-protected local std::shared_ptr copy of the static Nfc::mCallback before dereferencing it, so the INfcClientCallback object cannot be destroyed between the null check and the sendEvent()/sendData() call.",
     "component": "platform/hardware/st/nfc, AIDL NFC HAL (aidl/Nfc.h, struct Nfc)",
     "weakness": "Use-after-free caused by a race condition (unsynchronized access to a shared pointer from an asynchronous callback thread)",
     "root_cause": "Nfc::eventCallback and Nfc::dataCallback (static functions defined inline in aidl/Nfc.h) read the static std::shared_ptr<INfcClientCallback> Nfc::mCallback directly. They tested 'mCallback != nullptr' and then called mCallback->sendEvent(...) / mCallback->sendData(...) on the shared member itself, with no lock and no local strong reference. Per the commit message, an asynchronous thread (eventCallback) called a method on mCallback right after the main thread in Nfc::open had destroyed the underlying object. The plain null check could not prevent this race.",
     "fix_analysis": [
      "aidl/Nfc.h (file scope): adds 'static pthread_mutex_t sCallbackLock = PTHREAD_MUTEX_INITIALIZER;' as a header-level static mutex.",
      "aidl/Nfc.h:Nfc::eventCallback: declares 'std::shared_ptr<INfcClientCallback> localCallback', locks sCallbackLock, copies mCallback into localCallback and unlocks. It then null-checks localCallback and calls localCallback->sendEvent(mEvent, mStatus) instead of mCallback->sendEvent(...). The local copy holds a strong reference for the duration of the call.",
      "aidl/Nfc.h:Nfc::dataCallback: same pattern. The locked copy into localCallback is taken before the std::vector<uint8_t> data is built, and the null check and the sendData(data) call use localCallback.",
      "No other files are changed (diffstat: 1 file, 14 insertions, 4 deletions). The commit message calls the lock 'mCallbackLock'; in the code it is named sCallbackLock."
     ],
     "completeness": [
      "Covers both static reader paths in aidl/Nfc.h (eventCallback and dataCallback) with the same lock-and-copy pattern.",
      "Only the read side is changed in this commit. The commit message says both read and write of mCallback are protected, but the diff touches no .cpp file. The material does not show whether the code that writes mCallback (Nfc::open per the commit message) takes sCallbackLock. If the writer does not take the same lock, the reader-side mutex only serializes readers against each other.",
      "Per-major facts show 7/8 lines of the patched region present at the latest security tags (14.0.0_r29, 15.0.0_r17, 16.0.0_r8, 17.0.0_r1). The material does not show which line differs."
     ],
     "behavior_changes": [
      "A pthread mutex is locked and unlocked on every HAL event and data callback. The critical section is short: it only copies the pointer.",
      "If open replaces or clears the callback object while a sendEvent/sendData call is in flight, the old object can still receive that one call, because the local strong reference keeps it alive until the call returns.",
      "In dataCallback the callback pointer is now snapshotted before the data vector is constructed. There are no API, error code or logging changes."
     ],
     "preconditions": [
      "The device uses the ST NFC AIDL HAL from platform/hardware/st/nfc (this code is specific to that HAL).",
      "mCallback is reassigned through INfc::open on the HAL while the HAL's asynchronous thread is delivering events or data through Nfc::eventCallback / Nfc::dataCallback.",
      "The bulletin rates this as High severity EoP. The material does not show the exact attack surface."
     ],
     "per_major": {
      "14": "same fix: commit 502f1802c4e1 in android-security-14.0.0_r25. The header-only diff is identical apart from line offsets (the 14 branch lacks the dump() declaration). 7/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit ffe16073e613 in android-security-15.0.0_r13, identical to the Android 14 diff (same blob ids 2ac623b..af9140d). 7/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit c6da9eeb710c in android-security-16.0.0_r4 (cherry-pick of fc619c3348188ff0020cdeb7d4c4728a0246fe1a). 7/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change is self-contained in aidl/Nfc.h and was applied verbatim on 14, 15 and 16. When backporting, also check whether the code that writes mCallback (Nfc::open per the commit message) takes sCallbackLock, because this commit does not show the writer side."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the writers of mCallback (Nfc::open, and possibly close, in Nfc.cpp) take sCallbackLock. The diff only touches Nfc.h.",
      "Which translation units include Nfc.h, and so whether every user of the header-level static sCallbackLock shares the same mutex instance.",
      "Which single line of the 8-line patched region differs at the latest security tags (7/8 present on every major).",
      "The concrete trigger path and the privilege required, beyond the bulletin's High/EoP rating."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48642": {
     "cve": "CVE-2025-48642",
     "summary": "Changes pvmfw and libvmbase aarch64 data cache maintenance from \"dc cvau\" (clean to Point of Unification) to \"dc cvac\" (clean to Point of Coherency) so that memory zeroed by pvmfw before handing off to the payload is written back to main memory on SoCs with a unified cache before the PoC.",
     "component": "platform/packages/modules/Virtualization, pvmfw (protected VM firmware) and libvmbase, aarch64 cache maintenance",
     "weakness": "Insufficient clearing of sensitive data before transfer of control (cache maintenance operation to the wrong point: PoU instead of PoC), leading to information disclosure",
     "root_cause": "In guest/pvmfw/src/arch/aarch64/payload.rs:jump_to_payload, pvmfw zeroes .data/.bss, the stack and the EH stack (\"Zero all memory that could hold secrets\"). It then flushes the d-cache over those regions before disabling caches and jumping to the payload. The flush used \"dc cvau\", which only cleans to the Point of Unification. Per the commit message, some SoCs have a unified cache before the Point of Coherency, so on those SoCs a clean to PoU might not write the zeroed data to main memory. The generic libs/libvmbase/src/arch.rs:flush_region helper used the same PoU clean.",
     "fix_analysis": [
      "guest/pvmfw/src/arch/aarch64/payload.rs:jump_to_payload: the three inline-asm d-cache flush loops (over .data & .bss including the skipped region, over the stack region, and over the EH stack region) change from \"dc cvau, {cache_line}\" to \"dc cvac, {cache_line}\". The zeroed lines are now cleaned to the Point of Coherency rather than only to the Point of Unification.",
      "libs/libvmbase/src/arch.rs:flush_region: the per-line call changes from crate::dc!(\"cvau\", line) to crate::dc!(\"cvac\", line), so every caller of this vmbase flush helper also cleans to PoC.",
      "No other logic changes: loop bounds, line-size computation and ordering relative to the zeroing stores and the cache disable stay the same."
     ],
     "completeness": [
      "Covers all three explicit d-cache flush loops in the aarch64 jump_to_payload (.data/.bss including the skipped region, stack, EH stack) and the shared libvmbase flush_region helper.",
      "The fix only changes the cache-maintenance point. It does not change which regions are zeroed.",
      "The x86_64 counterpart guest/pvmfw/src/arch/x86_64/payload.rs:jump_to_payload (per index search) is not touched by this commit. The snippet shows CR0 constants (including CR0_CD), not ARM cache maintenance operations.",
      "The material does not show whether any other \"dc cvau\" uses exist elsewhere in the Virtualization project. Only these two files were patched."
     ],
     "behavior_changes": [
      "Cache cleans performed by pvmfw before payload hand-off, and by vmbase flush_region, now go to the Point of Coherency. There is no API, error code or interface change."
     ],
     "preconditions": [
      "The device uses pvmfw on aarch64. The affected code runs in jump_to_payload when pvmfw hands control to the guest payload, and in any user of libvmbase flush_region.",
      "Per the commit message, the SoC has a unified cache before the Point of Coherency, so a clean to PoU might not flush data to main memory."
     ],
     "per_major": {
      "14": "adapted fix (ed2818f8301e in android-security-14.0.0_r25): the same cvau->cvac change in the older layout. It covers pvmfw/src/entry.rs:jump_to_payload (two flush loops: .data/.bss including BCC, and stack) and pvmfw/src/helpers.rs:flush_region (inline asm \"dc cvau\" -> \"dc cvac\").",
      "15": "adapted fix (fa7ca326d451 in android-security-15.0.0_r13): the same change in pvmfw/src/entry.rs:jump_to_payload (two flush loops) and vmbase/src/memory/util.rs:flush_region (inline asm).",
      "16": "same fix (f0271f36388e in android-security-16.0.0_r4): guest/pvmfw/src/arch/aarch64/payload.rs:jump_to_payload (three flush loops) and libs/libvmbase/src/arch.rs:flush_region via the dc! macro.",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The change is a simple mnemonic swap (cvau -> cvac), but the file layout differs per release. 14 uses pvmfw/src/entry.rs and pvmfw/src/helpers.rs. 15 uses pvmfw/src/entry.rs and vmbase/src/memory/util.rs. 16 uses guest/pvmfw/src/arch/aarch64/payload.rs and libs/libvmbase/src/arch.rs (dc! macro). The 14 and 15 backports change two flush loops; 16 changes three. Grep for every \"cvau\" in pvmfw and vmbase rather than relying on hunk positions."
     ],
     "confidence": "high",
     "unknowns": [
      "Which SoCs have the unified-cache-before-PoC behavior (the commit only references internal bug b/434562039).",
      "Whether other ARM cache maintenance operations in the project, outside the two patched files, still use PoU semantics.",
      "Exact attacker position and what data was observable. The bulletin only rates it High / ID, and the material does not describe who can read the memory after hand-off.",
      "Whether the Android 17 tag contains the fix: only 6/8 lines of the patched region are reported present."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48644": {
     "cve": "CVE-2025-48644",
     "summary": "Bounds the total size of resolved IME metadata attributes read in the InputMethodInfo constructor with a new TypedArrayWrapper and MetadataReadBytesTracker (200KB cumulative, XmlPullParserException when exceeded), and changes IInputMethodManager.getEnabledInputMethodSubtypeList to return a blob-backed InputMethodSubtypeSafeList so it avoids TransactionTooLargeException.",
     "component": "platform/frameworks/base, input method framework (android.view.inputmethod.InputMethodInfo, com.android.internal.inputmethod SafeList classes, InputMethodManagerService IPC)",
     "weakness": "Missing bounds check on resolved resource data (uncontrolled resource consumption leading to DoS)",
     "root_cause": "The InputMethodInfo constructor parsed the IME's metadata XML and read attributes through plain TypedArray calls (sa.getString, a.getString, etc.). The only size guards were a check on the raw XML file size (the \"Validate file size using InputStream.skip()\" block) and a COMPONENT_NAME_MAX_LENGTH (1000-char) check on si.name, settingsActivity and languageSettingsActivity. String attributes can be resource references (e.g. @string/large_text) whose resolved length does not show up in the XML file size. An IME could therefore make the parser materialize very large strings, per <input-method> attribute and again for every <subtype>. The commit message says this can lead to OOM or large Binder transactions. Separately, IInputMethodManager.getEnabledInputMethodSubtypeList returned a plain List<InputMethodSubtype> in the Binder reply, so a large subtype list could cause a TransactionTooLargeException.",
     "fix_analysis": [
      "Commit 2bca2265ff3e, core/java/android/view/inputmethod/InputMethodInfo.java, constructor: creates one MetadataReadBytesTracker per parse. It wraps res.obtainAttributes(...InputMethod) with TypedArrayWrapper.createForMethod and each per-subtype res.obtainAttributes(...InputMethod_Subtype) with TypedArrayWrapper.createForSubtype, both in try-with-resources. close() calls TypedArray.recycle(). All attribute reads for the <input-method> tag and the <subtype> tags now go through the wrapper's getInt/getString/getBoolean/getResourceId.",
      "InputMethodInfo.TypedArrayWrapper.getString: resolves the string and enforces a per-attribute maximum length from getMaxLength(index), throwing XmlPullParserException if it is exceeded. The maximum is COMPONENT_NAME_MAX_LENGTH for InputMethod_settingsActivity and InputMethod_languageSettingsActivity outside subtypes, and Integer.MAX_VALUE otherwise. It then charges ret.length() * Character.BYTES to the tracker. getInt and getResourceId charge Integer.BYTES and getBoolean charges 1 byte, but only when hasValue(index) is true.",
      "InputMethodInfo.MetadataReadBytesTracker.onReadBytes: starts at MAX_METADATA_SIZE_BYTES (the test comments state 200 * 1024 = 204800 bytes) and throws XmlPullParserException once the remaining budget goes negative, which aborts the parse for that IME.",
      "InputMethodInfo constructor: the old combined check of si.name, settingsActivityComponent and languageSettingsActivityComponent against COMPONENT_NAME_MAX_LENGTH is removed. The si.name check moves earlier, just before the existing file size validation, and the activity-name checks now happen inside TypedArrayWrapper.getString.",
      "Subtype loop restructured: non-START_TAG events use continue, the InputMethodSubtype is built inside the try-with-resources, and the isAuxIme update and subtypes.add run after the wrapper is closed.",
      "Commit a438ce172b44, core/java/com/android/internal/inputmethod/AbstractSafeList.java (new): a generic Parcelable that marshalls a List<T extends Parcelable> into a byte[] via Parcel.writeTypedList/marshall and writes it with Parcel.writeBlob. extractFrom(from, creator) unmarshalls with createTypedArrayList and nulls the buffer, so a second extract returns an empty list.",
      "InputMethodInfoSafeList.java now extends AbstractSafeList: create(null) yields an empty list and the static empty() method is removed. New InputMethodSubtypeSafeList.java/.aidl extend AbstractSafeList<InputMethodSubtype>.",
      "core/java/com/android/internal/view/IInputMethodManager.aidl: getEnabledInputMethodSubtypeList now returns InputMethodSubtypeSafeList instead of List<InputMethodSubtype>. Server side: IInputMethodManagerImpl.Callback, IInputMethodManagerImpl and ZeroJankProxy change their return types, and InputMethodManagerService.getEnabledInputMethodSubtypeList returns InputMethodSubtypeSafeList.create(getEnabledInputMethodSubtypeListInternal(...)). Client side: IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList calls InputMethodSubtypeSafeList.extractFrom(...). InputMethodManagerService's two InputMethodInfoSafeList.empty() calls become InputMethodInfoSafeList.create(null).",
      "Tests: InputMethodInfoTest gains TypedArrayWrapper and tracker tests, and AbstractSafeListTest and InputMethodSubtypeSafeListTest are added."
     ],
     "completeness": [
      "A single tracker is shared across the parse, so cumulative accounting covers every attribute read through the wrapper on the <input-method> tag and on all <subtype> tags.",
      "Per-attribute string length limits apply only to InputMethod_settingsActivity and InputMethod_languageSettingsActivity on the <input-method> tag. getMaxLength returns Integer.MAX_VALUE for InputMethod_stylusHandwritingSettingsActivity and for all <subtype> strings (e.g. physicalKeyboardHintLanguageTag, physicalKeyboardHintLayoutType), with TODO(b/456008595) comments. Those strings are bounded only by the cumulative limit.",
      "The TypedArrayWrapper javadoc mentions STRING_ATTRIBUTES_MAX_CHAR_LENGTH for 'other string attributes', but getMaxLength does not enforce any such constant. The javadoc overstates what is enforced.",
      "Only values read as strings are counted by their length. Resource IDs (label, icon, isDefault) are charged Integer.BYTES, and the size of the resources they point to is not resolved or bounded here.",
      "The raw XML file size check that existed before remains as a separate guard.",
      "The second commit converts only getEnabledInputMethodSubtypeList to the SafeList pattern (getInputMethodList and getEnabledInputMethodList already used InputMethodInfoSafeList). Other IInputMethodManager methods that return InputMethodSubtype objects are unchanged in the diff.",
      "The per-major facts show 7/8 lines of the patched region present at each latest security tag (14 r29, 15 r17, 16 r8, 17 r1). The material does not identify the differing line."
     ],
     "behavior_changes": [
      "An IME whose resolved metadata attributes add up to more than MAX_METADATA_SIZE_BYTES now fails InputMethodInfo construction with XmlPullParserException ('The input method service has metadata exceeds the ... byte limit').",
      "settingsActivity or languageSettingsActivity strings longer than COMPONENT_NAME_MAX_LENGTH now throw from TypedArrayWrapper.getString with a new message ('String resources in input method exceed the length limit of ...'). The si.name check message changed to 'Input method name exceeds ... characters', and that check now runs before the file size validation.",
      "The IInputMethodManager.getEnabledInputMethodSubtypeList AIDL signature now returns InputMethodSubtypeSafeList (blob-based). The client-side invoker still returns List<InputMethodSubtype> via extractFrom.",
      "InputMethodInfoSafeList.empty() is removed and InputMethodInfoSafeList.create(null) returns an empty list instead. AbstractSafeList.extractFrom(null) returns an empty list.",
      "Boolean, int and resource-id attributes are read only when hasValue(index) is true. Otherwise the default is returned."
     ],
     "preconditions": [
      "An installed package that declares an InputMethodService whose metadata <input-method> or <subtype> attributes reference large string resources. That metadata is parsed in the InputMethodInfo constructor.",
      "For the Binder-size path: a caller of IInputMethodManager.getEnabledInputMethodSubtypeList (via IInputMethodManagerGlobalInvoker) for an IME with a large enabled subtype list."
     ],
     "per_major": {
      "14": "adapted fix: 558ee85e1d4a in android-security-14.0.0_r25. Same TypedArrayWrapper/MetadataReadBytesTracker design, but the <input-method> block has no languageSettingsActivity (no Flags check), no isVirtualDeviceOnly and no supportsConnectionlessStylusHandwriting attributes. getMaxLength lists only InputMethod_settingsActivity, and the tests are in core/tests/coretests. Only the parsing commit is shown for 14, so whether the SafeList commit landed there is not shown. 7/8 lines present at r29.",
      "15": "adapted fix: 903125b03826 in android-security-15.0.0_r13. The InputMethodInfo.java change is identical to 16 except it uses Flags.imeSwitcherRevamp() instead of Flags.imeSwitcherRevampApi(). The tests differ slightly. Only the parsing commit is shown for 15. 7/8 lines present at r17.",
      "16": "fix as analysed: 2bca2265ff3e (parsing hardening) and a438ce172b44 (InputMethodSubtypeSafeList) in android-security-16.0.0_r4, both cherry-picks. 7/8 lines present at r8.",
      "17": "not affected: the bulletin does not list Android 17. 7/8 lines of the patched region are present at android-security-17.0.0_r1. No fix commit for 17 is shown."
     },
     "backport_notes": [
      "The parsing commit relies on MAX_METADATA_SIZE_BYTES, COMPONENT_NAME_MAX_LENGTH and the InputStream.skip() file size validation already existing in InputMethodInfo: the diff references them but does not add them. Adapt the <input-method> attribute list and the getMaxLength switch to the attributes present on the target branch (14 lacks languageSettingsActivity, isVirtualDeviceOnly and supportsConnectionlessStylusHandwriting).",
      "The SafeList commit changes an AIDL return type and removes InputMethodInfoSafeList.empty(). The client (IInputMethodManagerGlobalInvoker) and the server (IInputMethodManagerImpl, ZeroJankProxy, InputMethodManagerService) must be updated together, and any out-of-tree users of empty() or of the raw IInputMethodManager method will need adjusting."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the InputMethodSubtypeSafeList commit (a438ce172b44) was also applied to Android 14 and 15. Only the parsing commit is shown for those branches.",
      "The exact definition of MAX_METADATA_SIZE_BYTES and whether STRING_ATTRIBUTES_MAX_CHAR_LENGTH exists in the class. Both are referenced but not defined in the diff.",
      "Which single line of the patched region is reported missing (7/8) at each latest security tag.",
      "Which of the four linked bug IDs corresponds to which symptom (OOM vs. TransactionTooLargeException).",
      "Which system paths construct InputMethodInfo from untrusted IME metadata, and when."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48645": {
     "cve": "CVE-2025-48645",
     "summary": "DeviceAdminInfo.loadDescription now catches an OutOfMemoryError thrown while loading the device admin's description resource through PackageManager.getText and rethrows it as Resources.NotFoundException, the exception type the method already declares.",
     "component": "platform/frameworks/base, device admin (core/java/android/app/admin/DeviceAdminInfo.java)",
     "weakness": "Improper handling of exceptional conditions: an uncaught OutOfMemoryError when loading an app-supplied resource string of unbounded size (resource exhaustion)",
     "root_cause": "DeviceAdminInfo.loadDescription(PackageManager) called pm.getText(mActivityInfo.packageName, mActivityInfo.descriptionRes, mActivityInfo.applicationInfo) directly and returned the result. Nothing on this path limits the size of the description, unlike the hidden loadDescriptionSafe: it delegates to ComponentInfo.loadDescription, which trims to 1024 characters according to its javadoc. If loading the resource threw OutOfMemoryError, the Error left loadDescription uncaught in the calling process. The commit message calls this a DoS caused by injecting a maliciously large string. The bulletin rates it High EoP.",
     "fix_analysis": [
      "core/java/android/app/admin/DeviceAdminInfo.java:loadDescription: the pm.getText(...) call is now inside a try/catch. On OutOfMemoryError the method throws a new NotFoundException, which is the exception type it already declares and already throws when descriptionRes == 0.",
      "No other method changes. The fix does not limit the size of the description. It only turns the OOM failure into the existing 'not found' exception. The fix is one 5-line commit, and the diff is identical on 14, 15 and 16."
     ],
     "completeness": [
      "The fix covers only DeviceAdminInfo.loadDescription and catches OutOfMemoryError only around pm.getText. The allocation attempt still happens in the caller's process. A description that is large but can still be allocated is returned untrimmed. No length cap is added, unlike loadDescriptionSafe / ComponentInfo.loadDescription, which trim to 1024 characters according to the javadoc in context.",
      "The other loaders in the same class, loadLabel (which delegates to mActivityInfo.loadLabel(pm)) and loadIcon, are untouched. The commit message mentions the Receiver's 'label', but the diff only changes the description path.",
      "Callers that do not catch Resources.NotFoundException would still get an uncaught exception, because the Error is rethrown as an exception rather than swallowed. The material does not show how callers handle it."
     ],
     "behavior_changes": [
      "When loading the description resource throws OutOfMemoryError, loadDescription now throws android.content.res.Resources.NotFoundException instead of letting the OutOfMemoryError propagate.",
      "Descriptions that load successfully behave the same. There is no new size limit and no new logging."
     ],
     "preconditions": [
      "An installed app declares a device admin receiver whose ActivityInfo.descriptionRes points to a very large string resource that the app supplies.",
      "Some process calls DeviceAdminInfo.loadDescription(PackageManager) on that admin. Per index search, a Settings test stubs loadDescription when it builds DeviceAdminListItem, which suggests Settings' device admin list calls it. Settings, Car Settings and TvSettings construct DeviceAdminInfo objects.",
      "The resulting OutOfMemoryError must not be caught elsewhere in the calling process."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base ecf88469d44f in android-security-14.0.0_r25, the same 5-line try/catch at a different offset (hunk @@ -442). All 5 lines are present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 42b92b39e52c in android-security-15.0.0_r13, identical diff (hunk @@ -486). All 5 lines are present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base cee45869c491 in android-security-16.0.0_r4 (cherry-picked from internal 06a5b2327caa / 660101e3cdd2, Change-Id Icab26c4b77e73f0fcb9a560e3211482ebe2f37bf). All 5 lines are present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. All 5 lines of the patched region are already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Self-contained 5-line change with no dependencies: wrap pm.getText in DeviceAdminInfo.loadDescription in the same try/catch. On a fork, check that the method still declares NotFoundException and that its callers handle it. If you want a size limit rather than only OOM handling, the hidden loadDescriptionSafe, shown in context, already trims through ComponentInfo.loadDescription."
     ],
     "confidence": "high",
     "unknowns": [
      "Which privileged process the bulletin's EoP rating refers to. The material only shows a failure in whatever process calls loadDescription. The commit message says DoS and mentions the Receiver's 'label', while the diff changes the description path.",
      "Whether any system_server caller invokes loadDescription. The index only surfaced Settings-family users.",
      "How callers handle NotFoundException from loadDescription.",
      "Whether the second internal cherry-pick (660101e3cdd2) contained anything beyond this hunk. Only the single-file diff is shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48646": {
     "cve": "CVE-2025-48646",
     "summary": "In ActivityStarter's FLAG_ACTIVITY_FORWARD_RESULT handling, the fix stops blindly copying sourceRecord.launchedFromPackage into callingPackage. It now copies it only after resolving that package name through PackageManagerInternal.getPackageUid and confirming UserHandle.isSameApp with the calling uid.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (services/core/java/com/android/server/wm/ActivityStarter.java)",
     "weakness": "Missing validation of a trusted-identity field (identity spoofing): a package name taken from the source ActivityRecord was adopted as the caller's package without checking that it belongs to the calling uid.",
     "root_cause": "In ActivityStarter, the trampoline path for Intent.FLAG_ACTIVITY_FORWARD_RESULT checked only that sourceRecord.launchedFromUid == callingUid. When that held, it unconditionally set callingPackage = sourceRecord.launchedFromPackage and callingFeatureId = sourceRecord.launchedFromFeatureId. The removed comment said this was safe because \"these two packages come from the same uid\". That assumption does not hold. The added test describes an ActivityRecord whose launchedFromPackage is a privileged package while its launchedFromUid is the unprivileged caller's uid, a state the test attributes to startNextMatchingActivity(). In that case the uid equality check passes, and the newly started activity records a launchedFromPackage that belongs to another app.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java, FLAG_ACTIVITY_FORWARD_RESULT branch, inside the sourceRecord.launchedFromUid == callingUid block: the fix reads sourceRecord.launchedFromPackage into a local and skips the override entirely if it is null. Previously a null value would have been copied into callingPackage.",
      "Same location: the fix obtains PackageManagerInternal via mService.getPackageManagerInternalLocked(). It then calls getPackageUid(launchedFromPackage, 0 /* flags */, UserHandle.getUserId(callingUid)) to resolve the package name to a uid in the caller's user.",
      "Same location: callingPackage and callingFeatureId are overridden with the source record's launchedFromPackage / launchedFromFeatureId only if UserHandle.isSameApp(packageUid, callingUid) is true. Otherwise they keep the immediate caller's values. The 'this is safe' comment is removed, and a reference to b/457742426 is added.",
      "services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java: adds testLaunchedFromPackage_nextMatchingActivity_forwardResult. The test builds a source record whose launchedFromPackage is a privileged package and whose launchedFromUid is the malicious uid, and mocks mMockPackageManager.getPackageUid for both packages. It then starts a target with FLAG_ACTIVITY_FORWARD_RESULT and asserts that the resulting ActivityRecord's launchedFromPackage and launchedFromUid are those of the immediate caller."
     ],
     "completeness": [
      "Covers the propagation point in the FLAG_ACTIVITY_FORWARD_RESULT branch of ActivityStarter, where launchedFromPackage/launchedFromFeatureId are inherited from the source record. The inherited values are now gated on a package-to-uid ownership check.",
      "Does not change how the mismatched launchedFromPackage/launchedFromUid pair is created in the first place (the test comment attributes this to startNextMatchingActivity()). That record state stays as it is, and only its onward propagation via this path is blocked. The material does not show the startNextMatchingActivity code, so it does not show whether other consumers of such a record are affected.",
      "The check compares appIds via UserHandle.isSameApp, so any package sharing the caller's uid still qualifies for the override. This matches the original 'same uid' intent.",
      "The fix does not touch launchedFromUid. On this path it was already callingUid, and the test asserts that the started activity's launchedFromUid is the immediate caller's uid."
     ],
     "behavior_changes": [
      "Under FLAG_ACTIVITY_FORWARD_RESULT, the started activity now keeps the immediate caller's callingPackage/callingFeatureId unless sourceRecord.launchedFromPackage resolves (via PackageManagerInternal.getPackageUid in the caller's user) to the same appId as callingUid.",
      "A null sourceRecord.launchedFromPackage no longer overwrites callingPackage/callingFeatureId.",
      "This path now makes one additional PackageManagerInternal.getPackageUid lookup. The diff adds no new error codes or exceptions."
     ],
     "preconditions": [
      "The caller starts an activity with Intent.FLAG_ACTIVITY_FORWARD_RESULT, a non-null sourceRecord (resultTo token) and requestCode < 0. With requestCode >= 0, START_FORWARD_AND_REQUEST_CONFLICT is returned earlier.",
      "sourceRecord.launchedFromUid == callingUid.",
      "sourceRecord.launchedFromPackage names a package that callingUid does not own. In the added test's scenario, this state arises when a privileged app launched the caller's activity and the caller then used startNextMatchingActivity() to reach another of its own activities."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base ad0b8a7b63ac in android-security-14.0.0_r25. The ActivityStarter.java hunk (at line ~990) and the test are identical to 16; only offsets differ. At android-security-14.0.0_r29, 5/8 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/base 04c368b5f527 in android-security-15.0.0_r13. The ActivityStarter.java hunk (at line ~1030) and the test are identical. At android-security-15.0.0_r17, 5/8 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base c148b4fae634 in android-security-16.0.0_r4 (hunk at line ~1142). The code context at android-security-16.0.0_r8 shows the patched block; the per-major check reports 5/8 lines of the patched region matched.",
      "17": "not listed by the bulletin (not affected per the bulletin). At android-security-17.0.0_r1, 5/8 lines of the patched region are present, so the material does not fully confirm that the fix is there."
     },
     "backport_notes": [
      "Single self-contained commit. The ActivityStarter.java hunk is identical across 14/15/16; only the line offset differs. It relies on mService.getPackageManagerInternalLocked(), PackageManagerInternal.getPackageUid and UserHandle.isSameApp, which the unmodified 14 hunk also uses.",
      "The new test depends on ActivityBuilder.setLaunchedFromPackage/setLaunchedFromUid and on mMockPackageManager in WindowTestsBase. On older or forked trees, check that these test helpers exist before carrying the test over."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the startNextMatchingActivity() code that produces the launchedFromPackage/launchedFromUid mismatch, or whether that code was changed separately.",
      "The material does not show which privileged consumers of ActivityRecord.launchedFromPackage made this an EoP.",
      "The commit lists two Cherrypick-From sources. The material shows only the combined result, so how the change was split between them is unknown.",
      "The material does not show what getPackageUid returns for a package that does not resolve in the caller's user. The diff only shows that the override is skipped whenever isSameApp fails.",
      "Per-major checks report only 5/8 lines of the patched region present at each latest tag (14 r29, 15 r17, 16 r8, 17 r1). Which lines differ is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48654": {
     "cve": "CVE-2025-48654",
     "summary": "CompanionDeviceManagerService now disassociates every association that the AssociationStore reports as revoked during service start-up, so revoked associations no longer survive a reboot.",
     "component": "platform/frameworks/base, CompanionDeviceManager service (services/companion)",
     "weakness": "Incomplete cleanup / stale persisted state: associations marked revoked were not removed after a reboot",
     "root_cause": "In CompanionDeviceManagerService start-up, after mAssociationStore.refreshCache() initialised the association store, the start-up path did not process associations that were already in the revoked state. A revoked association could therefore stay in the store across a reboot instead of being fully disassociated, which left stale association state in place. The REASON_REVOKED disassociation reason and AssociationStore.getRevokedAssociations() already existed, because the diff uses them without adding them; the boot path did not act on them.",
     "fix_analysis": [
      "services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java (service start-up, directly after mAssociationStore.refreshCache()): adds a loop over mAssociationStore.getRevokedAssociations() that calls mDisassociationProcessor.disassociate(ai.getId(), REASON_REVOKED) for each AssociationInfo. The normal disassociation path therefore runs for every revoked association as soon as the cache is loaded.",
      "Adds the static import of DisassociationProcessor.REASON_REVOKED. The commit changes no other files."
     ],
     "completeness": [
      "Covers only the boot-time path: revoked associations present in the store when the service starts are now removed through the standard DisassociationProcessor.disassociate path.",
      "The fix relies on the existing DisassociationProcessor.disassociate behaviour for the actual cleanup. The material does not show what that method removes.",
      "The fix does not change how an association enters the revoked state or how it is handled while the system is running. Those paths are not in the diff.",
      "The loop runs once at start-up and does not reference a user. Whether getRevokedAssociations() is user-scoped or global is not shown."
     ],
     "behavior_changes": [
      "At service start, each revoked association is disassociated with reason REASON_REVOKED. Any side effects of DisassociationProcessor.disassociate now happen at boot instead of the entries being left in place.",
      "No API surface, return value or permission change is visible in the diff."
     ],
     "preconditions": [
      "One or more associations must be in the revoked state in the persisted AssociationStore when the device reboots. The material does not show how an association reaches that state.",
      "The code runs unconditionally in CompanionDeviceManagerService start-up after mAssociationStore.refreshCache(); no caller-side condition is required."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin). At android-security-14.0.0_r29, 1/8 lines of the patched region are present. Google applied no fix.",
      "15": "Not affected (not listed by the bulletin). At android-security-15.0.0_r17, 6/8 lines of the patched region are present. Google applied no fix.",
      "16": "Same fix: commit 924df83d73d9 in android-security-16.0.0_r4 (cherry-pick, Change-Id I94b96d98608d6702e1d3a9581e135280149bf7e1). 8/8 lines are present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). 8/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The patch depends on AssociationStore.getRevokedAssociations(), DisassociationProcessor.REASON_REVOKED and the mDisassociationProcessor field already existing in CompanionDeviceManagerService. Forks with older companion code (Android 14 shows 1/8 and Android 15 shows 6/8 of the patched region) must check that these exist before applying the change."
     ],
     "confidence": "medium",
     "unknowns": [
      "How an association enters the revoked state and why it could persist across a reboot (the code that sets the revoked state is not in the material).",
      "What DisassociationProcessor.disassociate cleans up, and therefore what privileges a stale revoked association kept (the bulletin only states EoP, High).",
      "Whether getRevokedAssociations() is scoped to the current user or covers all users.",
      "Why Android 15 is not listed as affected even though it has most of the patched region, and which lines are present there.",
      "Whether Android 14 has the APIs the patch depends on."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64783": {
     "cve": "CVE-2025-64783",
     "summary": "Google fixed CVE-2025-64783 by replacing the Android copy of the Adobe DNG SDK with upstream release 1.7.1 build 2410 in a single 61-file commit (\"Update to DNG SDK 1.7.1 2410\"), rather than with a targeted patch; the specific vulnerable code path is not identified in the material.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK, DNG/TIFF raw image parsing and rendering)",
     "weakness": "unknown (bulletin type is ID, information disclosure; the diff is a wholesale SDK version bump and does not isolate a single flaw)",
     "root_cause": "The material does not isolate the defect. The fix is an upstream SDK update (kDNGSDK_BuildVersion 2410 added to dng_flags.h) touching parsing (dng_ifd.cpp, dng_info.cpp, dng_shared.cpp, dng_stream.cpp, dng_linearization_info.cpp, dng_misc_opcodes.cpp, dng_gain_map.cpp, dng_jxl.cpp), fingerprinting (dng_fingerprint.*), and writing (dng_image_writer.*). Visible hunks that touch input handling include: dng_jxl_io_buffer::ReadChunk in dng_jxl.cpp, whose new body checks the bytes remaining in the dng_stream, throws EndOfFile when it is exhausted and clamps the read size (the previous body is not shown); dng_ifd::ParseTag in dng_ifd.cpp, which adds a new tcGainMapMetadata_ISO_21496_1 tag reader with a 4 MiB tagCount cap; and dng_opcode_GainMap::ProcessArea in dng_gain_map.cpp, whose row/column loops were rewritten to iterate a precomputed count with pitch clamped to the overlap size. Which of these (or of the roughly 3000 lines not shown, including dng_stream.cpp, dng_pixel_buffer.cpp, dng_misc_opcodes.cpp, dng_linearization_info.cpp) corresponds to the bulletin entry is unknown.",
     "fix_analysis": [
      "Single commit 6b5cf2a88ebd (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8, Bug 465781139) updates the SDK sources to Adobe DNG SDK 1.7.1 build 2410; dng_flags.h gains kDNGSDK_MajorVersion/MinorVersion/DotVersion/BuildVersion definitions.",
      "source/dng_jxl.cpp: the new dng_jxl_io_buffer::ReadChunk computes bytes_remaining_in_stream = stream.Length() - stream.Position(), calls ThrowEndOfFile(\"dng_jxl_io_buffer\") when zero, limits next_read_size to min(fChunkSize, remaining) and memmoves leftover data to the start of the buffer when the next read would overflow it. The file also adds chunked reader/writer classes (jxl_data_writer, jxl_data_writer2, jxl_image_chunk_reader, jxl_buffer_chunk_reader); jxl_image_chunk_reader uses SafeUint32Mult for buffer sizing. The hunks showing how these classes are used are truncated.",
      "source/dng_ifd.cpp: dng_ifd::ParseTag adds case tcGainMapMetadata_ISO_21496_1 with CheckTagType(ttUndefined), a kMaxTagCount = 4*1024*1024 limit (ThrowBadFormat when exceeded) and host.Allocate(tagCount) before stream.Get; dng_ifd::IsValidDNG accepts sfPreviewGainMap; dng_ifd::IsBaselineJPEG treats ccJPEG + piRGB with 3 or 4 samples as baseline.",
      "source/dng_gain_map.cpp: dng_opcode_GainMap::ProcessArea clamps rowPitch/colPitch to overlap.H()/overlap.W(), precomputes rows/cols counts and iterates by index rather than by raw row/col stepping.",
      "source/dng_fingerprint.h/.cpp: dng_fingerprint::data becomes private with Data()/MutableData() accessors and an explicit copy constructor/assignment; dng_md5_printer::Process(const void*,uint32) is renamed ProcessPtr; new dng_md5_printer_le_stream and dng_md5_direct_printer classes are added; dng_md5_printer_stream::ProcessPtr flushes before hashing. Callers converted in the visible hunks: dng_big_table.cpp, dng_camera_profile.cpp, dng_hue_sat_map.cpp, dng_image_writer.cpp, dng_jpeg_image.cpp, dng_bmff.cpp, dng_exif.cpp, dng_gain_map.cpp, dng_ifd.cpp.",
      "source/dng_exceptions.h/.cpp: dng_exception now derives from std::exception and provides what(); qDNGVerboseExceptions (default 1) carries message/sub_message into the exception; DNG_NO_RETURN is placed before the return type.",
      "source/dng_abort_sniffer.cpp/.h: the dng_sniffer_task destructor is made virtual and moved out of line, and wraps fSniffer->EndTask() in try/catch(...).",
      "source/dng_image_writer.cpp/.h: WriteTIFF/WriteTIFFWithProfile/WriteDNG/WriteDNGWithMetadata gain gainMapMetadataBlock and gain-map alternate ICC profile parameters. In WriteTIFFWithProfile a gain map IFD is written only when both the image and the metadata block are present. In WriteDNGWithMetadata the metadata tag is added when a block is supplied. The PGTM duplication check now uses profile_tag_set::ProfileDidWritePGTMToMainIFD.",
      "source/dng_info.cpp: adds RICOH DNGPrivateData parsing via ParseMakerNoteIFD; fTIFFBlockOriginalOffset is initialised to 0 instead of kDNGStreamInvalidOffset.",
      "Remaining changed files (dng_stream.cpp +189, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp +60, dng_negative.cpp, dng_preview.cpp, dng_resample.cpp, dng_shared.cpp, dng_string.cpp, dng_xmp*.cpp) are listed in the diffstat, but their hunks are truncated in the material."
     ],
     "completeness": [
      "The fix is a monolithic upstream version bump that replaces the whole SDK rather than patching one site. Any defect in the previous Android snapshot that Adobe fixed in 1.7.1 2410 is therefore covered, but the material does not show which site the bulletin entry refers to.",
      "Visible hardening is partial and per path: the new tcGainMapMetadata_ISO_21496_1 reader caps tagCount at 4 MiB, and dng_jxl_io_buffer::ReadChunk has EOF/remaining-bytes checks. Other parsers in dng_ifd::ParseTag shown in context (e.g. tcPreviewSettingsDigest) were only mechanically converted to stream.Get(dng_fingerprint&), with the CheckTagCount logic unchanged.",
      "About 3000 lines of the diff (dng_stream.cpp, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_negative.cpp, etc.) are not shown, so the completeness of bounds checking in those files cannot be judged from the material.",
      "The context at android-security-16.0.0_r8 shows kDNGSDK_BuildVersion 2502, a dng_stream::Get with a maxOverRead parameter, and DNG_REQUIRE area/plane checks in dng_pixel_buffer::SetConstant. Build 2502 indicates that a further SDK update landed after this commit, so those checks cannot be attributed to this commit from the material."
     ],
     "behavior_changes": [
      "dng_jxl_io_buffer::ReadChunk throws dng_error_end_of_file when the input stream has no bytes remaining, and reads at most the remaining bytes.",
      "A GainMapMetadata (ISO 21496-1) tag larger than 4 MiB is rejected with dng_error_bad_format; the tag is now parsed and stored in dng_ifd::fGainMapMetadata.",
      "NewSubFileType sfPreviewGainMap is accepted by dng_ifd::IsValidDNG; JPEG-compressed RGB with 3 or 4 samples per pixel is treated as baseline JPEG in dng_ifd::IsBaselineJPEG.",
      "dng_exception now derives from std::exception, and its what() includes the error code and, by default (qDNGVerboseExceptions=1), the message text; the error codes gain dng_error_sdk_first/dng_error_sdk_end/dng_error_sdk_count.",
      "DNGPrivateData blocks starting with \"RICOH\" are now parsed as a Pentax maker note IFD.",
      "Public API changes: dng_fingerprint::data is private (use Data()/MutableData()); dng_md5_printer::Process(ptr,len) is renamed ProcessPtr; the WriteTIFF/WriteDNG signatures gain gain-map metadata and alternate profile parameters; dng_range_parallel_task::range gains fAllocator; the dng_sniffer_task destructor is virtual.",
      "Several fingerprint computations (RGB tables, hue/sat map interpolation, gain table maps, camera profile) switch to dng_md5_printer_le_stream and endian-aware Put_* calls."
     ],
     "preconditions": [
      "A DNG/TIFF file (possibly JXL-compressed) from an untrusted source is parsed or rendered by code that links external/dng_sdk.",
      "On Android, Skia's SkRawCodec uses the DNG SDK for DNG decoding (general platform knowledge). The material does not show which entry point reaches the vulnerable code for this CVE.",
      "Index search returned frameworks/base/core/jni/android_hardware_camera2_DngCreator.cpp (DngCreator_setup) as a semantic match, but that code uses TiffWriter, and the material does not establish its relevance to dng_sdk parsing."
     ],
     "per_major": {
      "14": "same fix: commit d0bfe8eeee73 \"Update to DNG SDK 1.7.1 2410\" (linked by change_id) in android-security-14.0.0_r25; 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 45da10961287 \"Update to DNG SDK 1.7.1 2410\" (linked by change_id) in android-security-15.0.0_r13; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 6b5cf2a88ebd in android-security-16.0.0_r4 (Cherrypick-From internal 403d0a45 and 2101eead); 8/8 lines present at android-security-16.0.0_r8, where dng_flags.h already shows a later build 2502.",
      "17": "not affected: not listed by the bulletin; the patched region is present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "This is a full SDK snapshot replacement, not a cherry-pickable hunk. It changes public headers (dng_fingerprint::data made private, dng_md5_printer::Process renamed ProcessPtr, the WriteTIFF/WriteDNG parameter lists, the dng_exception base class), so consumers of those APIs must be rebuilt and possibly adjusted. The new dng_jxl.cpp code also calls libjxl APIs such as JxlEncoderUseBoxes and output-processor-style callbacks, so the bundled libjxl must provide them.",
      "An Android-local edit marked \"BEGIN GOOGLE MODIFICATION\" appears in dng_misc_opcodes.cpp (the dng_opcode_MapTable::ProcessArea plane loop) at r8 and should be preserved when importing. Google applied the same commit (linked by change_id) to 14, 15 and 16, so taking the whole commit is the intended route."
     ],
     "confidence": "low",
     "unknowns": [
      "Which specific function or file in the 61-file update corresponds to the CVE-2025-64783 information disclosure; neither the commit message nor the bulletin identifies it.",
      "The previous (pre-fix) bodies of rewritten functions such as dng_jxl_io_buffer::ReadChunk, which are not visible in the diff.",
      "The content of roughly 3000 truncated diff lines, including the dng_stream.cpp, dng_misc_opcodes.cpp, dng_linearization_info.cpp and dng_pixel_buffer.cpp changes.",
      "Whether the two internal cherry-picks (403d0a45..., 2101eead...) contained anything beyond the upstream SDK import (e.g. Android build or consumer adjustments).",
      "Whether the later build 2502 visible at android-security-16.0.0_r8 is a separate security fix or a routine update.",
      "Which platform entry points reach the affected code for this CVE."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64784": {
     "cve": "CVE-2025-64784",
     "summary": "Google fixed this High-severity information-disclosure bug in external/dng_sdk by syncing the whole library to Adobe DNG SDK 1.7.1 build 2410 (61 files, +2721/-694). This is a bulk update that bundles many parser and buffer changes rather than a single targeted patch.",
     "component": "platform/external/dng_sdk, DNG/TIFF/JPEG XL parsing and rendering library",
     "weakness": "unknown. The bulletin type is ID. The diff is a wholesale SDK update whose visible changes add size and bounds handling (tag size cap, writer buffer cap, loop-count clamping), but the material does not isolate the specific flaw.",
     "root_cause": "The material does not isolate the vulnerable line. The fix commit \"Update to DNG SDK 1.7.1 2410\" replaces the previous SDK snapshot wholesale. Shown changes that may relate to an out-of-bounds read class: dng_opcode_GainMap::ProcessArea (dng_gain_map.cpp) used to step rows and columns by the raw RowPitch/ColPitch values, comparing offsets to the overlap edge, and dng_fingerprint exposed its raw data[] array publicly. The dng_jxl_io_buffer::ReadChunk hunk shows the post-fix body with an EOF check and clamping, but the pre-fix body is not visible. The code context at android-security-16.0.0_r8 also shows DNG_REQUIRE area/plane checks in dng_pixel_buffer::SetConstant and dng_linearize_plane::Process, and overflow/finite checks in dng_resample_weights::Initialize. Those files were touched by the commit, but their diffs are truncated in the material, so it is not known whether the commit introduced these checks.",
     "fix_analysis": [
      "Single commit 6b5cf2a88ebd (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8, cherry-picked from two internal commits). It adds kDNGSDK_* version defines (1.7.1, build 2410) to source/dng_flags.h and rewrites or touches 61 source files. There is no isolated security hunk.",
      "source/dng_jxl.cpp dng_jxl_io_buffer::ReadChunk: the post-fix body computes bytes remaining from stream.Length() - stream.Position(), throws ThrowEndOfFile(\"dng_jxl_io_buffer\") when nothing remains, clamps next_read_size to min(fChunkSize, bytes remaining), and memmoves the unconsumed tail to the buffer start when fIndex + next_read_size would exceed fMaxBufferSize. The pre-fix body is not visible in the hunk, so whether these checks are new is not shown.",
      "source/dng_jxl.cpp: adds streaming helpers jxl_data_writer, jxl_data_writer2 (which caps requested buffer size at kMaxSize = 1 GiB), jxl_image_chunk_reader and jxl_buffer_chunk_reader. These use SafeUint32Mult for row step and byte counts and dng_host::Allocate for buffers.",
      "source/dng_gain_map.cpp dng_opcode_GainMap::ProcessArea: rowPitch and colPitch are clamped to overlap.H() and overlap.W(). The row and column loops now run a precomputed count (ceil(H/rowPitch), ceil(W/colPitch)) instead of comparing raw offsets to the overlap edge.",
      "source/dng_ifd.cpp dng_ifd::ParseTag: a new tcGainMapMetadata_ISO_21496_1 case checks for type ttUndefined, rejects tagCount > 4 MiB via ThrowBadFormat, and reads into a host-allocated block. IsValidDNG now accepts sfPreviewGainMap. IsBaselineJPEG treats ccJPEG + piRGB with 3 or 4 samples per pixel as baseline.",
      "source/dng_fingerprint.h/.cpp: dng_fingerprint::data becomes private, with Data()/MutableData() accessors and an explicit copy constructor and copy assignment. dng_md5_printer::Process(void*, len) is renamed ProcessPtr, and typed Process(dng_fingerprint), Process(dng_string), Process_bool and Process_size are added. dng_md5_printer_stream::ProcessPtr flushes before hashing. New classes dng_md5_printer_le_stream and dng_md5_direct_printer are added. Call sites across dng_big_table.cpp, dng_camera_profile.cpp, dng_bmff.cpp, dng_exif.cpp, dng_hue_sat_map.cpp, dng_image_writer.cpp, dng_jpeg_image.cpp and dng_ifd.cpp are converted to typed Get/Put(fingerprint), printer.Process(fingerprint), ProcessPtr or the Data()/MutableData() accessors.",
      "source/dng_exceptions.h/.cpp: dng_exception now derives from std::exception and implements what(). When qDNGVerboseExceptions is set (default 1 in dng_flags.h), Throw_dng_error passes message and sub_message into the exception. Throw_dng_error is declared with DNG_NO_RETURN, which is [[noreturn]] under C++11.",
      "source/dng_abort_sniffer.cpp: the dng_sniffer_task destructor is moved out of line, made virtual, and wrapped in try/catch so exceptions from EndTask are swallowed.",
      "source/dng_info.cpp dng_info::ParseDNGPrivateData: new branch for private data starting with \"RICOH\" that parses a Pentax MakerNote IFD. The initial value of fTIFFBlockOriginalOffset changes from kDNGStreamInvalidOffset to 0.",
      "source/dng_image_writer.cpp/.h: WriteTIFF, WriteTIFFWithProfile, WriteDNG and WriteDNGWithMetadata gain gainMapMetadataBlock and gainMapAltProfileData/Size parameters. In WriteTIFFWithProfile, hasGainMap now requires both the image and the metadata block. Whether the negative's PGTM is written is now decided by whether the main profile already wrote a PGTM to the main IFD (fProfileDidWritePGTM).",
      "The diffs for dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_stream.cpp, dng_pixel_buffer.cpp, dng_resample.cpp, dng_negative.cpp, dng_matrix.cpp, dng_read_image.cpp, dng_shared.cpp, dng_string.cpp and others are truncated in the material and cannot be analysed here."
     ],
     "completeness": [
      "The fix is a full library sync. It covers every change Adobe shipped between the previous Android snapshot and build 2410, not only the reported bug. Which hunk closes CVE-2025-64784 cannot be identified from the material.",
      "The post-fix dng_jxl_io_buffer::ReadChunk guards EOF and buffer wrap, but the pre-fix body is not visible, so any earlier gap cannot be confirmed.",
      "jxl_data_writer::get_buffer (the std::vector-backed variant) grows fOutput with no upper bound, unlike jxl_data_writer2, which caps requests at kMaxSize. Only jxl_data_writer2 is used by the jxl_output_* callbacks shown.",
      "The new tcGainMapMetadata_ISO_21496_1 parse path caps the tag at 4 MiB. The new RICOH DNGPrivateData branch passes fDNGPrivateDataCount - 8 to ParseMakerNoteIFD, and the shown hunk has no explicit check that the count is at least 8.",
      "Android-local code marked GOOGLE MODIFICATION (the plane loop in dng_opcode_MapTable::ProcessArea in dng_misc_opcodes.cpp) is present at android-security-16.0.0_r8. Whether this commit itself preserved it is not shown."
     ],
     "behavior_changes": [
      "dng_exception now derives from std::exception and carries a text message (what()) that includes the DNG error code and, with qDNGVerboseExceptions=1, the message and sub_message passed to Throw_dng_error.",
      "The new DNG tag tcGainMapMetadata_ISO_21496_1 is parsed and rejected with a bad-format error if larger than 4 MiB. NewSubFileType sfPreviewGainMap is accepted by IsValidDNG.",
      "dng_ifd::IsBaselineJPEG returns true for ccJPEG + piRGB with 3 or 4 samples per pixel, which changes how such TIFFs are handled.",
      "DNGPrivateData blocks starting with \"RICOH\" are now parsed as Pentax MakerNote IFDs.",
      "dng_opcode_GainMap::ProcessArea iterates a precomputed number of rows and columns, with pitch clamped to the overlap size.",
      "API changes: dng_fingerprint::data is private (use Data()/MutableData()). dng_md5_printer::Process(const void*, uint32) is renamed ProcessPtr. The WriteTIFF/WriteDNG family gains gain-map metadata parameters. dng_range_parallel_task::range gains fAllocator.",
      "dng_host::MakeJXLEncodeSettings uses host-attached JXLEncodeSettings for lossless use cases."
     ],
     "preconditions": [
      "A crafted DNG/TIFF file (or a JXL-compressed DNG payload) is parsed or rendered by code that links dng_sdk. The touched parse paths include dng_ifd::ParseTag, dng_info::ParseDNGPrivateData, JXL decoding and opcode processing (dng_opcode_GainMap::ProcessArea).",
      "The material does not show which on-device consumer of dng_sdk exposes the vulnerable path. The index matches in android_hardware_camera2_DngCreator.cpp use TiffWriter and show no dng_sdk API, per index search."
     ],
     "per_major": {
      "14": "Same fix: commit d0bfe8eeee73 with the same subject \"Update to DNG SDK 1.7.1 2410\" and matching diff content shown, in android-security-14.0.0_r25, resolved by change_id. The patched region is fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "Same fix: commit 45da10961287 with the same subject and matching diff content, in android-security-15.0.0_r13, resolved by change_id. The patched region is fully present (8/8 lines) at android-security-15.0.0_r17.",
      "16": "Same fix: commit 6b5cf2a88ebd in android-security-16.0.0_r4. The patched region is fully present (8/8 lines) at android-security-16.0.0_r8, where dng_flags.h already shows kDNGSDK_BuildVersion 2502, which suggests a later SDK sync landed afterwards.",
      "17": "Not affected per the bulletin. The patched region is already present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is not a cherry-pickable point fix. It replaces the SDK snapshot across 61 files and includes API-breaking changes: dng_fingerprint::data becomes private, Process is renamed ProcessPtr, the WriteTIFF/WriteDNG signatures change, and dng_exception gets a new base class. Take the whole commit rather than individual hunks, update out-of-tree consumers, and check that Android-local GOOGLE MODIFICATION code (for example in dng_misc_opcodes.cpp) is still present after the pick.",
      "Google applied the same full-update commit to the 14, 15 and 16 security branches, and the diff index hashes shown match. This suggests the pre-fix snapshot was the same on all three."
     ],
     "confidence": "low",
     "unknowns": [
      "Which specific hunk in the 61-file update closes CVE-2025-64784. The material does not describe bug 465781139.",
      "The pre-fix body of dng_jxl_io_buffer::ReadChunk, and the full diffs of dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_stream.cpp (including whether the maxOverRead parameter of dng_stream::Get is new), dng_pixel_buffer.cpp, dng_resample.cpp, dng_negative.cpp and the rest of dng_jxl.cpp, all of which are truncated.",
      "Which Android process links dng_sdk, and therefore the privilege context of the disclosure.",
      "Whether the later build 2502 sync visible at android-security-16.0.0_r8 carries additional security-relevant changes."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-64893": {
     "cve": "CVE-2025-64893",
     "summary": "Google replaced the Android copy of external/dng_sdk with Adobe DNG SDK 1.7.1 build 2410 (single commit, 61 files, +2721/-694), a wholesale upstream update rather than a targeted fix.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK, DNG/TIFF/JPEG XL raw image parsing and rendering)",
     "weakness": "unknown (bulletin rates it High / ID; the commit is a wholesale SDK version bump and does not isolate the specific flaw)",
     "root_cause": "Not isolated by the material. The fix is the commit \"Update to DNG SDK 1.7.1 2410\" (Bug 465781139), which brings the whole SDK to Adobe build 2410. Among the visible changes: dng_ifd::ParseTag gains a new tcGainMapMetadata_ISO_21496_1 handler with a 4 MiB kMaxTagCount cap before host.Allocate; dng_opcode_GainMap::ProcessArea in source/dng_gain_map.cpp clamps row/col pitch to the overlap size and iterates precomputed counts (rows = ceil(overlap.H()/rowPitch), cols = ceil(overlap.W()/colPitch)) instead of stepping raw coordinates; dng_fingerprint::data becomes private with Data()/MutableData() accessors and explicit copy/assignment; dng_md5_printer gains typed Process_* helpers and a dng_md5_printer_le_stream. Which of these (or of the unshown changes in dng_stream.cpp, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_read_image.cpp, dng_resample.cpp, the rest of dng_jxl.cpp) corresponds to the reported information disclosure is not shown.",
     "fix_analysis": [
      "Single commit 6b5cf2a88ebd (Change-Id Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8, cherry-picked from two internal commits) updates source/ to DNG SDK 1.7.1 build 2410; dng_flags.h now defines kDNGSDK_VersionString \"1.7.1\" and kDNGSDK_BuildVersion 2410.",
      "source/dng_jxl.cpp: large rewrite (+1217 lines changed); new streaming jxl_data_writer2 output processor caps requests at kMaxSize (1 GiB) and allocates via the host allocator; new jxl_image_chunk_reader computes buffer sizes with SafeUint32Mult. The dng_jxl_io_buffer::ReadChunk body appears in the added lines, but its previous body is not shown, so what changed there cannot be determined.",
      "source/dng_ifd.cpp dng_ifd::ParseTag: new tcGainMapMetadata_ISO_21496_1 case validates tag type ttUndefined and rejects tagCount above 4 MiB (ThrowBadFormat) before allocating and reading; dng_ifd::IsValidDNG extended to accept sfPreviewGainMap; dng_ifd::IsBaselineJPEG now treats ccJPEG+piRGB with 3 or 4 samples as baseline.",
      "source/dng_gain_map.cpp dng_opcode_GainMap::ProcessArea: row and column pitch are clamped to the overlap size and loops iterate a precomputed count (rows/cols) with explicit col/row increments, replacing the previous coordinate-stepping loops.",
      "source/dng_fingerprint.h/.cpp: dng_fingerprint::data made private (friends dng_fingerprint_less_than, dng_md5_printer), Data()/MutableData() accessors, explicit copy constructor and operator=; dng_md5_printer::Process(ptr,len) renamed ProcessPtr, with Process(dng_fingerprint), Process(dng_string), Process_bool, Process_size; dng_md5_printer_stream::ProcessPtr flushes before hashing; new dng_md5_printer_le_stream and dng_md5_direct_printer. Call sites in dng_big_table.cpp, dng_camera_profile.cpp, dng_hue_sat_map.cpp, dng_image_writer.cpp, dng_jpeg_image.cpp, dng_bmff.cpp, dng_exif.cpp, dng_ifd.cpp updated accordingly.",
      "source/dng_exceptions.h/.cpp: dng_exception now derives from std::exception with what(); qDNGVerboseExceptions (default 1) carries message/sub_message into the exception; DNG_NO_RETURN uses [[noreturn]].",
      "source/dng_abort_sniffer.cpp: dng_sniffer_task destructor moved out of line, made virtual, and wraps EndTask in try/catch.",
      "source/dng_info.cpp: fTIFFBlockOriginalOffset initialised to 0 instead of kDNGStreamInvalidOffset; new RICOH DNGPrivateData branch parses a Pentax maker note IFD via ParseMakerNoteIFD.",
      "Remaining changed files (dng_stream.cpp +189, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp +60, dng_read_image.cpp, dng_resample.cpp, dng_negative.cpp) are listed in the stat but their hunks are truncated in the material."
     ],
     "completeness": [
      "The fix is a full SDK snapshot update, so every file in source/ is brought to upstream build 2410; there is no targeted patch to judge for sibling paths.",
      "Visible bounds-related changes are the new tcGainMapMetadata_ISO_21496_1 parser (4 MiB cap) and the count-based loops in dng_opcode_GainMap::ProcessArea; whether equivalent checks were added in the truncated dng_stream.cpp / dng_read_image.cpp / dng_jxl.cpp hunks cannot be confirmed from the material.",
      "dng_opcode_GainMap::ProcessArea still clamps mapPlane with Min_uint32(plane, fGainMap->Planes() - 1) and bounds the plane loop by buffer.Planes(). The sibling dng_opcode_MapTable::ProcessArea in the android-security-16.0.0_r8 code context uses ceil-divided counts with a GOOGLE MODIFICATION plane bound; the dng_misc_opcodes.cpp hunks of this commit are truncated, so whether the commit itself preserved or touched that block is not shown."
     ],
     "behavior_changes": [
      "dng_exception now derives from std::exception and what() returns \"dng_error: <code>[: message (sub_message)]\" (qDNGVerboseExceptions defaults to 1).",
      "New tag tcGainMapMetadata_ISO_21496_1 is parsed; tag counts above 4 MiB raise a bad-format error. sfPreviewGainMap is accepted as a valid NewSubFileType.",
      "dng_ifd::IsBaselineJPEG returns true for ccJPEG + piRGB with 3 or 4 samples per pixel (scanned TIFF with alpha).",
      "RICOH-prefixed DNGPrivateData blocks are now parsed as Pentax maker notes.",
      "dng_fingerprint::data is no longer publicly accessible; external code must use Data()/MutableData(). dng_md5_printer::Process(const void*, uint32) is renamed ProcessPtr, and dng_md5_printer's constructor is protected (use dng_md5_direct_printer or a stream printer).",
      "dng_image_writer::WriteTIFF/WriteTIFFWithProfile/WriteDNG/WriteDNGWithMetadata gain new trailing parameters (gainMapMetadataBlock, gainMapAltProfileData/Size); WriteTIFFWithProfile only writes a gain map IFD when both the image and the metadata block are supplied.",
      "SDK version macros kDNGSDK_* are now defined in dng_flags.h (1.7.1, build 2410)."
     ],
     "preconditions": [
      "A DNG/TIFF file must be parsed or rendered through the dng_sdk library on the device; the exact tag or code path that triggers the disclosure is not identified in the material.",
      "On Android, external/dng_sdk is used by Skia's SkRawCodec for DNG decoding (general platform knowledge).",
      "The index search returned frameworks/base android_hardware_camera2_DngCreator.cpp, but the shown snippet uses its own TiffWriter and does not call the patched dng_sdk functions; no concrete out-of-project caller of the changed functions is established by the index results."
     ],
     "per_major": {
      "14": "same fix: identical \"Update to DNG SDK 1.7.1 2410\" commit d0bfe8eeee73 (resolved by change_id) landed in android-security-14.0.0_r25; patched region fully present (8/8) at android-security-14.0.0_r29.",
      "15": "same fix: identical commit 45da10961287 (resolved by change_id) in android-security-15.0.0_r13; patched region fully present (8/8) at android-security-15.0.0_r17.",
      "16": "same fix: commit 6b5cf2a88ebd in android-security-16.0.0_r4; patched region fully present (8/8) at android-security-16.0.0_r8, whose code context shows kDNGSDK_BuildVersion 2502 (suggesting a later SDK update landed afterwards).",
      "17": "not affected per the bulletin; android-security-17.0.0_r1 contains the patched region (8/8 lines)."
     },
     "backport_notes": [
      "This is a whole-SDK version bump, not a cherry-pickable hunk: downstream forks must re-apply any local GOOGLE MODIFICATION blocks and adjust callers for the API changes (dng_fingerprint::data now private, dng_md5_printer::Process -> ProcessPtr, new WriteTIFF/WriteDNG parameters, dng_exception deriving from std::exception). Google applied the same commit to 14, 15 and 16.",
      "Out-of-project consumers of dng_sdk (e.g. Skia's SkRawCodec, general platform knowledge) should be rebuilt and checked against the changed dng_fingerprint and dng_md5_printer APIs."
     ],
     "confidence": "low",
     "unknowns": [
      "Which specific function/tag in the SDK produced the information disclosure; the commit message only references Bug 465781139 and the SDK version.",
      "Whether dng_jxl_io_buffer::ReadChunk was actually changed (its prior body is not shown in the diff).",
      "The content of the truncated hunks (dng_stream.cpp, dng_misc_opcodes.cpp, dng_linearization_info.cpp, dng_pixel_buffer.cpp, dng_read_image.cpp, dng_resample.cpp, remainder of dng_jxl.cpp), which may contain the actual fix.",
      "Whether this commit preserved existing GOOGLE MODIFICATION blocks (the code context is from a later tag).",
      "Which 8-line \"patched region\" the per-major resolver tracked.",
      "Whether the second cherry-picked internal commit (2101eead79db) contributed Android-specific changes beyond the upstream snapshot."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0005": {
     "cve": "CVE-2026-0005",
     "summary": "Adds a permission-gated hidden IActivityTaskManager.rebuildSystemLockTaskPinnedMode() that re-applies screen-pinning (LOCK_TASK_MODE_PINNED) status bar, keyguard, recents and task-listener state. SystemUI's KeyguardViewMediator now calls it when the lock task state is still pinned, and the keyguard-enabled flag is kept across a KeyguardService disconnect, so app pinning stays in effect after a SystemUI crash (per the commit title).",
     "component": "platform/frameworks/base, WindowManager LockTaskController / ActivityTaskManagerService (system_server) and SystemUI KeyguardViewMediator / KeyguardServiceDelegate",
     "weakness": "State desynchronization: lock-task (screen pinning) restriction state was not re-applied after SystemUI restarted, and KeyguardServiceDelegate discarded the keyguard-enabled state on service disconnect",
     "root_cause": "LockTaskController keeps the pinned-mode state (mLockTaskModeState, mLockTaskModeTasks) in system_server. Entering pinned mode also applies restrictions outside it: per the test, startLockTaskMode in pinned mode leads to WindowManager.disableKeyguard, status bar disable/disable2 with the pinned mask, notifyLockTaskModeChanged and a RecentTasks notification. Before the fix, nothing re-applied these after a SystemUI crash and restart, while system_server still reported LOCK_TASK_MODE_PINNED. In addition, KeyguardServiceDelegate.onServiceDisconnected called mKeyguardState.reset(), which also discarded mKeyguardState.enabled. As a result the keyguard-disabled state was not re-sent on reconnect, because onServiceConnected only calls setKeyguardEnabled when !mKeyguardState.enabled. The bulletin rates this High / ID.",
     "fix_analysis": [
      "core/java/android/app/IActivityTaskManager.aidl: adds a new hidden binder method rebuildSystemLockTaskPinnedMode() next to startSystemLockTaskMode/stopSystemLockTaskMode.",
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java:rebuildSystemLockTaskPinnedMode: calls enforceTaskPermission(\"rebuildSystemLockTaskPinnedMode\") (the same gate as startSystemLockTaskMode), clears the calling identity, and under mGlobalLock calls getLockTaskController().rebuildSystemLockTaskPinnedMode().",
      "services/core/java/com/android/server/wm/LockTaskController.java:rebuildSystemLockTaskPinnedMode: returns (with a Slog) if mLockTaskModeState != LOCK_TASK_MODE_PINNED or mLockTaskModeTasks is empty. Otherwise it takes the first task and calls mSupervisor.mRecentTasks.onLockTaskModeStateChanged(LOCK_TASK_MODE_PINNED, task.mUserId). It then posts to mHandler: showPinningEnterExitToast(true) if the status bar service is available, then mTaskChangeNotificationController.notifyLockTaskModeChanged(state), setStatusBarState(state, userId) and setKeyguardState(state, userId). The locked task list is not modified.",
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java: in the code path that handles mBootSendUserPresent/sendUserPresentBroadcast (enclosing method not shown), posts a runnable. The runnable queries mActivityTaskManagerService.getLockTaskModeState() and, if the result is LOCK_TASK_MODE_PINNED, calls rebuildSystemLockTaskPinnedMode(). A RemoteException is logged. From the commit title, this is meant to run when a restarted SystemUI comes up while still pinned.",
      "services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java:onServiceDisconnected: saves mKeyguardState.enabled before mKeyguardState.reset() and restores it afterwards. The keyguard-enabled state therefore survives the disconnect and is re-sent on reconnect by the existing !mKeyguardState.enabled branch in onServiceConnected.",
      "services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java: four new tests. Three check that the call is a no-op with an empty task list, with a DONT_LOCK task, and in LOCKED (non-pinned) mode. The fourth checks that in PINNED mode disableKeyguard, status bar disable/disable2, notifyLockTaskModeChanged and the RecentTasks notification are each invoked a second time."
     ],
     "completeness": [
      "Covers only LOCK_TASK_MODE_PINNED (screen pinning): rebuildSystemLockTaskPinnedMode returns for any other state, so full LOCK_TASK_MODE_LOCKED state is not re-applied by this change. Whether it needs to be is not shown in the material.",
      "The rebuild is driven only from the KeyguardViewMediator hunk in SystemUI. The diff adds no system_server-side detection of SystemUI death that would re-push the state. If a SystemUI restart does not reach that code path, nothing is rebuilt.",
      "Only mLockTaskModeTasks.getFirst() (get(0) on 14) is used, to supply the user id. The task list itself is not validated or modified.",
      "The KeyguardServiceDelegate change preserves only the enabled flag. All other fields of mKeyguardState are still reset on disconnect, as before.",
      "The new binder entry point is gated by enforceTaskPermission and runs under mGlobalLock, consistent with the sibling startSystemLockTaskMode method."
     ],
     "behavior_changes": [
      "New hidden IActivityTaskManager method rebuildSystemLockTaskPinnedMode() (binder interface change).",
      "When the KeyguardViewMediator path runs while the device is pinned, the pinning enter toast is shown again, the status bar is disabled again with the pinned mask, and the keyguard is disabled again. RecentTasks and task-change listeners receive a second onLockTaskModeStateChanged/notifyLockTaskModeChanged.",
      "The keyguard-enabled state now persists across a KeyguardService disconnect and is re-sent on reconnect.",
      "On a RemoteException inside the posted rebuild runnable, LockTaskController throws a RuntimeException on its handler thread."
     ],
     "preconditions": [
      "The device is in screen pinning (LOCK_TASK_MODE_PINNED) with at least one task in LockTaskController.mLockTaskModeTasks.",
      "The SystemUI process crashes or restarts while the device is pinned (per the commit title). This triggers KeyguardServiceDelegate.onServiceDisconnected and then the KeyguardViewMediator code path.",
      "rebuildSystemLockTaskPinnedMode can only be called by holders of the permission checked by enforceTaskPermission."
     ],
     "per_major": {
      "14": "Adapted fix (platform/frameworks/base 9ff1c081e9f7, android-security-14.0.0_r25). The logic is the same, but KeyguardViewMediator uses ActivityTaskManager.getService() instead of the mActivityTaskManagerService field, LockTaskController uses mLockTaskModeTasks.get(0) instead of getFirst(), and the tests use verifyNoInteractions(mSupervisor). 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix (platform/frameworks/base 08abd0bc0451, android-security-15.0.0_r13). The hunks match the 16 change, including mActivityTaskManagerService and getFirst(). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "Fix commit 92b109b60414 in android-security-16.0.0_r4 (cherry-pick of 575845147cb4, Change-Id Ia9f54b229b998e29da53436ed6ed64214903724c, RESTRICT AUTOMERGE). 8/8 patched lines present at android-security-16.0.0_r8.",
      "17": "Not affected: the bulletin does not list Android 17. The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply all five source files together: the SystemUI caller depends on the new AIDL method and on the ActivityTaskManagerService/LockTaskController implementation.",
      "On trees where KeyguardViewMediator has no mActivityTaskManagerService field, use ActivityTaskManager.getService() as the 14 variant does. Where ArrayList.getFirst() is unavailable, use mLockTaskModeTasks.get(0), also as in the 14 variant."
     ],
     "confidence": "medium",
     "unknowns": [
      "The KeyguardViewMediator method that encloses the new hunk is not shown; only that it contains the mBootSendUserPresent / sendUserPresentBroadcast handling. When exactly it runs after a SystemUI restart is therefore not confirmed.",
      "The material does not show what concretely was exposed after a SystemUI crash in pinned mode, or which restriction was lost. The bulletin only classifies it as High / ID.",
      "The material does not show how SystemUI could be made to crash in the reported scenario.",
      "The body of setLockTaskMode is not shown, so whether the rebuild matches it in full is not confirmed.",
      "Whether full LOCK_TASK_MODE_LOCKED needs equivalent handling is not addressed by the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0011": {
     "cve": "CVE-2026-0011",
     "summary": "Makes Settings.enableSystemPackageLPw able to reuse the disabled PackageSetting of a shared-UID system app when its update is uninstalled. It does this by letting addPackageLPw accept the package even when registerExistingAppId fails for shared uid, and by copying the sharedUserAppId. As a result, the preloaded version is no longer re-scanned and installed as a new app with a possibly changed UID.",
     "component": "platform/frameworks/base, PackageManagerService / Settings (system app update uninstallation, shared UID handling)",
     "weakness": "Logic error in package state restoration: the setting of a shared-UID system package could not be reused on re-enable, so the package was re-installed as a new app and its UID could change (UID/identity confusion). The bulletin rates it EoP.",
     "root_cause": "In services/core/java/com/android/server/pm/Settings.java, enableSystemPackageLPw (called when a system app's update is uninstalled) builds a new PackageSetting through addPackageLPw. addPackageLPw only stores the new setting in mPackages if the SDK-library clause holds or mAppIds.registerExistingAppId(uid, p, name) succeeds. Per the commit message, registerExistingAppId fails for shared uid, so the disabled package setting was not reused. Instead, the preloaded version was re-scanned and installed as a new app. When the manifest has sharedUserMaxSdkVersion, this can change the package's UID. The rebuilt setting also did not carry the sharedUserAppId of the disabled setting.",
     "fix_analysis": [
      "Settings.java:addPackageLPw gains a new boolean parameter, hasSharedUser. The condition for putting the new PackageSetting into mPackages becomes `(uid == INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps()) || mAppIds.registerExistingAppId(uid, p, name) || hasSharedUser`. A shared-user package is therefore accepted even when registerExistingAppId returns false.",
      "Settings.java:enableSystemPackageLPw passes p.hasSharedUser() from the disabled setting into addPackageLPw. On success it also calls ret.setSharedUserAppId(p.getSharedUserAppId()), alongside the fields it already copies (ABIs, version code, libraries, mime groups, install source, etc.). Per the commit message, this lets shared-UID system apps directly reuse the disabled setting, consistent with non-shared-UID system apps.",
      "Settings.java: the settings-reading call site of addPackageLPw (the `appId > 0` branch with the 'Reading package' debug log, around line 4266 on 16) passes `/* hasSharedUser= */ false`, which keeps its previous behavior.",
      "services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt: the Mockito stub for settings.addPackageLPw gets one more nullable() argument to match the new signature (test only)."
     ],
     "completeness": [
      "The fix covers the enableSystemPackageLPw -> addPackageLPw path for disabled system packages that have a shared user. Within the diff, this is the only call site that can pass hasSharedUser as true, and the index search found no other callers.",
      "The settings-reading call site keeps hasSharedUser=false, so packages restored from the settings file still need registerExistingAppId to succeed. That path is unchanged.",
      "The diff does not add the new PackageSetting to the SharedUserSetting's package list; it only sets sharedUserAppId on the setting. The material does not show whether the caller re-links the setting to the SharedUserSetting."
     ],
     "behavior_changes": [
      "When a shared-UID system app's update is uninstalled, enableSystemPackageLPw now returns a reused PackageSetting (with sharedUserAppId preserved). Per the commit message, the preloaded version is therefore no longer re-scanned and installed as a new app with a changed UID.",
      "The addPackageLPw signature changes (extra boolean hasSharedUser), so any out-of-tree caller or test stub must be updated."
     ],
     "preconditions": [
      "A preloaded system app that declares a shared user, optionally with sharedUserMaxSdkVersion in its manifest (per the commit message).",
      "The system app has been updated, which places the original setting in mDisabledSysPackages, and that update is then uninstalled. This triggers Settings.enableSystemPackageLPw.",
      "The material does not show who can trigger the update/uninstall sequence or what privilege is gained, beyond the bulletin's EoP / High rating."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base 77d8e59a1af6 in android-security-14.0.0_r25. Same logic (hasSharedUser parameter, `registerExistingAppId(...) || hasSharedUser`, setSharedUserAppId copy, settings-reading call site passes false), applied to the older, longer addPackageLPw signature (ABI, version code, uses-libraries, mime groups arguments), which has no isSdkLibrary/Flags.disallowSdkLibsToBeApps clause. The MockSystem.kt stub is extended accordingly. 7/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 65e7256522b4 in android-security-15.0.0_r13, with the same content as the 16 commit. 7/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 51368785bd09 in android-security-16.0.0_r4 (cherry-pick of 6b5ea2f7fbf5). The code context at android-security-16.0.0_r8 shows the patched lines (Settings.java lines 918-920 and 940; MockSystem.kt lines 170-171). Per-major facts report 7/8 lines of the patched region present.",
      "17": "not affected (not listed by the bulletin). 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit, no dependency shown. On 14, addPackageLPw has the longer legacy signature and no isSdkLibrary / Flags.disallowSdkLibsToBeApps clause. Append the hasSharedUser parameter, OR it into the registerExistingAppId condition, pass false from the settings-reading call site, and add one nullable() to the MockSystem.kt stub, otherwise the mocking tests will not compile."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact security consequence of the UID change (which privilege is gained, and by whom) is not shown. Only the bulletin's EoP / High rating and the commit message's 'breaking behavior of changed UIDs' are available.",
      "Why registerExistingAppId fails for shared uid, and what it does internally, is not shown.",
      "Whether PackageManagerService re-adds the reused PackageSetting to the SharedUserSetting's package list after enableSystemPackageLPw returns is not shown.",
      "Which line of the patched region is missing in the 7/8 match at each latest tag is not shown.",
      "No regression test is included; the commit was tested manually with system-app-test.sh."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0012": {
     "cve": "CVE-2026-0012",
     "summary": "In SystemUI's ExpandableNotificationRow.setHideSensitive, the hide-sensitive animation is now skipped (non-animated branch taken, animators cancelled) exactly when the row is showing the public/redacted layout and that state did not change, instead of being gated on Flags.skipHideSensitiveNotifAnimation() (Android 16) or never skipped unless animated == false (Android 14/15).",
     "component": "platform/frameworks/base, SystemUI notification rows (packages/SystemUI/.../statusbar/notification/row/ExpandableNotificationRow.java)",
     "weakness": "Incorrect condition controlling the hide-sensitive (redaction) animation path in a notification row (bulletin type: ID)",
     "root_cause": "ExpandableNotificationRow.setHideSensitive(hideSensitive, animated, delay, duration) computes mShowingPublic = mSensitive && hideSensitive and, if animated and not skipped, calls animateShowingPublic(delay, duration, mShowingPublic). On Android 14/15 the only way to avoid that animated path was animated == false, so a row that was not yet initialized (mShowingPublicInitialized false) but whose showing state did not change (isShowingLayoutNotChanged) still went through animateShowingPublic even when it should simply be displaying the public (redacted) layout. On Android 16 a skip existed but was conditioned on Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged, i.e. independent of mShowingPublic: when the flag was off the skip never applied, and when the flag was on the skip also applied to rows meant to show the private layout, which per the commit message is too aggressive and can cause 0 content alpha.",
     "fix_analysis": [
      "ExpandableNotificationRow.java:setHideSensitive (Android 16, commit ef2a8e203164): shouldSkipHideSensitiveAnimation is changed from `Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged` to `mShowingPublic && isShowingLayoutNotChanged`. The skip no longer depends on any flag and applies only to rows whose target state is the public layout and whose state did not change.",
      "When shouldSkipHideSensitiveAnimation is true the code takes the non-animated branch: it cancels pending animators on mPublicLayout, mPrivateLayout and mChildrenContainer and calls resetAllContentAlphas() (on 16 this reset is still gated by `!NotificationContentAlphaOptimization.isEnabled() || mShowingPublic != oldShowingPublic`, otherwise it only logs via mLogger.logSkipResetAllContentAlphas), then sets mPublicLayout visibility to VISIBLE/INVISIBLE from mShowingPublic and calls updateChildrenVisibility(). In that case animateShowingPublic is not called.",
      "ExpandableNotificationRow.java:setHideSensitive (Android 14 commit 334c15a0ebee, Android 15 commit 679801a99ccd): introduces the local isShowingLayoutNotChanged, reuses it in the early-return check, and adds `shouldSkipHideSensitiveAnimation = mShowingPublic && isShowingLayoutNotChanged`, changing `if (!animated)` to `if (!animated || shouldSkipHideSensitiveAnimation)`. These branches had no flag-gated skip before, so the fix adds the skip rather than narrowing it."
     ],
     "completeness": [
      "The change is a single condition in setHideSensitive; animateShowingPublic itself and any alpha handling inside it are unchanged.",
      "The skip only applies when mShowingPublic is true and unchanged; a row transitioning private->public (state changed) with animated == true still goes through animateShowingPublic, as before.",
      "On Android 16 the non-animated branch still skips resetAllContentAlphas() when NotificationContentAlphaOptimization is enabled and the showing state did not change; the fix relies on that existing logic and does not modify it.",
      "setHideSensitiveForIntrinsicHeight and child rows' own setHideSensitive calls are untouched; the fixed method only calls updateChildrenVisibility() for children."
     ],
     "behavior_changes": [
      "Rows already in the public/redacted state whose state does not change no longer go through animateShowingPublic on initialization; layout visibility is applied immediately and in-flight animators are cancelled.",
      "On Android 16, Flags.skipHideSensitiveNotifAnimation() is no longer consulted by this code; rows targeting the private layout with unchanged state now take the animated path when animated == true instead of being forced through the non-animated path (addresses the 0 content alpha described in the commit message)."
     ],
     "preconditions": [
      "A notification row with mSensitive == true and setHideSensitive called with hideSensitive == true (so mShowingPublic is true), animated == true, and the row not GONE.",
      "mShowingPublicInitialized == false while the computed mShowingPublic equals the previous value (isShowingLayoutNotChanged), otherwise the method returns early or takes the state-changed path.",
      "Callers of setHideSensitive are not shown in the material; the sensitive/redaction context (e.g. which UI state drives hideSensitive) is not shown."
     ],
     "per_major": {
      "14": "adapted fix: commit 334c15a0ebee in android-security-14.0.0_r25 adds isShowingLayoutNotChanged and shouldSkipHideSensitiveAnimation to a plain `if (!animated)` branch (no flag, no NotificationContentAlphaOptimization gate); 6/7 patched lines present at android-security-14.0.0_r29.",
      "15": "adapted fix: commit 679801a99ccd in android-security-15.0.0_r13, same shape as 14 (method also reads oldAlpha); 7/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit ef2a8e203164 in android-security-16.0.0_r4 replaces the Flags.skipHideSensitiveNotifAnimation() condition with mShowingPublic; 4/5 patched lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); only 2/5 lines of the patched region match at android-security-17.0.0_r1, so the code there differs and whether it contains an equivalent condition is not shown by the material."
     },
     "backport_notes": [
      "For branches without Flags.skipHideSensitiveNotifAnimation() or NotificationContentAlphaOptimization (14/15 shape), apply the 14/15 variant: add the two locals and extend `if (!animated)` to `if (!animated || shouldSkipHideSensitiveAnimation)`; for branches with the flag-gated condition (16 shape), replace the flag call with mShowingPublic. After the fix setHideSensitive no longer reads the flag."
     ],
     "confidence": "medium",
     "unknowns": [
      "What animateShowingPublic renders (e.g. whether it crossfades private and public layouts) and the exact user-visible disclosure are not shown.",
      "The default state of Flags.skipHideSensitiveNotifAnimation() on Android 16 before the fix is not shown.",
      "Callers of setHideSensitive and the UI states (e.g. lockscreen redaction) that reach the unchanged-public-uninitialized path are not shown.",
      "Whether Android 17's differing code includes an equivalent condition is not shown.",
      "Whether the aconfig flag is still defined or used elsewhere after the fix is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0013": {
     "cve": "CVE-2026-0013",
     "summary": "PickActivity.setupLayout in DocumentsUI now calls setSelector(null) on the copied \"more apps\" intent so that a selector supplied by the calling app cannot steer which activity DocumentsUI launches.",
     "component": "platform/packages/apps/DocumentsUI, picker (PickActivity / RootsFragment \"more apps\" handling)",
     "weakness": "Incomplete sanitization of a caller-controlled Intent (intent redirection / confused deputy): the selector field was not cleared when DocumentsUI copied an untrusted intent that it later reuses to show and launch other apps.",
     "root_cause": "PickActivity.setupLayout(Intent intent) builds `moreApps = new Intent(intent)` from the incoming, caller-controlled intent. It sanitizes the copy only with `setComponent(null)` and `setPackage(null)`. The copy is passed to RootsFragment.show as the intent used to show other apps that can handle the request. It is also passed to UserManagerState.getCanForwardToProfileIdMap or CrossProfileUtils.getCrossProfileResolveInfo for cross-profile resolution. An Intent's selector is a separate embedded Intent that is used for resolution when present, and the Intent copy constructor carries it over (general platform knowledge). Because the selector was not cleared, the commit message says a selector on the original intent could be used to launch an arbitrary activity with the permissions of DocumentsUI.",
     "fix_analysis": [
      "src/com/android/documentsui/picker/PickActivity.java:setupLayout adds `moreApps.setSelector(null);` immediately after `setComponent(null)` and `setPackage(null)`. As a result, the copied intent carries no caller-supplied selector by the time it reaches RootsFragment.show and the cross-profile resolution helpers (getCanForwardToProfileIdMap, getCrossProfileResolveInfo).",
      "Single commit, two-line change (one comment, one statement); no other files touched."
     ],
     "completeness": [
      "Covers the `moreApps` copy in PickActivity.setupLayout, the only place in the shown code where the incoming intent is copied for reuse. RootsFragment.show, getCanForwardToProfileIdMap and getCrossProfileResolveInfo receive the sanitized copy. The showNavRail call is cut off in the material, so its intent argument is not shown.",
      "The fix clears component, package and selector only. Other fields of the caller intent (action, data, type, categories, extras, flags, ClipData) are still copied unchanged into `moreApps`. The material does not show whether any of them matter.",
      "The original `intent` object itself is not modified. Any other use of the raw incoming intent elsewhere in DocumentsUI is outside the diff and not visible in the material."
     ],
     "behavior_changes": [
      "A selector on the intent sent to PickActivity is no longer carried into the \"more apps\" intent or cross-profile resolution. Those now resolve on the main intent's fields only.",
      "No new error, exception or user-visible message. Callers that relied on a selector to influence the app list silently lose that effect."
     ],
     "preconditions": [
      "An app starts DocumentsUI's PickActivity with an intent that carries a selector, using one of the actions in the setupLayout branch that calls RootsFragment.show (ACTION_GET_CONTENT, ACTION_OPEN, ACTION_CREATE, ACTION_OPEN_TREE or ACTION_PICK_COPY_DESTINATION).",
      "RootsFragment.show receives `moreApps` with includeApps=true only when mState.action == ACTION_GET_CONTENT. `moreApps` also goes to the cross-profile resolution helpers when mState.supportsCrossProfile is true.",
      "The material does not show whether a user interaction (selecting an app entry) is needed before the intent is launched."
     ],
     "per_major": {
      "14": "adapted fix: commit a48245c5bfa7 in android-security-14.0.0_r25 adds the same two lines in PickActivity.setupLayout. The surrounding code differs: it calls the mState.supportsCrossProfile() method and makes a single CrossProfileUtils.getCrossProfileResolveInfo(getPackageManager(), moreApps) check, with no private-space branch in the hunk. 6/6 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 15f898d5082a in android-security-15.0.0_r13, same added lines and the same context lines as the 16 commit. 6/6 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 9f2d3f09f8fd in android-security-16.0.0_r4 (cherry-pick of 32d6a7338dc3). 6/6 lines present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 5/6 lines of the patched region are present; the material does not show which line differs."
     },
     "backport_notes": [
      "The patch is self-contained: insert `moreApps.setSelector(null);` right after `moreApps.setPackage(null);` in PickActivity.setupLayout. Only the cross-profile context lines that follow differ between branches. 14 calls supportsCrossProfile() and makes a single getCrossProfileResolveInfo call; 15 and 16 have the private-space/getCanForwardToProfileIdMap branch. Anchor the hunk on the setComponent/setPackage lines."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include how RootsFragment launches the `moreApps` intent (startActivity path, flags, whether the user must select an app).",
      "Whether RootsFragment.showNavRail also receives `moreApps` (the call is cut off in the material).",
      "Which line of the patched region differs in Android 17 (5/6 lines present).",
      "No test was added (Test: manual); the material does not show a regression test."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0014": {
     "cve": "CVE-2026-0014",
     "summary": "Adds an early `return false` for SDK sandbox UIDs (Process.isSdkSandboxUid) in the AppOpsService trust helper used for attribution tag validation, so sandboxed callers are no longer treated as trusted/system.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop/AppOpsService.java)",
     "weakness": "Missing caller-type check (SDK sandbox UID not excluded from a trust decision); bulletin type is DoS",
     "root_cause": "A private helper in AppOpsService.java decides whether a uid/package pair is trusted for attribution tag validation. The hunk does not show its name; on Android 16 it is around line 5043. It returns true when packageName is null and when the appId is in the system range (appId > 0 && appId < Process.FIRST_APPLICATION_UID). It then continues with further checks that the material does not show. It had no handling for SDK sandbox UIDs, so, per the commit message, sandboxed UIDs could be treated as a \"system\" app for attribution tag validation. The bulletin classifies the issue as DoS. The downstream path that leads to the DoS is not shown.",
     "fix_analysis": [
      "services/core/java/com/android/server/appop/AppOpsService.java, trust helper for attribution tag validation (function name not in the hunk): the fix inserts `if (Process.isSdkSandboxUid(uid)) return false;` after the existing `if (packageName == null) return true;` and before the system appId range check. Any SDK sandbox UID with a non-null packageName is therefore classified as untrusted, whatever the later appId/package based logic would decide.",
      "Single commit, three added lines, no other files changed. The same lines were applied to the 14, 15 and 16 security branches."
     ],
     "completeness": [
      "The fix covers only this one helper. The sandbox check comes after the `packageName == null` early return, so a sandbox UID with a null packageName still returns true (trusted) as before.",
      "The rest of the helper (after the system appId check) is not shown, so the material does not show whether other non-sandbox caller classes could reach the same trusted result.",
      "No other AppOps validation paths are touched by the fix."
     ],
     "behavior_changes": [
      "When this helper is consulted, SDK sandbox UIDs (with a non-null packageName) are now treated as untrusted for attribution tag validation, so their attribution tags are no longer exempt from that validation.",
      "No new error codes or API changes in the diff; only the outcome of the internal trust decision changes."
     ],
     "preconditions": [
      "The caller runs in an SDK sandbox UID (Process.isSdkSandboxUid(uid) is true) and reaches the AppOps path that performs attribution tag validation through this helper, with a non-null packageName.",
      "The material does not show which public AppOps entry points call the helper."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 7320b5316d76 in android-security-14.0.0_r25, the same 3 lines at a different offset (around line 3959); 3/3 lines present at android-security-14.0.0_r29",
      "15": "same fix: platform/frameworks/base 531f3ac25912 in android-security-15.0.0_r13, the same 3 lines at around line 4718; 3/3 lines present at android-security-15.0.0_r17",
      "16": "same fix: platform/frameworks/base b51a58ecec96 in android-security-16.0.0_r4, around line 5043; 3/3 lines present at android-security-16.0.0_r8",
      "17": "not affected (not listed by the bulletin); the 3 patched lines are present at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "The hunk context is the same on 14 to 16 (anchor: `if (packageName == null) { return true; }` followed by `int appId = UserHandle.getAppId(uid);`), so it applies with only offset changes. Process.isSdkSandboxUid exists in android.os.Process since Android 13 (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The name of the patched helper method and the code after the system appId check are not shown in the material.",
      "Which AppOps binder entry points call this helper, and how the skipped validation leads to the DoS. The test name AppOpsMemoryUsageTest hints at memory growth, but the mechanism and the affected process are not shown.",
      "Why a sandbox UID was previously judged trusted. The shown appId range check alone would likely not match sandbox UIDs, so the deciding logic is probably in the part of the helper that is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0015": {
     "cve": "CVE-2026-0015",
     "summary": "In AppOpsService, an untrusted proxy (OP_FLAG_UNTRUSTED_PROXIED) can no longer get a proxied attribution tag accepted from its own manifest or through the system-proxy allowance. isPackageNullOrSystem now checks that the named package belongs to the uid. The dry-run start path and the finish path no longer create per-tag AppOps state. A new client-id based lookup lets finishes of untrusted proxied starts find their in-progress event, falling back to the null tag.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop)",
     "weakness": "Insufficient validation of caller-supplied attribution tags from untrusted proxies, allowing unbounded AppOps state growth in system_server (bulletin type: DoS)",
     "root_cause": "In AppOpsService.verifyAndGetBypass, a tag not declared in the proxied package's manifest was re-checked in the proxy package via isAttributionInPackage(proxyPkg, attributionTag) and accepted whether or not the proxy was trusted. In the ROOT_UID branch, and in a second branch that returns RestrictionBypass.UNRESTRICTED, the tag was accepted whenever isPackageNullOrSystem(proxyPackageName, proxyUid) was true. For app-range uids, that helper only called mPackageManagerInternal.isSystemPackage(packageName) and did not check that packageName belongs to the uid. Accepted tags are cached through getOpsLocked/getOpLocked with edit=true. The dry-run start path and the finish path also used edit=true with the given tag. The existing code comment says the intent was \"to prevent abusive apps clogging the appops system with unlimited attribution tags via proxy calls\". The untrusted-proxy and package-ownership gaps undermined that intent.",
     "fix_analysis": [
      "AppOpsService.java:verifyAndGetBypass (full form) gains an isProxyTrusted parameter. In the ROOT_UID branch and the UNRESTRICTED-bypass branch, isAttributionTagValid becomes isProxyTrusted && isPackageNullOrSystem(proxyPackageName, proxyUid). The proxy-manifest re-check becomes isProxyTrusted && isAttributionInPackage(proxyPkg, attributionTag). The 3-arg wrapper passes true. The former 5-arg wrapper now takes isProxyTrusted and passes suppressErrorLogs=false. checkPackage passes (..., true, true).",
      "AppOpsService.java:isPackageNullOrSystem: after the existing system-appId early return, the helper now returns false if mPackageManagerInternal.getPackageUid(packageName, PackageManager.MATCH_ALL, UserHandle.getUserId(uid)) != uid, before calling isSystemPackage.",
      "AppOpsService.java: the note path, the start path and the dry-run start path (startOperationDryRun, which takes startIfModeDefault) compute proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0 and pass it to verifyAndGetBypass. As before, an invalid tag is set to null.",
      "AppOpsService.java: the dry-run start path now calls getOpsLocked(uid, packageName, null, ..., edit=true). The Ops object may be created, but no information about the tag is cached. On Android 14 the change is dryRun ? null : attributionTag inside the start function.",
      "AppOpsService.java finish path: verifyAndGetBypass is called with isProxyTrusted=true (per the code comment, trust is unknown at that point). getOpLocked switches to edit=false. The direct mDeviceAttributedOps lookup is replaced by getAttributedOpWithClientId(op, clientId, attributionTag, virtualDeviceId, hasProxy), where hasProxy = proxyUid != Process.INVALID_UID.",
      "AppOpsService.java:getAttributedOpWithClientId: with no proxy it returns the plain lookup. With a proxy it returns the tag's AttributedOp if that has an in-progress event with a matching clientId and no OP_FLAG_UNTRUSTED_PROXIED. Otherwise, if verifyAndGetBypass(op.uid, op.packageName, attributionTag) reports the tag valid, it returns the tag's AttributedOp if that has an untrusted in-progress event for the clientId. Otherwise it returns the null-tag AttributedOp if that has an untrusted in-progress event for the clientId. Otherwise it returns null and the caller logs 'Attribution not found'.",
      "AttributedOp.java: adds hasInProgressEvent(Predicate<InProgressStartOpEvent>). It iterates mPausedInProgressEvents when paused and mInProgressEvents otherwise."
     ],
     "completeness": [
      "Covers the note, start and dry-run start paths that call verifyAndGetBypass with proxy information, plus the finish path. Trust comes from the OP_FLAG_UNTRUSTED_PROXIED bit in the flags passed in, so correctness depends on callers setting that flag for untrusted proxies. The diff does not show how that is done.",
      "Trusted proxies can still have tags from their own manifest accepted for a proxied package. The diff leaves this unchanged.",
      "isPackageNullOrSystem still returns true for any appId below FIRST_APPLICATION_UID without the new ownership check. Because of the isProxyTrusted gate, this affects only trusted proxies.",
      "The finish path still calls verifyAndGetBypass with isProxyTrusted=true. It avoids creating state through edit=false and the clientId-based lookup, not through tag validation.",
      "checkPackage only adapts to the new signature."
     ],
     "behavior_changes": [
      "Proxied note/start ops that carry OP_FLAG_UNTRUSTED_PROXIED and a tag not declared by the proxied package are now recorded under the null attribution tag. In the ROOT_UID and UNRESTRICTED-bypass branches, the tag is treated as invalid whenever the proxy is untrusted.",
      "For app-range uids, isPackageNullOrSystem now returns false when the package does not resolve to that uid in its user, so such calls no longer get the system-proxy allowance.",
      "The finish path no longer creates Op entries (edit=false). When a proxy is present, it searches by clientId and flags with a null-tag fallback. A finish whose in-progress event cannot be matched logs 'Attribution not found' and returns.",
      "Dry-run starts no longer cache per-attribution-tag information.",
      "New public method AttributedOp.hasInProgressEvent(Predicate) on the package-private AttributedOp class."
     ],
     "preconditions": [
      "A caller that reaches AppOpsService proxied note/start operations (with proxyUid/proxyPackageName) as an untrusted proxy and supplies attribution tags not declared by the proxied package, or that names a system package as proxyPackageName with a non-matching proxyUid.",
      "The affected code runs in system_server's AppOpsService. The bulletin rates the impact as DoS (High)."
     ],
     "per_major": {
      "14": "adapted fix (RESTRICT AUTOMERGE, a404da3cbbc6 in android-security-14.0.0_r25). Same logic, but dryRun is a parameter of the start function (getOpsLocked tag becomes dryRun ? null : attributionTag), there are no virtual-device maps (uses op.mAttributions), and getAttributedOpWithClientId has no virtualDeviceId parameter. The backport also removes a 'wasNull' local and adds a Consumer import in AttributedOp. Patched region present at android-security-14.0.0_r29.",
      "15": "adapted fix (22d9cd12ef99 in android-security-15.0.0_r13). Same logic as 16, but uses getPersistentId(virtualDeviceId) instead of getPersistentDeviceIdForOp, and the note path has no notedCount parameter. Patched region present at android-security-15.0.0_r17.",
      "16": "same fix (a4523e227733 in android-security-16.0.0_r4, cherry-pick of 110db0acb84c). Patched region present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). Patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. Branches differ in dry-run and per-device structure. Where dry run is a flag of the start function, use the Android 14 form (dryRun ? null : attributionTag, op.mAttributions). Where mDeviceAttributedOps exists, use the matching persistent-device helper (getPersistentId or getPersistentDeviceIdForOp). The 5-arg verifyAndGetBypass overload is replaced, so every call site must be updated for the new isProxyTrusted parameter."
     ],
     "confidence": "medium",
     "unknowns": [
      "How OP_FLAG_UNTRUSTED_PROXIED gets set for callers. The public proxy entry points are not in the diff.",
      "The exact method names of the note, start and finish paths. The diff hunks do not show them.",
      "The condition guarding the UNRESTRICTED-bypass branch of verifyAndGetBypass.",
      "The exact DoS manifestation (memory growth, persisted state size or other). The material only has the code comment about clogging appops and the bulletin type.",
      "Contents of the referenced AttributionTest."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0017": {
     "cve": "CVE-2026-0017",
     "summary": "When the new per-modality biometric settings keys (FACE_/FINGERPRINT_KEYGUARD_ENABLED, FACE_/FINGERPRINT_APP_ENABLED) are unset after an OTA, BiometricService and the Settings preference controllers now inherit the value of the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED keys instead of falling back to the hard-coded default.",
     "component": "platform/frameworks/base (services/core biometrics, BiometricService.SettingObserver) and platform/packages/apps/Settings (biometrics/face and biometrics/fingerprint preference controllers)",
     "weakness": "Insecure default during settings-key migration: a missing new key was read with a constant default (DEFAULT_KEYGUARD_ENABLED / DEFAULT_APP_ENABLED in the service, DEFAULT = ON in Settings), discarding the user's previously stored choice in the legacy key.",
     "root_cause": "In BiometricService.SettingObserver, the uri-keyed settings change handler read Settings.Secure.FACE_KEYGUARD_ENABLED, FINGERPRINT_KEYGUARD_ENABLED, FACE_APP_ENABLED and FINGERPRINT_APP_ENABLED with the fixed default DEFAULT_KEYGUARD_ENABLED ? 1 : 0 or DEFAULT_APP_ENABLED ? 1 : 0. The four Settings controllers (FaceSettingsAppsPreferenceController, FaceSettingsKeyguardUnlockPreferenceController, FingerprintSettingsAppsPreferenceController, FingerprintSettingsKeyguardUnlockPreferenceController) also read them with DEFAULT = ON. On a device updated by OTA where only the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED keys were set, the new keys are absent. A user who had turned biometric keyguard unlock (or app use) off then had it treated as the default after the update. This affected both the service state (mFaceEnabledOnKeyguard, mFingerprintEnabledOnKeyguard, mFaceEnabledForApps, mFingerprintEnabledForApps) and the Settings UI toggle. The commit title describes this as 'Unlock your phone' unexpectedly turned ON after OTA.",
     "fix_analysis": [
      "platform/frameworks/base cea235f00865, services/core/java/com/android/server/biometrics/BiometricService.java, SettingObserver uri-keyed change handler: in the FACE_KEYGUARD_ENABLED and FINGERPRINT_KEYGUARD_ENABLED branches, the code first reads Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED with default -1. defaultValue is DEFAULT_KEYGUARD_ENABLED if the legacy key is unset (-1), otherwise (legacy == 1). That defaultValue replaces the constant default when the new key is read into mFaceEnabledOnKeyguard / mFingerprintEnabledOnKeyguard.",
      "Same file, FACE_APP_ENABLED and FINGERPRINT_APP_ENABLED branches: the code reads Settings.Secure.BIOMETRIC_APP_ENABLED with default -1 and derives defaultValue the same way (DEFAULT_APP_ENABLED if unset, else legacy == 1) before reading the new key into mFaceEnabledForApps / mFingerprintEnabledForApps. The service change only affects reads; it does not write the new keys.",
      "platform/packages/apps/Settings 7d8fbee887fc, constructors of FaceSettingsAppsPreferenceController, FaceSettingsKeyguardUnlockPreferenceController, FingerprintSettingsAppsPreferenceController and FingerprintSettingsKeyguardUnlockPreferenceController: the fix adds NOT_SET = -1 and reads the new key and the matching legacy key (BIOMETRIC_APP_ENABLED or BIOMETRIC_KEYGUARD_ENABLED) with default NOT_SET. If the new key is NOT_SET and the legacy key is not, Settings.Secure.putIntForUser writes the legacy value into the new key for getUserId(). isChecked() is only reformatted and still reads the new key with DEFAULT = ON.",
      "Tests: BiometricServiceTest gains four tests (gated on com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) asserting getEnabledForApps / getEnabledOnKeyguard return false when the legacy key is 0 and the new key is unset. Four new Robolectric controller tests assert isChecked() is false in the same situation and true when the new key is explicitly 1."
     ],
     "completeness": [
      "All four new per-modality keys are handled in both the service observer and the Settings controllers, each mapped to its matching legacy key (keyguard -> BIOMETRIC_KEYGUARD_ENABLED, app -> BIOMETRIC_APP_ENABLED). The comments in the two keyguard branches of BiometricService mention BIOMETRIC_APP_ENABLED but the code reads BIOMETRIC_KEYGUARD_ENABLED, so the code is correct and only the comment is wrong.",
      "The BiometricService change applies only inside the SettingObserver change handler for those URIs. The material does not show whether the maps are first filled at service start through this same handler.",
      "The service side never persists the migrated value. Persistence happens only when one of the four Settings preference controllers is constructed. Until then the service relies on the default it computes each time the handler reads the key.",
      "In BiometricService the legacy value is mapped with 'biometricKeyguardEnabled == 1' / 'biometricAppEnabled == 1', so any legacy value other than 1 (not only 0) yields disabled. The Settings controllers copy the raw legacy int instead.",
      "The Settings controllers use -1 as the sentinel for 'not set', so a stored explicit -1 cannot be told apart from an absent key. The new tests rely on exactly this by writing -1."
     ],
     "behavior_changes": [
      "BiometricService: on devices where FACE_/FINGERPRINT_KEYGUARD_ENABLED or FACE_/FINGERPRINT_APP_ENABLED is unset, getEnabledOnKeyguard / getEnabledForApps now follow the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED value instead of the constant default.",
      "Settings app: constructing any of the four face/fingerprint toggle controllers now does a one-time Settings.Secure.putIntForUser of the new key when it is unset and the legacy key is set. The new key is then persisted and the toggle shows the pre-OTA choice.",
      "No change when the new keys are already set, or when neither the legacy nor the new key exists (the hard-coded defaults still apply)."
     ],
     "preconditions": [
      "Device updated (OTA) to a build using the new per-modality keys, where the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED keys were set (for example to off) and the new keys are unset for that user.",
      "The BiometricService.SettingObserver change handler runs for the new-key URIs. The Settings path runs when one of the four controllers is constructed. Index search lists FaceSettings.onCreate among related users, but the snippet does not show it constructing the controller.",
      "The frameworks/base tests are gated on com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION; the production hunks shown are not themselves flag-gated."
     ],
     "per_major": {
      "14": "not affected per the bulletin; at android-security-14.0.0_r29 only 3/8 lines of the patched region are present.",
      "15": "not affected per the bulletin; at android-security-15.0.0_r17 only 3/8 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base cea235f00865 and platform/packages/apps/Settings 7d8fbee887fc in android-security-16.0.0_r4 (cherry-picks of a3799e443e9f and 05f0884146a0); 8/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); 8/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply both commits together. The frameworks/base change gives the service the correct default when it reads the keys; the Settings change persists the migration. In Settings, insert the migration block after super(context, key). Constructor bodies may differ between branches: the android-16.0.0_r4 copy of FaceSettingsAppsPreferenceController in the index has a sensor-strength loop setting mIsAllowed that the security-branch diff context does not show. Expect context conflicts."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether BiometricService fills mFaceEnabledOnKeyguard and the other maps at startup through the same change handler, or through a separate initial read that would still use the constant default.",
      "The exact name of the enclosing SettingObserver method; the hunks show only its uri-keyed branches.",
      "The values of DEFAULT_KEYGUARD_ENABLED and DEFAULT_APP_ENABLED in BiometricService.",
      "Whether any other migration (for example a SettingsProvider upgrade step) writes the new per-modality keys during OTA; the material shows none.",
      "When setUserId() is called on the controllers relative to construction. The migration uses getUserId() in the constructor, and per index search FaceSettingsPreferenceController.getUserId() returns mUserId set by setUserId(); the ordering is not shown.",
      "Which Settings screens construct the four controllers, and when."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0020": {
     "cve": "CVE-2026-0020",
     "summary": "The fix makes the manifest parser trim leading and trailing whitespace from the android:name of <permission>, <permission-tree> and <permission-group> declarations in ParsedPermissionUtils.",
     "component": "platform/frameworks/base, package manager manifest parsing (ParsedPermissionUtils, permission / permission-tree / permission-group parsing)",
     "weakness": "Missing input normalization: manifest-declared permission and permission-group names were accepted verbatim, including surrounding whitespace",
     "root_cause": "In core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java, parsePermission, parsePermissionTree and parsePermissionGroup did not trim the parsed name before storing it. Names with leading or trailing whitespace were therefore kept as declared. Judging from the fix, names that differ from another name only by whitespace could be treated as distinct downstream. The bulletin rates the issue as a High severity EoP; the material does not describe the concrete abuse path.",
     "fix_analysis": [
      "ParsedPermissionUtils.java:parsePermission: adds permission.setName(permission.getName().trim()) after the attribute block (knownCerts and restricted-flag handling) and before PermissionInfo.fixProtectionLevel.",
      "ParsedPermissionUtils.java:parsePermissionTree: on 15/16, adds permission.setName(permission.getName().trim()) after sa.recycle() and before the indexOf('.') check, so that check now runs on the trimmed name.",
      "ParsedPermissionUtils.java:parsePermissionGroup: appends .setName(permissionGroup.getName().trim()) to the builder chain after .setFlags(...).",
      "Single commit touching one file; Flag: EXEMPT CVE_FIX; test: atest AppSecurityTests."
     ],
     "completeness": [
      "Covers the three declaration types parsed in ParsedPermissionUtils: <permission>, <permission-tree> and <permission-group>.",
      "Only String.trim() is applied. It removes characters at or below U+0020 from both ends of the name. It does not handle other forms of name confusion (interior or Unicode whitespace, case), and the material does not show these as in scope.",
      "The trim happens late in each parse function. Any name-based handling earlier in the function still sees the untrimmed value; for example, the restricted-flag exception message in parsePermission uses getName() before the trim. The material does not show what other earlier code does with the name.",
      "This commit does not touch <uses-permission> names or other component names; whether they are normalized elsewhere is not shown."
     ],
     "behavior_changes": [
      "Permission, permission-tree and permission-group names declared with leading or trailing whitespace are now stored as their trimmed value instead of the literal manifest string.",
      "On 15/16, the permission-tree indexOf('.') checks run on the trimmed name, so the result of that check can change for names that have surrounding whitespace.",
      "The diff adds no new error codes or APIs."
     ],
     "preconditions": [
      "A package is parsed whose AndroidManifest declares a <permission>, <permission-tree> or <permission-group> element with an android:name that has leading or trailing whitespace.",
      "The code runs in the package manager manifest parser (ParsedPermissionUtils) during package parsing."
     ],
     "per_major": {
      "14": "adapted fix (RESTRICT AUTOMERGE, d7a73fb8bfc2 in android-security-14.0.0_r25): the same three trims, but in services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java and without the added blank lines. In parsePermissionTree the trim sits after a block ending in an error message that references permission.getName(), and before setProtectionLevel(PROTECTION_NORMAL).setTree(true). 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix (4fa32d4fef90 in android-security-15.0.0_r13): identical hunks in core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix (e770e9f02341 in android-security-16.0.0_r4, cherry-pick of 595cf99ecd42). 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); 8/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The file sits at different paths: services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java on 14 and core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java on 15+. The hunks are small and self-contained (three setName(...trim()) calls).",
      "In parsePermissionTree, the trim is placed differently on 14 and on 15/16. On 15/16 it comes before the indexOf('.') check. On 14 it comes after a validation block and before setProtectionLevel. Check which name-based checks run before the trim in your tree."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show which consumer of permission or permission-group names (for example registration or lookup) was confused by names that differ only by whitespace, so the concrete EoP path is unknown.",
      "Whether code earlier in each parse function (before the trim line) validates or uses the untrimmed name is not fully shown.",
      "The full permission-tree validation logic and its error path on 14 and 15/16 are not fully shown.",
      "Why only 7/8 lines of the patched region match on android-security-14.0.0_r29 is not explained by the material.",
      "Whether <uses-permission> names are normalized elsewhere is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0021": {
     "cve": "CVE-2026-0021",
     "summary": "On multi-pane (activity-embedding) devices, AppInfoBase now checks INTERACT_ACROSS_USERS_FULL against the package returned by SettingsActivity.getInitialCallingPackage() instead of using the getLaunchedFromUid()-based check, and the AppUsageAccessSettingsActivity intent-filter gains android:priority=\"1\".",
     "component": "platform/packages/apps/Settings, applications/AppInfoBase (app info pages such as UsageAccessDetails) and AndroidManifest.xml",
     "weakness": "Missing permission check on the calling package on multi-pane devices (EoP per bulletin)",
     "root_cause": "In AppInfoBase, the caller-permission check runs after verifying that the activity is a SettingsActivity. It called ActivityManager.getService().getLaunchedFromUid(activity.getActivityToken()) and evaluated that UID. It had no separate handling for devices with Settings activity embedding (multi-pane) enabled. The fix adds a package-based INTERACT_ACROSS_USERS_FULL check for that case, which implies the UID-based check was not sufficient on multi-pane devices. The material does not show exactly why it fell short.",
     "fix_analysis": [
      "src/com/android/settings/applications/AppInfoBase.java: in the caller-permission check, after the `activity instanceof SettingsActivity` test, a new branch is taken when ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) is true. It reads ((SettingsActivity) activity).getInitialCallingPackage(). If that is empty, it logs and returns false (deny). Otherwise it requires mPm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) == PERMISSION_GRANTED and returns true or false accordingly. The existing getLaunchedFromUid path is now reached only when embedding is not enabled.",
      "src/com/android/settings/applications/AppInfoBase.java: adds import com.android.settings.activityembedding.ActivityEmbeddingUtils.",
      "AndroidManifest.xml: the <intent-filter> of Settings$AppUsageAccessSettingsActivity (action android.settings.USAGE_ACCESS_SETTINGS with data scheme package, fragment UsageAccessDetails) changes from no priority to android:priority=\"1\". This matches the priority already declared on the sibling Settings$UsageAccessSettingsActivity filter. The material does not state the rationale."
     ],
     "completeness": [
      "The new package-based check applies only when ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) is true. On non-embedding devices the original getLaunchedFromUid-based check is unchanged.",
      "The check lives in AppInfoBase, so it applies to fragments deriving from AppInfoBase that use this method (e.g. UsageAccessDetails per the manifest meta-data). Fragments not derived from AppInfoBase that do their own caller checks are not touched by this commit.",
      "On embedding devices, the check denies when the initial calling package is unavailable (fail closed) instead of falling back to the UID-based check."
     ],
     "behavior_changes": [
      "On multi-pane devices, the AppInfoBase check now denies when SettingsActivity.getInitialCallingPackage() is empty and logs Log.w 'Not able to get calling package name for permission check'.",
      "On multi-pane devices, callers whose package lacks INTERACT_ACROSS_USERS_FULL are denied with a Log.w naming the package and the permission. Before the fix, the decision depended on the launching UID.",
      "Intent resolution for android.settings.USAGE_ACCESS_SETTINGS with a package: data URI now sees Settings$AppUsageAccessSettingsActivity at priority 1 instead of the default priority 0."
     ],
     "preconditions": [
      "A device with Settings activity embedding (multi-pane) enabled, i.e. ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) returns true.",
      "An exported Settings activity hosting an AppInfoBase-derived fragment is started, e.g. Settings$AppUsageAccessSettingsActivity (exported, android.settings.USAGE_ACCESS_SETTINGS with scheme package), which loads com.android.settings.applications.UsageAccessDetails per the manifest.",
      "The exact method name, and the arguments (e.g. a target user id) that make the check matter, are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 19171d5c31ad in android-security-14.0.0_r25, identical diff (manifest hunk at line 2124, AppInfoBase hunk at line 174); 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 298fd46325a8 in android-security-15.0.0_r13, identical diff (manifest hunk at line 2300); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 48af8a13dd12 in android-security-16.0.0_r4 (cherry-pick of bd4d57ade077); 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17; the patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. It depends on com.android.settings.activityembedding.ActivityEmbeddingUtils.isEmbeddingActivityEnabled(Activity), SettingsActivity.getInitialCallingPackage(), and the existing mPm PackageManager field in AppInfoBase. The identical diff applied on 14, 15 and 16. A tree lacking getInitialCallingPackage() would need that helper first.",
      "OEM trees that customize the Settings$AppUsageAccessSettingsActivity manifest entry should still add android:priority=\"1\" to its USAGE_ACCESS_SETTINGS intent-filter, because this is part of the upstream fix."
     ],
     "confidence": "medium",
     "unknowns": [
      "Why the getLaunchedFromUid-based check was not sufficient on multi-pane devices is not shown.",
      "The name and callers of the AppInfoBase method containing the patched check, and which arguments (e.g. target user) it protects, are not shown in the material.",
      "How SettingsActivity.getInitialCallingPackage() is populated, and whether the launching app can influence it, is not shown.",
      "Which user's package state mPm.checkPermission(permission, packageName) evaluates in this context is not shown.",
      "The reason for adding android:priority=\"1\" to the AppUsageAccessSettingsActivity intent-filter is not stated in the commit or the material.",
      "Whether other Settings fragments outside AppInfoBase perform a similar getLaunchedFromUid-based check is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0023": {
     "cve": "CVE-2026-0023",
     "summary": "PackageInstallerService now unconditionally clears PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE from caller-supplied SessionParams.installFlags during install session creation, before re-adding it only when DevicePolicyManagerInternal.isUserOrganizationManaged(userId) is true.",
     "component": "platform/frameworks/base, PackageManager / PackageInstallerService (install session creation)",
     "weakness": "Missing sanitization of a caller-controlled, system-only flag (improper input validation / trust of client-supplied install flags)",
     "root_cause": "In the session-creation path of services/core/java/com/android/server/pm/PackageInstallerService.java, the server only ORed INSTALL_FROM_MANAGED_USER_OR_PROFILE into params.installFlags when DevicePolicyManagerInternal reported the target user as organization managed. It never cleared the bit first, so a value already present in the caller-provided SessionParams.installFlags was carried through into the PackageInstallerSession. An app could thus make a session appear to originate from a managed user or profile even when it did not, which the commit message says leads to unexpected behaviour (the bulletin rates this as a High severity EoP).",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java, session creation path (after the forceQueryableOverride reset and before the PackageInstallerSession is constructed): adds `params.installFlags &= ~PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE;` immediately after obtaining the DevicePolicyManagerInternal service.",
      "The existing conditional `if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) { params.installFlags |= INSTALL_FROM_MANAGED_USER_OR_PROFILE; }` is unchanged, so on this path the flag is now set only when the system decides it should be, regardless of what the caller passed.",
      "Pattern matches the adjacent sanitization already present in the same method for forceQueryableOverride (reset unless root/shell) and INSTALL_REQUEST_UPDATE_OWNERSHIP (cleared for APEX or when ENFORCE_UPDATE_OWNERSHIP is denied).",
      "Regression test named in commit message: PackageManagerShellCommandInstallTest#testSessionCreationWithManagedUserOrProfileFlag_notFromManagedProfile (test file not included in the shown diff)."
     ],
     "completeness": [
      "Covers the handling of INSTALL_FROM_MANAGED_USER_OR_PROFILE in the PackageInstallerService session-creation path; after the fix the bit on this path is derived solely from DevicePolicyManagerInternal.isUserOrganizationManaged(userId).",
      "The fix is a 2-line insertion; no other sites that read or copy installFlags (e.g. session restoration from persisted state, other install entry points) are touched or shown in the material, so whether the flag can enter by another route is not determinable from the material.",
      "Other caller-settable installFlags bits are not addressed by this change; only INSTALL_FROM_MANAGED_USER_OR_PROFILE is newly masked."
     ],
     "behavior_changes": [
      "Any INSTALL_FROM_MANAGED_USER_OR_PROFILE bit supplied by a client in SessionParams.installFlags is silently discarded at session creation; no error is returned.",
      "The flag is still set by the server when the target user is organization managed, so behaviour on managed users/profiles is unchanged."
     ],
     "preconditions": [
      "Caller can create a PackageInstaller session and controls the SessionParams passed to it, including the installFlags field.",
      "Target userId is not organization managed (otherwise the server would set the flag anyway).",
      "Code path: PackageInstallerService session creation, reached before the PackageInstallerSession object is constructed."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base a9550d0a75d0 in android-security-14.0.0_r25, identical 2-line insertion at a different offset (hunk at line ~941); 3/3 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 221ac26392cd in android-security-15.0.0_r13, identical 2-line insertion (hunk at line ~1016); 3/3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 09055276288a in android-security-16.0.0_r4 (hunk at line ~1027); 3/3 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; the patched lines are already present (3/3) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit change: insert the mask line immediately before the `if (dpmi != null && dpmi.isUserOrganizationManaged(userId))` block in PackageInstallerService; it only requires that this DevicePolicyManagerInternal-based conditional and PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE already exist in the target tree."
     ],
     "confidence": "high",
     "unknowns": [
      "What downstream behaviour INSTALL_FROM_MANAGED_USER_OR_PROFILE controls is not shown in the material; the commit only says 'unexpected behaviour'.",
      "Which permissions a caller needs to create a session and set installFlags is not shown in the material.",
      "The enclosing method name is not shown in the hunk header (only the class declaration); the context indicates the session-creation path.",
      "Whether installFlags can reach a PackageInstallerSession through any path other than this creation routine (e.g. persisted session restore) is not shown.",
      "The added test's contents are not included in the diff."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0024": {
     "cve": "CVE-2026-0024",
     "summary": "MediaProvider now forces redaction when a file is opened via the ContentResolver open path with an EXTRA_MEDIA_CAPABILITIES_UID whose identity holds PERMISSION_IS_REDACTION_NEEDED, so a picker acting on behalf of another app respects that app's redaction requirements (ACCESS_MEDIA_LOCATION bypass per the commit message).",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java file open / redaction decision (ContentResolver open path)",
     "weakness": "Missing permission check on the delegated (on-behalf-of) UID in the redaction decision (confused deputy)",
     "root_cause": "In src/com/android/providers/media/MediaProvider.java, the ContentResolver open path decided whether to redact a media file with isRedactionNeededForOpenViaContentResolver(redactedUri, ownerPackageName, file). That call did not receive the Bundle opts passed to open, and the visible part of the function decides from the direct caller (getCallingPackageOrSelf() compared with the owner). When a picker opened media on behalf of another app and supplied MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, the redaction requirements of that target UID were never checked. Per the commit message, this allowed a bypass of ACCESS_MEDIA_LOCATION that returned unredacted location data. The bulletin classes it as information disclosure (ID), severity High.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java: the open path (around line 10404 at android-security-16.0.0_r8) now passes the Bundle opts into isRedactionNeededForOpenViaContentResolver(redactedUri, ownerPackageName, file, opts).",
      "src/com/android/providers/media/MediaProvider.java:isRedactionNeededForOpenViaContentResolver gains a Bundle opts parameter. The new check runs after the existing 'redactedUri != null -> true' check and before the callerIsOwner check. It reads opts.getInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID). If the value is > 0, it builds LocalCallingIdentity.fromExternal(getContext(), mUserCache, mediaCapabilitiesUid, null, null). If identity.hasPermission(PERMISSION_IS_REDACTION_NEEDED) is true, the function returns true (redact).",
      "Because the new check comes before the callerIsOwner shortcut, a caller that owns the file but acts for a UID that requires redaction still gets redacted output. The existing redactionRanges computation and the getRequireOriginal() UnsupportedOperationException check then apply unchanged to the new redactionNeeded value."
     ],
     "completeness": [
      "Covers: the ContentResolver open path that calls isRedactionNeededForOpenViaContentResolver, for callers that put EXTRA_MEDIA_CAPABILITIES_UID in opts.",
      "Leaves unchanged: when opts is null, the extra is absent, or the UID is <= 0, the decision falls through to the existing caller-based logic (callerIsOwner and whatever follows, which the diff does not show).",
      "The fix only adds a 'force redact' outcome. It never makes a file unredacted based on the delegated UID.",
      "The diff does not show any validation of who may set EXTRA_MEDIA_CAPABILITIES_UID, or whether other open paths (e.g. FUSE-based opens) read this extra. Both are outside the material.",
      "No test is included (Test: manual)."
     ],
     "behavior_changes": [
      "Opens that carry EXTRA_MEDIA_CAPABILITIES_UID for a UID holding PERMISSION_IS_REDACTION_NEEDED now return redacted content, even if the direct caller owns the file.",
      "For such opens, a URI with MediaStore.getRequireOriginal() set now throws UnsupportedOperationException ('Caller must hold ACCESS_MEDIA_LOCATION permission to access original') where it could previously succeed.",
      "One extra LocalCallingIdentity.fromExternal lookup runs per open when the extra is present."
     ],
     "preconditions": [
      "A media file is opened through MediaProvider's ContentResolver open path, without a redacted URI.",
      "The direct caller (e.g. a SAF picker) passes MediaStore.EXTRA_MEDIA_CAPABILITIES_UID > 0 in the open Bundle, identifying another app.",
      "The identified UID holds PERMISSION_IS_REDACTION_NEEDED (related to ACCESS_MEDIA_LOCATION per the commit message), while the direct caller would otherwise get unredacted access."
     ],
     "per_major": {
      "14": "adapted fix: commit 50387ce9d85a in android-security-14.0.0_r25. The logic is identical; only the context differs (RedactionInfo/getRedactionRanges instead of RedactionUtils.getRedactionRanges, line offsets ~8759/8878). At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "same fix: commit 77f9bd12a6ed in android-security-15.0.0_r13, identical hunks at ~9459/9578. At android-security-15.0.0_r17, 6/8 lines of the patched region are present.",
      "16": "same fix: commit 69a25763cdb4 in android-security-16.0.0_r4 (cherry-pick of e5e47f93838e). At android-security-16.0.0_r8, 6/8 lines of the patched region are present.",
      "17": "not affected: Android 17 is not listed by the bulletin. At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single, self-contained commit. It relies on existing symbols that must exist in the target branch: LocalCallingIdentity.fromExternal (called as fromExternal(getContext(), mUserCache, uid, null, null)), PERMISSION_IS_REDACTION_NEEDED, mUserCache and MediaStore.EXTRA_MEDIA_CAPABILITIES_UID. Only the surrounding redaction-range type differs (RedactionInfo on 14 vs long[] on 15/16)."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of isRedactionNeededForOpenViaContentResolver after the callerIsOwner check is not shown, so the exact pre-fix caller-based decision is not visible.",
      "The material does not show whether MediaProvider checks that the direct caller is allowed to assert EXTRA_MEDIA_CAPABILITIES_UID, or whether other open paths (e.g. FUSE) handle this extra.",
      "Beyond the commit message's reference to the SAF picker, the material does not show which pickers set EXTRA_MEDIA_CAPABILITIES_UID.",
      "The material does not show whether this fix is also delivered through a MediaProvider mainline module update.",
      "The material does not explain why only 6 of the 8 patched-region lines are found in each latest tag."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0025": {
     "cve": "CVE-2026-0025",
     "summary": "Notification.java now reads EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES through getParcelableArrayFromBundle(extras, KEY, Bundle.class) instead of extras.getParcelableArray(KEY, Parcelable.class), in visitUris, in hasImage and in the MessagingStyle code that restores mMessages/mHistoricMessages from extras. The message arrays are therefore read as Bundle-typed arrays before being passed to MessagingStyle.Message.getMessagesFromBundleArray.",
     "component": "platform/frameworks/base, core/java/android/app/Notification.java (MessagingStyle extras parsing, including Notification.visitUris used for URI permission checks)",
     "weakness": "Improper input validation: Parcelable arrays read with a loose element type (Parcelable.class) and then interpreted as Bundle message entries",
     "root_cause": "In core/java/android/app/Notification.java, five reads across three methods obtained the message arrays with extras.getParcelableArray(key, Parcelable.class), which does not restrict elements to Bundle, and then passed the arrays to MessagingStyle.Message.getMessagesFromBundleArray: visitUris (EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES), hasImage (EXTRA_MESSAGES) and the MessagingStyle code that fills mMessages/mHistoricMessages (both keys). As a result, an app-supplied array that mixed Bundle message entries with a non-Bundle Parcelable was not rejected when it was read. The commit message mentions class cast exceptions. The new test testNoUriGrantsForBadMessagesList adds a crafted non-Bundle element to EXTRA_MESSAGES (its comment says this is to avoid visitUri checking URIs in EXTRA_MESSAGES) and checks that checkGrantUriPermission is never called for the contact photo URI. The bulletin classifies the issue as information disclosure (ID), High.",
     "fix_analysis": [
      "core/java/android/app/Notification.java:visitUris: EXTRA_MESSAGES is now read as Bundle[] via getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class). EXTRA_HISTORIC_MESSAGES is read via getParcelableArrayFromBundle(extras, EXTRA_HISTORIC_MESSAGES, Bundle.class), with the local variable still typed Parcelable[]. Both results still go through ArrayUtils.isEmpty and MessagingStyle.Message.getMessagesFromBundleArray, then message.visitUris(visitor).",
      "core/java/android/app/Notification.java:hasImage: the same replacement for EXTRA_MESSAGES, before getMessagesFromBundleArray.",
      "core/java/android/app/Notification.java, MessagingStyle code that restores state from extras (the method name is not shown in the diff hunk): the same replacement for EXTRA_MESSAGES and EXTRA_HISTORIC_MESSAGES, before mMessages and mHistoricMessages are built.",
      "Effect per the new test: when EXTRA_MESSAGES contains a non-Bundle element, enqueueNotificationWithTag throws java.lang.ArrayStoreException, and mUgmInternal.checkGrantUriPermission is not called for the target URI.",
      "services/tests/uiservicestests/.../NotificationManagerServiceTest.java: adds testNoUriGrantsForBadMessagesList with a crafted Parcelable (MyParceledListSlice). It also switches existing tests to the static imports of EXTRA_MESSAGES and EXTRA_MESSAGING_PERSON."
     ],
     "completeness": [
      "Covers every EXTRA_MESSAGES / EXTRA_HISTORIC_MESSAGES read that appears in the diff: visitUris (both keys), hasImage (EXTRA_MESSAGES) and the MessagingStyle restore code (both keys).",
      "In the visitUris context shown, the other array extras are already strongly typed: EXTRA_PEOPLE_LIST with Person.class and EXTRA_REMOTE_INPUT_HISTORY_ITEMS with RemoteInputHistoryItem.class.",
      "The commit does not modify MessagingStyle.Message.getMessagesFromBundleArray itself.",
      "The added test method testNoUriGrantsForBadMessagesList has no @Test annotation in any of the three branch diffs, so the material does not establish whether the test actually runs."
     ],
     "behavior_changes": [
      "When a Notification's EXTRA_MESSAGES array contains a non-Bundle Parcelable, enqueueNotificationWithTag now throws an ArrayStoreException (the test expects this).",
      "Well-formed MessagingStyle notifications, whose message arrays contain only Bundles, are expected to behave as before.",
      "Per the commit message, there are fewer class cast exceptions."
     ],
     "preconditions": [
      "An app posts a Notification whose MessagingStyle extras (EXTRA_MESSAGES and/or EXTRA_HISTORIC_MESSAGES) contain an array with at least one non-Bundle element. The test does this via mBinderService.enqueueNotificationWithTag.",
      "The code is reached through Notification.visitUris (the URI permission path the test exercises), through hasImage, or when MessagingStyle is restored from extras."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 5e1623a880d2 in android-security-14.0.0_r25. The Notification.java changes are identical at the same sites, at different line offsets. The test is added with a smaller import set. At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/base 5ffc92d00244 in android-security-15.0.0_r13, with identical Notification.java changes. At android-security-15.0.0_r17, 6/8 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base 014dea279c49 in android-security-16.0.0_r4 (cherry-pick of 71d4afae00c7, Change-Id I3022e010de95f14dcd0d09d123684ee265101e0a). At android-security-16.0.0_r8, 6/8 lines of the patched region are present; the code context shows the getParcelableArrayFromBundle calls in visitUris.",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-commit, mechanical change: replace extras.getParcelableArray(KEY, Parcelable.class) with getParcelableArrayFromBundle(extras, KEY, Bundle.class) at the five reads in Notification.java. The change depends on the getParcelableArrayFromBundle helper existing on the target branch, which the material does not show; check this before backporting."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not include the implementations of getParcelableArrayFromBundle and MessagingStyle.Message.getMessagesFromBundleArray. The pre-fix failure mode, and how far visitUris progressed with a mixed array, are therefore not shown.",
      "The material does not show what information was disclosed or through which consumer; the bulletin gives only type ID, High.",
      "The method name of the MessagingStyle restore site is not shown in the diff hunk.",
      "The material does not explain why only 6/8 lines of the patched region match at the latest tags."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0034": {
     "cve": "CVE-2026-0034",
     "summary": "Caps the per-user lists of approved and user-set managed services (notification listeners, assistants, condition providers) in ManagedServices at 100 entries. setPackageOrComponentEnabled now reports whether the change was applied, and NotificationManagerService skips follow-up actions (secondary approvals, broadcasts, policy save) when it was not.",
     "component": "platform/frameworks/base, system_server notification service (com.android.server.notification: ManagedServices, NotificationManagerService)",
     "weakness": "Missing limit on growth of a per-user approved-services list (allocation/binding without limits)",
     "root_cause": "ManagedServices.setPackageOrComponentEnabled always added the package or component to the per-user `approved` set in mApproved and to mUserSetServices, then called rebindServices. Nothing limited how many entries a user could accumulate. The new constant's comment says the approved list effectively sets the number of services bound per user, so before the fix the number of bound managed services per user had no limit. The method returned void, so callers in NotificationManagerService could not tell whether an approval took effect. They always went on to the follow-up actions: the secondary approval in mConditionProviders, the ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED and ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED broadcasts, and handleSavePolicyFile.",
     "fix_analysis": [
      "ManagedServices.java: adds `private static final int MAX_SERVICE_ENTRIES = 100`, documented as the maximum number of entries per user in the mApproved or mUserSetServices lists.",
      "ManagedServices.java:setPackageOrComponentEnabled (both the 4-arg and 5-arg overloads) now returns boolean. The 5-arg body introduces a `changed` flag. When enabling, the item is added to `approved` only if `approved.size() < MAX_SERVICE_ENTRIES`. Otherwise it logs Slog.w 'Failed to allow ... because there are too many already' and adds nothing. When disabling, the item is removed and changed is set to true.",
      "ManagedServices.java:setPackageOrComponentEnabled: the mUserSetServices update moves inside `if (changed)`. Adding to userSetServices is also gated by `userSetServices.size() < MAX_SERVICE_ENTRIES` and is silently skipped when the list is full; removal is unconditional. rebindServices(false, userId) is called only when changed, and `changed` is returned.",
      "NotificationManagerService.java:setNotificationPolicyAccessGrantedForUser: keeps the boolean returned by mConditionProviders.setPackageOrComponentEnabled and returns early when it is false. In that case there is no ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED broadcast and no handleSavePolicyFile.",
      "NotificationManagerService.java, listener access grant path (exercised by the test through setNotificationListenerAccessGrantedForUser): the calls are reordered so mListeners.setPackageOrComponentEnabled(listener.flattenToString(), userId, true, granted, userSet) runs first. If it returns false, the method returns before the secondary mConditionProviders.setPackageOrComponentEnabled(..., false, ...) call and before the broadcast.",
      "NotificationManagerService.java, NotificationAssistants setPackageOrComponentEnabled override: now returns boolean. It returns false when the requested component is already the single enabled one; otherwise it returns the super result.",
      "NotificationManagerService.java, NotificationListeners setPackageOrComponentEnabled override: now returns boolean. If super returned false, it returns false immediately, skipping the redactSensitiveNotificationsFromUntrustedListeners trust update and the ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED broadcast. Otherwise it returns true.",
      "Tests: ManagedServicesTest adds setPackageOrComponentEnabled_tooManyPackages_stopsAdding and setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet. NotificationManagerServiceTest stubs the mocked setPackageOrComponentEnabled calls to return true and adds testSetListenerAccessForUser_tooManyListeners_skipsFollowups."
     ],
     "completeness": [
      "The cap is enforced only in ManagedServices.setPackageOrComponentEnabled. Any other code that fills mApproved (for example XML restore or default-service loading) is not in the diff and is not shown to be bounded. The material does not show whether such paths exist.",
      "The limit is per user and per ManagedServices instance: listeners, assistants and condition providers each get their own 100-entry cap. There is no global cap.",
      "In the listener grant path, the return value of the secondary mConditionProviders.setPackageOrComponentEnabled(..., isPrimary=false, ...) call is still ignored. If that list is full, the listener is approved while its condition-provider entry is silently dropped.",
      "When mUserSetServices is full, the approval still succeeds and returns true, but the user-set marker is silently not recorded. The tooManyChanges test confirms this: 'toomany' is allowed but isPackageOrComponentUserSet returns false. Entries in mUserSetServices are freed only on package removal (onPackagesChanged), not on disable.",
      "Disabling a package that is not currently approved still sets changed=true and triggers rebindServices, as before the fix.",
      "The commit message names a flag (com.android.server.notification.limit_managed_services_count), but the diff contains no flag check. In the code shown, the limit applies unconditionally."
     ],
     "behavior_changes": [
      "Enabling a package/component once a user's approved list in a ManagedServices instance already holds 100 entries is silently rejected: Slog.w is logged, there is no rebind, and setPackageOrComponentEnabled returns false. On the policy-access path the broadcast and handleSavePolicyFile are skipped. On the listener grant path the condition-provider update and the broadcasts are skipped.",
      "The setPackageOrComponentEnabled signature changes from void to boolean. It is a protected API, and the overrides in NotificationAssistants and NotificationListeners are updated.",
      "The NotificationAssistants override now returns false when re-enabling the assistant that is already enabled.",
      "rebindServices is no longer invoked when the approval is rejected.",
      "Once a user has 100 user-set entries, new user-set markers are dropped until packages are removed."
     ],
     "preconditions": [
      "A caller must reach ManagedServices.setPackageOrComponentEnabled. The entry points shown are NotificationManagerService.setNotificationPolicyAccessGrantedForUser (guarded by checkCallerIsSystemOrShell) and the listener access grant path. Both also require mAllowedManagedServicePackages.test to pass for the target package.",
      "The new rejection path is hit only when more than 100 packages/components are approved for the same user in the same ManagedServices instance."
     ],
     "per_major": {
      "14": "Adapted fix: commit cb1ce8020b43 in android-security-14.0.0_r25, with the same ManagedServices and caller changes. The NotificationListeners override differs only in lacking the redactSensitiveNotificationsFromUntrustedListeners block. 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: commit a68f6e5719ce in android-security-15.0.0_r13. Its content matches the 16 commit at different line offsets. 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix: commit e363f8210456 in android-security-16.0.0_r4 (cherry-pick of a132684a093d). 7/8 lines of the patched region are present at android-security-16.0.0_r8. The code context confirms that MAX_SERVICE_ENTRIES and the setNotificationPolicyAccessGrantedForUser early return are in place.",
      "17": "Not affected: the bulletin does not list Android 17. 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change alters the return type of the protected setPackageOrComponentEnabled overloads. Every subclass override (NotificationAssistants, NotificationListeners, and any vendor subclasses of ManagedServices) must be updated to return boolean. The listener grant path must also be reordered so mListeners is updated before mConditionProviders.",
      "Mockito mocks return false by default for boolean methods (general platform knowledge). Existing NotificationManagerServiceTest cases therefore need the added thenReturn(true) stubs for mListeners/mAssistants/mConditionProviders; without them, the follow-up broadcasts those tests verify will be skipped."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how an unbounded approved list leads to the EoP the bulletin classifies, nor the original reporter's trigger.",
      "The diff does not show whether other paths write mApproved or mUserSetServices (restore/XML parsing, default approvals), or whether they remain unbounded.",
      "The role of the flag com.android.server.notification.limit_managed_services_count named in the commit message is not shown; the diff has no flag gate.",
      "The diff does not show whether any caller of the assistant grant path checks the new boolean return value."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0035": {
     "cve": "CVE-2026-0035",
     "summary": "MediaProvider now rejects MediaStore CREATE_*_REQUEST calls (createWriteRequest/createDeleteRequest and siblings) with an IllegalArgumentException when any requested URI does not resolve to exactly one existing row in the Files table at request time, instead of building a PermissionActivity intent for it.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java createRequest handling (MediaStore CREATE_WRITE/DELETE/..._REQUEST, PermissionActivity intent construction)",
     "weakness": "Missing input validation (no existence check on requested URIs before creating a permission request)",
     "root_cause": "In MediaProvider.java, the code that handles MediaStore createRequest calls (the block that builds `new Intent(method, null, context, PermissionActivity.class)` and stores EXTRA_CALLING_PACKAGE_UID) accepted the caller-supplied collection of URIs without checking that each URI matched an item in the Files table. Per the commit message, a caller could request CREATE_WRITE/DELETE/..._REQUEST for a URI that did not exist at the time of the request, and the request was still created. The pre-fix test testCreateRequest used a hard-coded, never-inserted item id (42) and expected a non-null result. The bulletin rates this High / EoP. The material does not show what follows from a request for a non-existent URI.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java (createRequest handling, just before the PermissionActivity Intent is constructed): a new validation loop is added. It calls clearLocalCallingIdentity() to get a token, then for every Uri in `uris` runs queryForSingleItem(uri, new String[]{FileColumns._ID}, null, null, null) in a try-with-resources. queryForSingleItem throws FileNotFoundException if it finds zero items or multiple items, or if a read problem occurs. The catch turns this into IllegalArgumentException(\"Invalid Uri: \" + uri, e), which stops the request before any Intent is built. restoreLocalCallingIdentity(token) runs in finally, so the provider identity is always restored.",
      "Because the check sits before `final Context context = getContext(); final Intent intent = new Intent(method, ...)`, it applies to every request method routed through this block (the commit message lists CREATE_WRITE/DELETE/.._REQUEST), not only to write requests.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: testCreateRequest is rewritten to first insert a real audio row (DISPLAY_NAME test.mp3, audio/mpeg) via sIsolatedResolver and to request on that URI (assumeTrue(uri != null)). A new test, testCreateRequest_invalidUri_throwsException, asserts that createWriteRequest on a non-inserted item id (42) throws IllegalArgumentException."
     ],
     "completeness": [
      "Covers: every URI in the request list is checked. Because the check comes before the generic Intent(method, ...) construction, it gates all request methods that pass through this block.",
      "The existence check runs with the local calling identity cleared. It therefore checks whether the item exists in the database as seen by the provider, not as seen by the caller. It checks only existence and uniqueness, not access.",
      "As the commit message states, the check runs only when the request is created. This commit does not change what PermissionActivity does later if an item is removed or replaced between request creation and the user's decision.",
      "queryForSingleItem also rejects URIs that match multiple rows or fail to read, so those URIs now also cause IllegalArgumentException. This is slightly broader than a strict 'does not exist' check.",
      "Only the MediaProvider request path is touched. This commit makes no changes to PermissionActivity or to the MediaStore framework API."
     ],
     "behavior_changes": [
      "MediaStore.createWriteRequest / createDeleteRequest and related CREATE_*_REQUEST calls now throw IllegalArgumentException (\"Invalid Uri: <uri>\") when any URI in the list does not resolve to exactly one Files-table row. Previously a non-null request was returned (as the pre-fix test expected).",
      "One extra database query (FileColumns._ID projection) runs for each requested URI, under cleared calling identity, before the request is created.",
      "Callers that pass stale or not-yet-inserted URIs now fail when the request is created."
     ],
     "preconditions": [
      "A caller invokes a MediaStore createRequest method (CREATE_WRITE_REQUEST, CREATE_DELETE_REQUEST, or another CREATE_*_REQUEST method routed to this block) on MediaProvider with a collection of URIs.",
      "At least one supplied URI does not match exactly one row in the Files table at request time.",
      "MediaStore.createWriteRequest and its sibling methods are public API that ordinary apps can call through a ContentResolver (general platform knowledge)."
     ],
     "per_major": {
      "14": "same fix: commit 003dd55147bf in android-security-14.0.0_r25. The 16-line hunk in MediaProvider.java at line ~6970 is identical, and the test changes are the same. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 56e6173dcc97 in android-security-15.0.0_r13, identical hunk at line ~7644. The test diff also has a cosmetic indentation change on the @Test/testGrantMediaReadForPackage lines. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 119013a3d7e8 in android-security-16.0.0_r4 (Change-Id I00a3b15d8dbcd19afaec3980f7d7a07122bef640), hunk at line ~8383. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin. The patched region is already present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. The MediaProvider.java hunk is identical across 14/15/16; only the line offset differs. It depends on the existing helpers queryForSingleItem and clearLocalCallingIdentity/restoreLocalCallingIdentity (LocalCallingIdentity), and on FileColumns._ID. Place it immediately before the `new Intent(method, null, context, PermissionActivity.class)` construction in the createRequest handler."
     ],
     "confidence": "high",
     "unknowns": [
      "The diff header shows only the enclosing class, not the method that contains the patched hunk, so the material does not name the exact handler method.",
      "The material does not show what PermissionActivity did with a request for a non-existent URI, i.e. the concrete privilege-escalation consequence behind the High/EoP rating.",
      "The material does not show whether the overall bug fix included changes beyond this cherry-picked commit (e.g. in PermissionActivity or on the framework MediaStore side).",
      "The material does not show whether this fix is also delivered through a MediaProvider mainline module update."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "17": {
   "counts": {
    "announced": 0,
    "certain": 0,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [],
   "tags": [],
   "unannounced": [],
   "coverage": {
    "n_ingested_tags": 2,
    "first_ingested_tag": "android-security-17.0.0_r1",
    "latest_ingested_tag": "android-security-17.0.0_r2",
    "window_base_tag": "android-17.0.0_r1",
    "window_start": "2026-06-16",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {}
  }
 },
 "bulletin": {
  "url": "https://source.android.com/docs/security/bulletin/2026/2026-03-01",
  "latest_version": "1.6",
  "latest_revision": {
   "version": "1.6",
   "date": "May 28, 2026",
   "notes": "Updated Issue List"
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "March 2, 2026",
    "notes": "Bulletin published"
   },
   {
    "version": "1.1",
    "date": "March 4, 2026",
    "notes": "AOSP Links Added"
   },
   {
    "version": "1.2",
    "date": "March 9, 2026",
    "notes": "Updated Issue List"
   },
   {
    "version": "1.3",
    "date": "March 10, 2026",
    "notes": "Updated Issue List"
   },
   {
    "version": "1.4",
    "date": "April 2, 2026",
    "notes": "Updated Issue List"
   },
   {
    "version": "1.5",
    "date": "April 17, 2026",
    "notes": "Updated Issue List"
   },
   {
    "version": "1.6",
    "date": "May 28, 2026",
    "notes": "Updated Issue List"
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [],
  "out_of_scope_rows": {
   "Arm components": 1,
   "Imagination Technologies": 6,
   "Kernel": 8,
   "Kernel components": 6,
   "MediaTek components": 20,
   "Qualcomm closed-source components": 8,
   "Qualcomm components": 7,
   "Unisoc components": 7
  },
  "note": "changes are only visible from `tracked_since`; earlier revisions of the bulletin cannot be reconstructed. Out-of-scope rows (vendor closed-source, kernel) have no public AOSP code to resolve."
 }
}