{
 "month": "2025-09",
 "generated": "2026-10-06T12:05+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 44,
    "certain": 42,
    "probable": 2,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2023-24023",
     "osv_id": "ASB-A-255601934",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Bluetooth stack now caches a bonded BR/EDR peer's Secure Connections support when its link key is notified. When peer security capabilities arrive on a new connection, it disconnects the link if the peer reports a downgrade from Secure Connections mode (a BLUFFS mitigation).",
      "confidence": "medium",
      "backport_notes": [
       "The diff depends on helpers that the material does not show: btm_sec_store_device_sc_support and btm_sec_is_device_sc_downgrade, plus btm_sec_is_session_key_size_downgrade / btm_sec_update_session_key_size on 15. They must already exist in the target tree or be brought in first. The 14 commit uses p_dev_rec->is_bond_type_persistent() directly, while the 15 code uses p_dev_rec->sec_rec."
      ],
      "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": [
      "related_bug"
     ],
     "confidence": "probable",
     "first_fix": {
      "tag": "android-security-14.0.0_r12",
      "date": "2024-09-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r12",
      "date": "2024-09-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "b5155f0591f9395560b3a6983f3143770c11ae72",
       "tag": "android-security-14.0.0_r12",
       "provenance": "security_tag",
       "method": "related_bug",
       "confidence": "probable",
       "evidence": "probable: reference f904fd5ad7ef mentions bug 314331379, carried by b5155f0591f9 (cited in the reference subject)",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 363,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-49714",
     "osv_id": "ASB-A-271962784",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces an insufficient CHECK-based size assertion in avrc_vendor_msg (system/stack/avrc/avrc_opt.cc) with a bound that accounts for the BT_HDR, AVCT_MSG_OFFSET, the 3 AVRC header bytes and the 3 company_id bytes before vendor_len. The function now returns nullptr instead of aborting when the composed command would not fit in the AVRC_META_CMD_BUF_SIZE buffer.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, self-contained change that uses only existing symbols (BT_HDR, AVCT_MSG_OFFSET, AVRC_META_CMD_BUF_SIZE). When backporting, check that every caller of avrc_vendor_msg handles a nullptr return, because before the fix the function never returned null; it aborted instead."
      ],
      "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_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/modules/Bluetooth",
       "sha": "6b669f231d0faf4658bb3ba6ea7f77d4d4a5e1b1",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-0076",
     "osv_id": "ASB-A-366403307",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SystemUI's Quick Access Wallet tile and wallet screen now only load a WalletCard's card image and card icon when the Icon is TYPE_BITMAP or TYPE_ADAPTIVE_BITMAP; all other Icon types (URI, URI_ADAPTIVE_BITMAP, RESOURCE, DATA) produce a null drawable instead of being loaded in SystemUI's context.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The production change is three small edits (the QuickAccessWalletTile switch and the two QAWalletCardViewInfo conditionals) plus a test-only accessor in WalletView. The new tile test is added as a new file under packages/SystemUI/multivalentTests."
      ],
      "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_r18",
      "date": "2025-03-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r18",
      "date": "2025-03-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cac7c51cd276c1bbd2a3ee4b2d56cb98c984dcba",
       "tag": "android-security-14.0.0_r18",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 182,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-0089",
     "osv_id": "ASB-A-378505461",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a home-intent normalization step to ActivityStartInterceptor. ACTION_MAIN intents that carry CATEGORY_HOME/CATEGORY_SECONDARY_HOME in a non-standard format (extra categories, data/type, or an explicit component naming the default home) are rewritten into the canonical home intent and re-resolved before the activity is started.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. It depends on ActivityStarter.Request.componentSpecified, ActivityTaskManagerService.getHomeIntent(), RootWindowContainer.resolveHomeActivity(int, Intent), ActivityTaskSupervisor.resolveIntent(Intent, String, int, int, int, int) and resolveActivity(Intent, ResolveInfo, int, ProfilerInfo), with the signatures used in the diff. Adjust if a fork's signatures differ. Keep the 9-arg intercept overload, or update the existing ActivityStartInterceptorTest callers.",
       "The Android 15 commit shown in the material (f27918b39cff) is unrelated to this change. When backporting to a 15-based tree, find the cherry-pick of Change-Id I300f705798909014329f92118c266dbc04173e05 instead."
      ],
      "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_r20",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r20",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ee0d85dd3f501395d1f8e43a87d372657dc37c21",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 119,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26454",
     "osv_id": "ASB-A-299928772",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ManagedProvisioning's DisclaimersParserImpl now validates each caller-supplied EXTRA_PROVISIONING_DISCLAIMER_CONTENT Uri before copying its content into the provisioning disclaimer cache. The scheme must be content or android.resource, and for content URIs the Binder caller must hold read permission. Any disclaimer that fails validation is skipped.",
      "confidence": "high",
      "backport_notes": [
       "Apply both commits in order (c341743982f0, then 51723855c430); the second edits only the function the first introduces. The combined change is confined to DisclaimersParserImpl.java and adds imports of android.os.Binder, android.content.Intent, android.content.pm.PackageManager and java.util.Objects. It targets the parse loop that calls saveDisclaimerContentIntoFile(uri, i).",
       "When backporting, confirm that parse() runs in a context where Binder.getCallingUid() reflects the provisioning initiator. Outside an incoming Binder transaction, Binder.getCallingUid() returns the process's own uid (general platform knowledge), so checkUriPermission would test ManagedProvisioning's own access instead of the caller's."
      ],
      "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_r21",
      "date": "2025-06-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/apps/ManagedProvisioning",
       "sha": "c341743982f0be78011304b1368f183ef1cef416",
       "tag": "android-security-14.0.0_r21",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/ManagedProvisioning",
       "sha": "51723855c43075801da10ce29c4b03be2bdeb5f6",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 91,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/apps/ManagedProvisioning"
     ]
    },
    {
     "cve": "CVE-2025-32321",
     "osv_id": "ASB-A-378902342",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AccountTypePreferenceLoader.isSafeIntent, the fix keeps the existing check that the activity an intent resolves to belongs to the authenticator's uid. When the check passes, it now pins the intent to that resolved component with intent.setComponent(), so the intent becomes explicit and targets the validated activity.",
      "confidence": "medium",
      "backport_notes": [
       "The hunk is small, and its content is the same on 14, 15 and 16, differing only by a one-line offset. It should apply to branches that still have the `return resolvedAppInfo.uid == authenticatorAppInf.uid;` form. The bulletin lists Android 13 as affected, but the material includes no Android 13 commit.",
       "Make sure the caller starts the same Intent instance it passed to isSafeIntent, because the fix works by changing that instance."
      ],
      "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_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/apps/Settings",
       "sha": "fd19bf11ef0848a8d1baa38ad507ef708d6799c1",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32323",
     "osv_id": "ASB-A-397216537",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DocumentsUI now sanitizes the calling app's label in Shared.getCallingAppName with TextUtils.makeSafeForPresentation (500-character limit, TRIM and SINGLE_LINE flags) and puts the name in double quotes in the open_tree_dialog_message string. This stops the label from reading as part of the folder-access confirmation text.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The Shared.java and strings.xml hunks are the functional part. 14 and 15 share the Shared.java pre-image (index 5ac9de4d7), but 16 differs, so check the context when applying. The DocumentsAdapter, DirectoryFragment and TestEnvironment removals are optional cleanup. If a fork has other Environment implementers, remove their getCallingAppName override as well.",
       "The commit changes only res/values/strings.xml. Fork-specific or translated string resources are not part of it."
      ],
      "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_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/apps/DocumentsUI",
       "sha": "1259beab2b8fc2e1697617b4e030fc69fb8ac5b5",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2025-32325",
     "osv_id": "ASB-A-402319736",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens Parcel::appendFrom in libbinder in three ways: it rejects appends that would overwrite existing objects in the destination, it only copies and acquires source objects that actually fall inside the requested [offset, offset+len) range (instead of a contiguous index range), and it keeps the object-ordering bookkeeping (mObjectsSorted, RPC mObjectPositions) accurate.",
      "confidence": "high",
      "backport_notes": [
       "One commit per branch, with only context differences across 14/15/16: acquire_object takes tagFds on 16, and the growData condition uses mDataSize + len on 14/15 versus mDataPos + len on 16. The 14 commit places TEST(Parcel, AppendOverObject) after DebugReadAllFds.",
       "The fix depends on validateReadData being available and checking the destination's object list. Verify it is present in any older tree before backporting."
      ],
      "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_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/frameworks/native",
       "sha": "7be10040534a1c5b1b8c32fcaa538992a70e51a0",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-32326",
     "osv_id": "ASB-A-365739560",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is now launched only as a copy pinned to the component and package that the safety check resolved, instead of relaunching a copy of the original Intent after vetting.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained change in one file; the same diff content was applied to 14, 15 and 16. Forks with a customized assertSafeToStartCustomActivity need to change its return type to Intent, return a copy with the component and package set from the vetted ActivityInfo, and have onReceive launch the returned copy.",
       "The code at android-security-14.0.0_r29 places the vettedIntent copy at a different spot in the function than this commit does, so compare carefully against the tree you are backporting from."
      ],
      "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_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/apps/Settings",
       "sha": "33a1cbf7bd0e4db07d53e63f4cd470c8eaa4999c",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32327",
     "osv_id": "ASB-A-389681152",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PickerDbFacade no longer embeds unrecognized, caller-supplied projection column names into the SQL projection of queryMediaIdForApps; unknown columns are dropped from the SQL query and re-added as null-valued columns in a MatrixCursor built after the query.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit per branch; on branches where the query helper is named queryMediaIdForAppsLocked and getMediaStoreProjectionLocked takes pickerSegmentType (Android 15 shape), carry the extra parameter through as in 3f48b77e7cb9. The only new import is android.database.DatabaseUtils; the code at android-security-14.0.0_r29 shows it alongside the existing static import of com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar."
      ],
      "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_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": "186cff75881cbcd6a7db2153f2fc147329556a22",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-32330",
     "osv_id": "ASB-A-389127608",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the LE audio broadcast (audio sharing) password generator in SettingsLib's LocalBluetoothLeBroadcast, which derived a 12-character hex string from UUID.randomUUID(), with a 16-character password drawn from an 89-symbol alphabet using java.security.SecureRandom.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the only new dependency is java.security.SecureRandom. Per the commit message, ag/31424453 for the same bug is superseded and need not be backported.",
       "If removing the java.util.UUID import as the Android 14 commit does, confirm UUID is not used elsewhere in the fork's LocalBluetoothLeBroadcast.java (the Android 15 commit does not remove it)."
      ],
      "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_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/frameworks/base",
       "sha": "a9d98a5646f12c479d4918b4d3defc2107ddbfe6",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32333",
     "osv_id": "ASB-A-409780975",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' SpaActivity.startSpaActivityForApp now rejects an intent data scheme-specific part that does not match a Java-style package name regex before putting it into the SPA navigation destination string. For malformed values it returns false and launches no activity.",
      "confidence": "medium",
      "backport_notes": [
       "A one-line Kotlin change in SpaActivity.kt plus a unit test. The fix uses only kotlin.text.Regex.",
       "The test needs org.mockito.Mockito.any/never imports in tests/spa_unit."
      ],
      "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_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/apps/Settings",
       "sha": "591ea09a63e577a9ed666006e70430cc4f245078",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32347",
     "osv_id": "ASB-A-388528350",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricEnrollIntroduction now strips the MultiBiometricEnrollHelper extras (EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE, EXTRA_ENROLL_AFTER_FINGERPRINT) from its launch intent in onStart() whenever the calling package is not Settings itself, so PendingIntent extras supplied by external packages are dropped during enrollment.",
      "confidence": "medium",
      "backport_notes": [
       "A single self-contained commit touching one source file: apply the onStart() override and the visibility change to BiometricEnrollIntroduction.java. Check that any fork-specific subclasses do not override or call the two methods that are now private.",
       "The bulletin lists Android 13 as affected, but the material gives no Android 13 commit or tag status."
      ],
      "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_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/apps/Settings",
       "sha": "214b82f7097bc5056938780f3f4d9c5194bea2eb",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32349",
     "osv_id": "ASB-A-408215749",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowManager now forces the default Animation_Toast style on TYPE_TOAST windows from sessions without mCanAddInternalSystemWindow, and uses a fixed short alpha fade instead of the window's own exit animation when a non-system overlay is forcibly hidden, so an app can no longer control how long its overlay stays on screen during a forced hide.",
      "confidence": "medium",
      "backport_notes": [
       "Two commits under bug 408215749. The DisplayPolicy change is self-contained; it relies on R.style.Animation_Toast and win.mSession.mCanAddInternalSystemWindow, both already present. The WindowStateAnimator change depends on the new WindowState.isForceHiddenNonSystemOverlayWindow() getter and the existing mForceHideNonSystemOverlayWindow field, and must sit before the `switch (transit)` branch.",
       "Only the DisplayPolicy commit is shown for Android 15/16; check whether the WindowStateAnimator commit is present on the target branch before considering the backport complete."
      ],
      "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_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/frameworks/base",
       "sha": "a93c95a17ae4091cb8c283f0cb3fd3cd11fe83ba",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "b8401310e97fd5eccc0a0051a81a0077559d6a6f",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32350",
     "osv_id": "ASB-A-404256832",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes android:exported=\"true\" from SystemUI's ControlsActivity manifest entry and adds SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS to the window of the dialog created by ControlsSettingsDialogManager, so non-system windows are not drawn on top of it.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The only version-specific detail is the SystemUI R import (com.android.systemui.R on 14 vs com.android.systemui.res.R on 15/16). When applying the manifest hunk, find the activity by the android:launchMode=\"singleInstance\" + android:visibleToInstantApps=\"true\" element immediately before .wallet.ui.WalletActivity, because the hunk context does not include the android:name line.",
       "The new test reads dialog.window.attributes.privateFlags and uses the existing TestableAlertDialog helper in the test tree. It is optional for the runtime fix."
      ],
      "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": [
      "subject"
     ],
     "confidence": "probable",
     "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/frameworks/base",
       "sha": "b08dcc947008bfda406a5e0540f125e50010cfa6",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "subject",
       "confidence": "probable",
       "evidence": "probable: same subject as reference d6b86833ee06, common file packages/SystemUI/AndroidManifest.xml, score 1.00",
       "similarity": 1.0
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48522",
     "osv_id": "ASB-A-418773283",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AssociationRequest.Builder.setDisplayName now rejects display names longer than 1024 characters with an IllegalArgumentException. Before, it accepted an app-supplied string of any length, and that string is shown in the CompanionDeviceManager confirmation UI.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained 7-line Java addition with no dependencies shown. Insert the check in Builder.setDisplayName after the checkNotUsed()/requireNonNull pair. The 14, 15 and 16 diffs differ only in line offsets."
      ],
      "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_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/frameworks/base",
       "sha": "aa93bf9db68492fee16c25aaca536fb2328cc5bc",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48523",
     "osv_id": "ASB-A-388032224",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the branch in SelectAccountActivity.onCreate that silently returned the sole writable account as RESULT_OK. When one or more accounts exist, the account selection dialog is now always shown, so the vCard import flow no longer picks the account automatically.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single commit that deletes 9 lines in SelectAccountActivity.onCreate. The diff is identical across 14, 15 and 16 (same blob change 8ead5fab3..797fc8670), so it should apply cleanly to any branch that contains the size()==1 branch. Optionally also update the outdated comment at lines 57-60."
      ],
      "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": [
      "change_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/apps/Contacts",
       "sha": "6104890ea916f5f8951cc944530a99efea64e71c",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref c314edb9bde8",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Contacts"
     ]
    },
    {
     "cve": "CVE-2025-48524",
     "osv_id": "ASB-A-399885815",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WifiPermissionsUtil.isSystem() now returns false early when the calling uid is an SDK sandbox uid (on T+), instead of deriving system-app status from the ApplicationInfo flags of the supplied package name.",
      "confidence": "high",
      "backport_notes": [
       "The patch is self-contained and applies identically on 14/15/16 (same hunk; only the line offset differs: 1138 on 14 vs 1126 on 15/16). The commit adds no imports, so SdkLevel and Process must already be imported in this file. The Process.isSdkSandboxUid call is guarded by SdkLevel.isAtLeastT(). The bulletin also lists Android 13 as affected, but no 13 commit is included in the material."
      ],
      "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_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/modules/Wifi",
       "sha": "980d442c5926f033776d737df2b14c98ca804b71",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2025-48526",
     "osv_id": "ASB-A-407764858",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ChooserActivity now gives the adapter for the non-launched (cross-profile) profile a sanitized copy of the payload intents instead of the caller's original intents. In the copy, package and component are cleared, and so are the selector's package and component.",
      "confidence": "high",
      "backport_notes": [
       "For 14-style trees (explicit personal/work ChooserGridAdapter creation, no ChooserRequest model), the port is the new IntentUtils.kt (the 14 version takes List<Intent?> and keeps nulls) plus the two ternaries keyed on findSelectedProfile(). For 15+-style trees, use the ChooserRequest.crossProfilePayloadIntents form and the isCrossProfile loop. The 14 branch was changed again later (prepareCrossProfileIntents at r29), so consider porting from the latest security tag rather than this commit alone.",
       "No 'Fix in fork' section was provided, so no per-file fork verdict is available."
      ],
      "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_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/modules/IntentResolver",
       "sha": "4ffdecbbef8accc196580cf5c396c1b8e5da687f",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48527",
     "osv_id": "ASB-A-378088320",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' Notification History screen now replaces the title and text of history, snoozed and dismissed notifications with the generic \"notification_hidden_text\" string for any profile that is securely locked and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS disabled, instead of rendering the full content.",
      "confidence": "high",
      "backport_notes": [
       "The fix is self-contained to three files in packages/apps/Settings and uses hidden framework APIs (LockPatternUtils, KeyguardManager.isDeviceLocked(int), Settings.Secure.getIntForUser) and the internal resource com.android.internal.R.string.notification_hidden_text, which Settings can reference as a platform app (general platform knowledge). Update every call site of the two adapter constructors, since the signatures change.",
       "The 14 and 16 commits are the minimal form; the 15 commit adds onDestroy clearing and comments. The redaction logic is the same in all three."
      ],
      "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_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/apps/Settings",
       "sha": "cee46cf86cd938da0f829eb56701bdfbf92ccb36",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48529",
     "osv_id": "ASB-A-325030433",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the shared-preference storage, readback and channel migration of the voicemail ringtone URI in frameworks/opt/telephony, so TelephonyManager#setVoicemailRingtoneUri becomes a no-op and getVoicemailRingtoneUri only returns the voicemail NotificationChannel sound.",
      "confidence": "high",
      "backport_notes": [
       "Single commit in frameworks/opt/telephony. The diff is identical on 14, 15 and 16, with the same blob hashes (de1ddd3026 to ac6a385fa4 and d8988e3230 to 3dd3d375c9). Forks with unmodified VoicemailNotificationSettingsUtil and NotificationChannelController should be able to take it as-is. Forks that added other readers of the ringtone preference should check that none remain."
      ],
      "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_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/frameworks/opt/telephony",
       "sha": "122251ced157264d2e5a7446b79ffb5c33854514",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/opt/telephony"
     ]
    },
    {
     "cve": "CVE-2025-48531",
     "osv_id": "ASB-A-389681530",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' CredentialStorage now identifies the caller of its install flow with ActivityManager.getLaunchedFromPackage(getActivityToken()) instead of Activity.getCallingPackage() when deciding whether the caller is com.android.certinstaller.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches one file. ActivityManager.getService().getLaunchedFromPackage(IBinder) and Activity.getActivityToken() are hidden framework APIs that Settings can use because it builds against the platform (general platform knowledge). Only hunk line offsets differ between 14, 15 and 16."
      ],
      "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_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/apps/Settings",
       "sha": "da4efc0a0615eeede3c09e30db3156ac878c3c23",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48534",
     "osv_id": "ASB-A-381419370",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "CellBroadcastHandler.getDefaultCBRPackageName now queries broadcast receivers with PackageManager.MATCH_SYSTEM_ONLY instead of flags 0, so non-system packages can no longer be selected as the default CellBroadcastReceiver package.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with a two-line functional change: in getDefaultCBRPackageName, replace the flags argument 0 with PackageManager.MATCH_SYSTEM_ONLY. The new test needs the method to be accessible (public) and uses the mocked mMockedContext and mMockedPackageManager."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/modules/CellBroadcastService",
       "sha": "af24a165e952c2b7faf5787741fa1c953b116627",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/CellBroadcastService"
     ]
    },
    {
     "cve": "CVE-2025-48535",
     "osv_id": "ASB-A-353680402",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is no longer launched as received after vetting. Settings now launches a copy pinned to the exact component and package that passed the resolveActivity/package check, and a second commit makes that same copy the object that is resolved.",
      "confidence": "high",
      "backport_notes": [
       "Apply in order: first 33a1cbf7bd0e (assertSafeToStartCustomActivity returns a pinned copy and onReceive launches it), then ca8f012da894 (moves the new Intent(intent) copy above resolveActivity). The second diff's context depends on the first. Both touch only RestrictionsResultReceiver in AppRestrictionsFragment.java.",
       "Branches based on a later tag may already contain the additional removeFlags/setData(null)/setClipData(null) lines seen at 14.0.0_r29. Those come from a separate change and should be kept if present."
      ],
      "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-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/apps/Settings",
       "sha": "33a1cbf7bd0e4db07d53e63f4cd470c8eaa4999c",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "ca8f012da8942de253a9bc236f66786d823e5f46",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48537",
     "osv_id": "ASB-A-391894257",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ZenModeConditions.evaluateRule now maps condition ids to built-in system ConditionProviderServices only for system-owned or manual rules, rejects non-system rules whose component is in the \"android\" package, and tracks subscriptions by (Uri, ComponentName) pairs instead of by Uri alone.",
      "confidence": "high",
      "backport_notes": [
       "On Android 14 the fix also adds the isManual parameter to evaluateRule (true for config.manualRule, false for automatic rules); on 15/16 that parameter already exists. Any downstream code or tests that use the protected mSubscriptions field must adapt to the ArraySet<Pair<Uri, ComponentName>> type. The commit also updates ZenModeHelperTest.testRulesWithSameUri to register a package CPS; the test comment notes that without a valid CPS, ZenModeConditions clears rule.condition."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/frameworks/base",
       "sha": "49078c4bfdcedbf4f0c6432d69f15617e1a52f7f",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48538",
     "osv_id": "BUL-CVE-2025-48538",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Extends the protected-package check in PackageManagerService's package-hiding path so that, in addition to \"android\", the SystemUI package (resolved via PackageManagerInternal.getSystemUiServiceComponent()) can no longer be hidden.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, single-hunk change that depends on PackageManagerInternal.getSystemUiServiceComponent() and LocalServices; the same diff appears in 14, 15 and 16 at different line offsets, so a fork needs to locate its existing `\"android\".equals(packageName)` hide check."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/frameworks/base",
       "sha": "36348592fd270d3184540845429f09146e8826d8",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48540",
     "osv_id": "ASB-A-416734088",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Three small commits to libs/binder/RpcState.cpp make RPC Binder terminate the session (session->shutdownAndWait(false)) on error paths that previously only returned an error code and left the session alive. The paths are: a TRANSACT command received when a non-ANY CommandType was expected; a bad object-table span in an incoming transaction; and CommandData allocation failure in processTransact and waitForReply.",
      "confidence": "medium",
      "backport_notes": [
       "Apply in order: 995eb96a80a2 (SPAN), then 71342a41e519 (wrong type), then e73e9de0288c (ENOMEM). The diff index hashes chain 03fa69973d -> 5935e95f50 -> 11868a5bbc -> 6bdd385822. Each hunk only adds (void)session->shutdownAndWait(false), plus one ALOGE in the wrong-type commit.",
       "Consider also adding the same shutdown to the reply-side reinterpret<const uint32_t>() failure in waitForReply. The shown Android 14 code leaves it without shutdownAndWait."
      ],
      "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_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/frameworks/native",
       "sha": "71342a41e519f2698aa9f7137f8641e0000df3d1",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "995eb96a80a226b1b6bab2700aeaf5ce3beb1889",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "e73e9de0288cafadfafa5497eaad71c26931bf30",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48541",
     "osv_id": "ASB-A-411418366",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "FaceSettings now only honours the hw_auth_token, sensor id, challenge and EXTRA_USER_ID intent extras when the activity's initial calling package is the Settings app itself; for any other (or unknown) caller it ignores them and uses the current user.",
      "confidence": "high",
      "backport_notes": [
       "The fix is self-contained in FaceSettings.java but depends on SettingsActivity.getInitialCallingPackage() existing and on the hosting activity being a SettingsActivity (requireActivity() is cast unconditionally); confirm both in the target branch before applying.",
       "Also keep the field initializers (mSensorId = -1, mChallenge = 0) since the external-caller branch no longer assigns them."
      ],
      "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_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/apps/Settings",
       "sha": "f3cc1073e51e7945d7f75b4f43fe30d610a37ab8",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48542",
     "osv_id": "ASB-A-273501008",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds per-account storage accounting to AccountManagerService. Once an account's estimated user-data and auth-token storage exceeds 30,000,000 divided by the number of accounts, new user-data writes are dropped silently and auth-token writes return false. Passwords longer than 1,000,000 characters are rejected on account add and on password change.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches only AccountManagerService.java. It relies on the existing userDataCache, authTokenCache, accountCache, cacheLock and dbLock fields, and on the lazy-load branches in writeUserDataIntoCacheLocked, writeAuthTokenIntoCacheLocked and readAuthTokenInternal. On 16 the only difference is the setUserdataInternal signature (extra callingUid)."
      ],
      "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_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/frameworks/base",
       "sha": "9a4d7193f893d90a47b4a328a79aa073eea7ffb0",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48543",
     "osv_id": "ASB-A-421834866",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an instantiability check to three ART JNI object creation entry points in jni_internal.cc (AllocObject, NewObjectV and NewObjectA, per the matching CheckJNI call sites). Creating an instance of an abstract class or interface through them now throws java.lang.InstantiationException. The fix also relaxes the CheckJNI helper so it only rejects array classes.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. The three jni_internal.cc insertions and the check_jni.cc rename apply unchanged on 14, 15 and 16; only line offsets differ. In a customized tree, insert each check right after the `if (c == nullptr) return nullptr;` that comes before the IsStringClass() branch in each of the three functions. Keep the check_jni.cc change so that CheckJNI builds behave the same as the base path."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/art",
       "sha": "caeef036e31bc336ec0e768a8e52054f39a88571",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2025-48545",
     "osv_id": "ASB-A-397438392",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountManagerService.isSystemUid() now returns false immediately for SDK sandbox UIDs instead of deriving system status from the packages that PackageManager associates with that UID.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained 3-line change, identical on 14, 15 and 16. It needs isSystemUid() in AccountManagerService.java and Process.isSdkSandboxUid() (added with the SDK sandbox in Android 13; general platform knowledge). The bulletin lists Android 13 as affected, but the material includes no 13 commit."
      ],
      "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-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/frameworks/base",
       "sha": "30dc386d4a57fce3ee2e7be3030cf80db81ae204",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48546",
     "osv_id": "ASB-A-388029380",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SafeActivityOptions now rejects with a SecurityException any activity start whose ActivityOptions carry setLaunchWindowingMode(WINDOWING_MODE_PINNED), pointing callers to ActivityOptions#makeLaunchIntoPip instead.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit: an 11-line block plus a static import in SafeActivityOptions.java, placed between the existing permission-denial block and the launch-activity-type check; the same hunk applies at different offsets on 14/15/16. Remove testKeepsPictureInPictureLaunchModeInOptions / ...WithBoundsInOptions from TaskLaunchParamsModifierTests if present; optionally add the SafeActivityOptionsTest case from the 15/16 commits (absent from the 14 commit)."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/frameworks/base",
       "sha": "41bdf43266daa9143ed105112dfc7366b71d3d31",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48547",
     "osv_id": "ASB-A-419105158",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PermissionController now stops a package's one-time permission session on revoke only when no remaining permission of that package is still granted as one-time, and the Kotlin helper that makes this decision is rewritten so its grant and one-time checks evaluate correctly.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained in two PermissionController files. Android 13 is listed as affected by the bulletin, but the material includes no 13 commit.",
       "When backporting the Kotlin hunk, remove the LightAppPermGroup parameter from anyPermsOfPackageOneTimeGranted and update the caller shown in the diff. The functional parts are: dropping the group skip and the isOneTime early return, removing the flag pre-mask, and switching to the REQUESTED_PERMISSION_GRANTED bit test. The variable rename from idx to index is cosmetic."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/modules/Permission",
       "sha": "122e39ced57a1f6a85b6bb46d4ab68a6652a74d8",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2025-48548",
     "osv_id": "ASB-A-293603271",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes audioserver's silencing of a recording client match the AppOps op state. startRecording now returns the exact permission result (granted/soft/hard denied). startInput un-silences a client only when the op was granted. finishRecording is called only for clients that were not silenced. Sources that do not need the record op are checked against their own op instead of having their result ignored. Companion frameworks/base changes fix AppOps attribution-chain preflight and cleanup, and the SystemUI mic indicator suppression logic.",
      "confidence": "medium",
      "backport_notes": [
       "Apply in this order: av c962764b8234, then an intermediate change not in the material, then av fa795ebf0e65. The fa795ebf0e65 pre-image of AudioRecordClient.cpp already contains reg()/check() lambdas and OP_RECORD_AUDIO co-monitoring, which c962764b8234 does not produce. The startRecording return type changes from bool to int in ServiceUtilities.h, so every caller must be checked.",
       "base 2c18e5f08282 changes preflight behavior that, per its commit message, audioserver relies on. Carry it together with av fa795ebf0e65, which adds a checkPermissionForPreflightFromDatasource call. The material does not show how bbd8db54b3c9 depends on the av commits."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 11,
       "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/frameworks/av",
       "sha": "c962764b82345c46b4c20353afb84f1b57686744",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/av",
       "sha": "fa795ebf0e6552510584877e32703a829d4f1a1b",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "2c18e5f08282f41731c934e676b9faaaaa6a951d",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "bbd8db54b3c9e7cd6e49a190753f4cab9db40789",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "feedcb4926616b82af000eabe208469f411efd7e",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "projects": [
      "platform/frameworks/av",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48549",
     "osv_id": "ASB-A-325912429",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Four coordinated changes make microphone app-op enforcement and the mic indicator consistent across a full attribution chain: audioserver's OpRecordAudioMonitor now checks and watches every link of the chain (and always OP_RECORD_AUDIO); PermissionManagerService finishes already-started proxy ops when a later link fails and aligns the preflight check with the data-delivery path; and SystemUI only suppresses the mic indicator when all of a UID's recordings are silenced.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the base commits in order: bbd8db54b3c9 (finish all on failure, parent index 297ad73e054b) then 2c18e5f08282 (preflight skip, parent index 693d7b348b55). The 2c18e5f08282 commit message says audioserver implicitly relies on the preflight behavior, so ship it together with the av change.",
       "The av change relies on AttributionSourceState.next and on the existing startWatchingMode-with-flags and doesPackageTargetAtLeastU code in AudioRecordClient.cpp. Trees that differ there need an adapted version."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/frameworks/av",
       "sha": "db3bbd5a03e43929b5b90ffa73ecc3e0d5837afd",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "2c18e5f08282f41731c934e676b9faaaaa6a951d",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "bbd8db54b3c9e7cd6e49a190753f4cab9db40789",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "feedcb4926616b82af000eabe208469f411efd7e",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "projects": [
      "platform/frameworks/av",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48550",
     "osv_id": "ASB-A-401256328",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds package-name validation to both SlicePermissionManager.PkgUser constructors so that a malformed package string is rejected with IllegalArgumentException instead of being accepted.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit in one file. It depends on FrameworkParsingPackageUtils.validateName(String, boolean, boolean), which the 14, 15 and 16 diffs import and call.",
       "14 and 15 share identical pre/post blob hashes; on 16 the hunk content is the same and only the line offsets differ."
      ],
      "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_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/frameworks/base",
       "sha": "54158e2925dcfe5bb58f26fd594d1bfe30519de8",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48551",
     "osv_id": "ASB-A-407991863",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Sharesheet \"edit image\" action in IntentResolver's ChooserActivity is now launched as the user that launched the sharesheet instead of always as the personal profile user.",
      "confidence": "high",
      "backport_notes": [
       "Android 14 has no mUserInteractor, so the fix uses UserHandle.of(UserHandle.myUserId()). A backport to any 14-based fork should follow the 14 variant, and any other implementation of ActionActivityStarter (including tests) must adopt the renamed methods.",
       "If the target branch has the use_preferred_image_editor flag, the companion change ag/34038177 (flag-on path) is also needed. It is not included in this material."
      ],
      "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-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/modules/IntentResolver",
       "sha": "75f41ba2e89d5c0cdef487b88d5da0c876a108f7",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48552",
     "osv_id": "ASB-A-365975561",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Wraps the three Settings.Global proxy writes in DevicePolicyManagerService.saveGlobalProxyLocked in a try/catch, so an exception from SettingsProvider while applying a device admin's global proxy no longer aborts the caller (for example the removal of an ActiveAdmin).",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk in saveGlobalProxyLocked: a try/catch around the three mInjector.settingsGlobalPut* calls. The Settings.Global -> Global qualifier change in the diff is cosmetic, so the target tree's existing qualifier can be kept."
      ],
      "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_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/frameworks/base",
       "sha": "58e0b9a15338da4e97fd77271a252dacb0de697a",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48553",
     "osv_id": "BUL-CVE-2025-48553",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DevicePolicyManagerService.handlePackagesChanged now re-parses the device admin metadata of an active admin's receiver (via a new DeviceAdminInfo) after a package change and removes the admin if the package, receiver or metadata is missing or malformed; DeviceAdminInfo's metadata parser additionally converts OutOfMemoryError into XmlPullParserException so that failure is catchable.",
      "confidence": "high",
      "backport_notes": [
       "Backport both files together: the `catch (Exception e)` in isActiveAdminPackageValid relies on DeviceAdminInfo converting OutOfMemoryError to XmlPullParserException. The new method uses unqualified GET_META_DATA, MATCH_DIRECT_BOOT_AWARE and MATCH_DIRECT_BOOT_UNAWARE; the diff adds no imports, so confirm those static imports exist in the target DevicePolicyManagerService (Android 14's removed code used the PackageManager.-qualified form, so check the import block)."
      ],
      "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_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/frameworks/base",
       "sha": "0f9ca51bfd428257bed2868057da0e4b5982db0f",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48554",
     "osv_id": "ASB-A-414603411",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DevicePolicyManagerService now re-parses an active admin's device-admin metadata whenever its package changes and drops the admin if the metadata is malformed, and DeviceAdminInfo converts an OutOfMemoryError raised while parsing that metadata into an XmlPullParserException so callers can handle it.",
      "confidence": "high",
      "backport_notes": [
       "Apply both hunks together. The DevicePolicyManagerService helper catches Exception only, so without the DeviceAdminInfo change that converts OutOfMemoryError to XmlPullParserException, an OOM during parsing is still uncaught.",
       "The helper uses bare GET_META_DATA / MATCH_DIRECT_BOOT_AWARE / MATCH_DIRECT_BOOT_UNAWARE names. On a branch whose source file lacks the matching static imports, add them or qualify the names (general platform knowledge)."
      ],
      "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-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/frameworks/base",
       "sha": "0f9ca51bfd428257bed2868057da0e4b5982db0f",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48558",
     "osv_id": "ASB-A-380885270",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BatteryService now resolves the ACTION_REQUEST_SHUTDOWN intent it fires on low battery or over-temperature to an explicit component whose owning uid holds android.permission.SHUTDOWN. Before, it launched the implicit intent from the system process, so any activity with a matching intent-filter could resolve.",
      "confidence": "high",
      "backport_notes": [
       "Single-file change with no dependency on other commits. It uses PackageManagerInternal.queryIntentActivities, ActivityManagerInternal.getCurrentUserId() and LocalServices, which are internal system_server APIs, so a tree where those signatures differ needs the calls adapted (general platform knowledge). Android 13 is listed as affected, but the material includes no Android 13 commit."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/frameworks/base",
       "sha": "e22e656b5e881405867315bf8c463fe0534baa63",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48559",
     "osv_id": "ASB-A-416491779",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppOpsService.verifyAndGetBypass no longer always accepts attribution tags for the root UID: a tag is now valid only when the proxy package is null or a system package. finishProxyOperation now passes the proxy identity into verification, so unverified tags from non-system proxies are not treated as valid on the finish path.",
      "confidence": "high",
      "backport_notes": [
       "Single commit per branch. When porting, match the local finishOperationUnchecked signature (14 has no virtualDeviceId; 15/16 do) and the local attributed-op lookup (op.mAttributions on 14, op.mDeviceAttributedOps on 15/16). The verifyAndGetBypass and isPackageNullOrSystem hunks are identical across 14/15/16.",
       "Check that the 6-arg verifyAndGetBypass (with proxyUid/proxyPackageName/suppressErrorLogs) exists in the target. The fix relies on it and on the existing special-proxied-apps branch it refactors."
      ],
      "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-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/frameworks/base",
       "sha": "1c677924559849232e06958a7579369b8ffad74f",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48562",
     "osv_id": "ASB-A-423815728",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PrintSpooler's save-to-PDF path now opens the destination content URI in write-truncate mode (\"wt\") instead of the default mode, so that overwriting an existing file gets a truncated output stream.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained one-line change in RemotePrintDocument.writeContent with no other commits in the material. ContentResolver.openOutputStream(Uri, String mode) is a long-standing public API (general platform knowledge)."
      ],
      "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_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/frameworks/base",
       "sha": "a52780f0ce9eb4956ecfaa4bd30ef7a87e34ba0e",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48563",
     "osv_id": "ASB-A-401545800",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an onNullBinding() override to RemoteFillService that calls unbind(), so system_server no longer stays bound, with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, to an autofill service whose onBind() returned null.",
      "confidence": "high",
      "backport_notes": [
       "Single, self-contained commit. It goes between onServiceConnectionStatusChanged and dispatchCancellationSignal; the hunk context is the same on 14, 15 and 16, with only line offsets differing. It needs RemoteFillService to extend ServiceConnector.Impl (for unbind()) and needs sVerbose, TAG and Slog to be in scope."
      ],
      "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-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/frameworks/base",
       "sha": "1f704bff05f0cae830c7369b32a12961b9d292b3",
       "tag": "android-security-14.0.0_r22",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r22"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "295549202"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8d457e365ac9b502badc0c59cea3e23bdb530bbd",
       "subject": "Revert \"Update CtsShim to use sharedUserId\"",
       "tag": "android-security-14.0.0_r22",
       "merged": "2025-07-11",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r22"
     ],
     "tag": "android-security-14.0.0_r22"
    },
    {
     "bug_ids": [
      "399120953"
     ],
     "commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "07a6825434613391e0874da27c86f7ae0059fde8",
       "subject": "Don't blur too many layers",
       "tag": "android-security-14.0.0_r22",
       "merged": "2025-07-16",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r22"
     ],
     "tag": "android-security-14.0.0_r22"
    },
    {
     "bug_ids": [
      "315320090",
      "418085016"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "5f88dfe2cd402202657107aa2b13eb75d28091e1",
       "subject": "Ensure Gallery always shows as system fixed",
       "tag": "android-security-14.0.0_r22",
       "merged": "2025-07-11",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r22"
     ],
     "tag": "android-security-14.0.0_r22"
    },
    {
     "bug_ids": [
      "337781186"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "8f9abbc2b041f7429699f3e224da8b1fa8407997",
       "subject": "DO NOT MERGE remove the contacts picker from CallForwardingOptions",
       "tag": "android-security-14.0.0_r22",
       "merged": "2025-07-11",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r22"
     ],
     "tag": "android-security-14.0.0_r22"
    }
   ],
   "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-2023-24023": {
     "cve": "CVE-2023-24023",
     "summary": "The Bluetooth stack now caches a bonded BR/EDR peer's Secure Connections support when its link key is notified. When peer security capabilities arrive on a new connection, it disconnects the link if the peer reports a downgrade from Secure Connections mode (a BLUFFS mitigation).",
     "component": "platform/packages/modules/Bluetooth, system/stack/btm (BTM security manager, btm_sec.cc)",
     "weakness": "Missing validation of a peer's security parameters against previously established (cached) values on reconnection, which allows a Secure Connections downgrade",
     "root_cause": "In system/stack/btm/btm_sec.cc, btm_sec_set_peer_sec_caps took the sc_supported value the remote reported on each connection and went on to record the peer's capabilities (remote_feature_received and related fields) without comparing it to a value observed when the device was bonded. btm_sec_link_key_notification did not cache the bonded peer's Secure Connections support either. Nothing therefore rejected a reconnection on which a peer bonded with Secure Connections reported no Secure Connections support.",
     "fix_analysis": [
      "system/stack/btm/btm_sec.cc:btm_sec_link_key_notification: after link key handling, if p_dev_rec->is_bond_type_persistent() and the device is BR/EDR or dual mode, the function calls btm_sec_store_device_sc_support(p_dev_rec->get_br_edr_hci_handle(), p_dev_rec->SupportsSecureConnections()) to cache the peer's Secure Connections support.",
      "system/stack/btm/btm_sec.cc:btm_sec_set_peer_sec_caps: right after it finds the device record by handle, the function calls btm_sec_is_device_sc_downgrade(hci_handle, sc_supported). If that reports a downgrade, it calls acl_set_disconnect_reason(HCI_ERR_HOST_REJECT_SECURITY), then btm_sec_send_hci_disconnect(p_dev_rec, HCI_ERR_AUTH_FAILURE, hci_handle, \"attempted to downgrade from Secure Connections mode\"), and returns before remote_feature_received and the other capability fields are updated.",
      "Android 15 (f904fd5ad7ef): removes the bluffs_mitigation aconfig flag from flags/security.aconfig and the com::android::bluetooth::flags::bluffs_mitigation() guards in btm_sec.cc. This makes the following always active: the Secure Connections downgrade check, the SC support caching, and the encryption key size downgrade check (btm_sec_is_session_key_size_downgrade / btm_sec_update_session_key_size in read_encryption_key_size_complete_after_encryption_change). It also deletes the flag-off branch of btm_sec_encryption_change_evt that skipped the key size read when set_min_encryption was enabled and SET_MIN_ENCRYPTION_KEY_SIZE was supported."
     ],
     "completeness": [
      "The Android 14 commit only adds the connection-time check for Secure Connections mode, as its commit message states. The encryption key size downgrade check visible in the Android 15 code is not part of this diff, and the material does not show whether Android 14 has that check.",
      "SC support is cached only for records with a persistent bond type that are BR/EDR or dual mode. How the downgrade check behaves for records with no cached value is not shown.",
      "The downgrade check relies on btm_sec_store_device_sc_support and btm_sec_is_device_sc_downgrade, which this diff does not define. The material does not show their storage semantics (in-memory or persisted).",
      "On a detected downgrade, the function returns before remote_feature_received and the other peer capabilities are recorded."
     ],
     "behavior_changes": [
      "When btm_sec_is_device_sc_downgrade reports a Secure Connections downgrade for a connection, the stack disconnects the link with HCI_ERR_AUTH_FAILURE and sets the ACL disconnect reason to HCI_ERR_HOST_REJECT_SECURITY.",
      "Secure Connections support is now cached at link key notification time for persistently bonded BR/EDR or dual-mode devices.",
      "On Android 15 the mitigation is no longer gated by the bluffs_mitigation flag. For successful, enabled encryption changes on non-LE links, the encryption key size is read and checked for a downgrade whenever the controller supports READ_ENCRYPTION_KEY_SIZE. The flag-off path that skipped this when SET_MIN_ENCRYPTION_KEY_SIZE was in use is removed."
     ],
     "preconditions": [
      "Bluetooth is enabled and there is an existing persistent BR/EDR or dual-mode bond whose link key notification went through btm_sec_link_key_notification (reached from btu_hcif_link_key_notification_evt in system/stack/btu/btu_hcif.cc, per index search).",
      "A later connection on which the peer security capabilities delivered to btm_sec_set_peer_sec_caps indicate a downgrade from Secure Connections."
     ],
     "per_major": {
      "14": "same fix: b5155f0591f9 in android-security-14.0.0_r12 adds the SC support caching and the downgrade check with no flag guard. 6/6 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "adapted fix: the mitigation was guarded by the bluffs_mitigation aconfig flag. f904fd5ad7ef in android-security-15.0.0_r5 removes the flag, so the SC check and the key size downgrade check are always on. Device record fields are accessed via p_dev_rec->sec_rec. 4/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). 5/6 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 0/6 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The diff depends on helpers that the material does not show: btm_sec_store_device_sc_support and btm_sec_is_device_sc_downgrade, plus btm_sec_is_session_key_size_downgrade / btm_sec_update_session_key_size on 15. They must already exist in the target tree or be brought in first. The 14 commit uses p_dev_rec->is_bond_type_persistent() directly, while the 15 code uses p_dev_rec->sec_rec."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the encryption key size downgrade check seen in the Android 15 code was also applied to Android 14. Only the Secure Connections mode CL is shown for 14.",
      "Definitions and storage semantics of btm_sec_store_device_sc_support and btm_sec_is_device_sc_downgrade (in-memory or persisted across restarts), and how they behave when no value is cached.",
      "The bulletin's affected list says 13, 14, 15, 16-next, while the per-major facts say Android 16 is not listed. The material does not reconcile this.",
      "Which caller delivers remote features to btm_sec_set_peer_sec_caps (the index search found only a test mock).",
      "Android 13 status: the bulletin lists it as affected, but the material gives no commit or tag for it.",
      "Why only 4/8 (15), 5/6 (16) and 0/6 (17) lines of the patched region match in the later tags."
     ],
     "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-2024-49714": {
     "cve": "CVE-2024-49714",
     "summary": "Replaces an insufficient CHECK-based size assertion in avrc_vendor_msg (system/stack/avrc/avrc_opt.cc) with a bound that accounts for the BT_HDR, AVCT_MSG_OFFSET, the 3 AVRC header bytes and the 3 company_id bytes before vendor_len. The function now returns nullptr instead of aborting when the composed command would not fit in the AVRC_META_CMD_BUF_SIZE buffer.",
     "component": "platform/packages/modules/Bluetooth, AVRCP stack (system/stack/avrc/avrc_opt.cc)",
     "weakness": "Missing/incorrect bounds check leading to out-of-bounds (heap) write",
     "root_cause": "avrc_vendor_msg allocates a fixed buffer of AVRC_META_CMD_BUF_SIZE bytes with osi_calloc. It then writes starting at (p_cmd + 1) + AVCT_MSG_OFFSET: one byte ctype, one byte subunit, one byte AVRC_OP_VENDOR, three bytes company_id, and finally a memcpy of p_msg->vendor_len bytes from p_msg->p_vendor_data. The pre-fix guard was CHECK(AVRC_META_CMD_BUF_SIZE > (AVRC_MIN_CMD_LEN + p_msg->vendor_len)). It used AVRC_MIN_CMD_LEN rather than the fixed overhead the fix introduces, sizeof(BT_HDR) + AVCT_MSG_OFFSET + 6. Per the commit title (OOB write), a vendor_len that passed the old check could make the write go past the end of the allocated buffer. The guard was also a CHECK (process abort) rather than an error return.",
     "fix_analysis": [
      "system/stack/avrc/avrc_opt.cc:avrc_vendor_msg: defines AVRC_MIN_VENDOR_CMD_LEN as sizeof(BT_HDR) + AVCT_MSG_OFFSET + 3 + 3. This is the number of bytes in the buffer before the vendor payload, and it matches the writes that follow (offset set to AVCT_MSG_OFFSET, three header bytes, AVRC_CO_ID_TO_BE_STREAM for company_id).",
      "system/stack/avrc/avrc_opt.cc:avrc_vendor_msg: replaces CHECK(p_msg != NULL) and CHECK(AVRC_META_CMD_BUF_SIZE > (AVRC_MIN_CMD_LEN + p_msg->vendor_len)) with one early return of nullptr when p_msg == nullptr or AVRC_META_CMD_BUF_SIZE < AVRC_MIN_VENDOR_CMD_LEN + p_msg->vendor_len. The memcpy of vendor_len bytes can therefore no longer exceed the osi_calloc(AVRC_META_CMD_BUF_SIZE) allocation. Equality (an exact fit) is allowed.",
      "The writes that follow and the p_cmd->len computation are unchanged. Only the precondition in front of them is corrected."
     ],
     "completeness": [
      "The new bound covers every byte written into p_cmd in avrc_vendor_msg: BT_HDR, AVCT_MSG_OFFSET, 3 header bytes, 3 company_id bytes and vendor_len payload bytes. The memcpy is therefore fully bounded against the AVRC_META_CMD_BUF_SIZE allocation.",
      "The function is static and the material does not show its callers. Whether callers handle the new nullptr return cannot be judged from the material; before the fix, the function either returned a valid buffer or aborted.",
      "The commit changes only avrc_vendor_msg. It touches no other buffer-composing paths in avrc_opt.cc, and the material does not show those paths."
     ],
     "behavior_changes": [
      "An oversized vendor_len (or a null p_msg) now makes avrc_vendor_msg return nullptr instead of triggering a CHECK abort of the Bluetooth process.",
      "The size check now uses AVRC_MIN_VENDOR_CMD_LEN instead of AVRC_MIN_CMD_LEN, so the maximum accepted vendor_len may differ from before. An exact fit (sum equal to AVRC_META_CMD_BUF_SIZE) is accepted."
     ],
     "preconditions": [
      "A tAVRC_MSG_VENDOR must reach avrc_vendor_msg with a vendor_len large enough that AVRC_MIN_VENDOR_CMD_LEN + vendor_len exceeds AVRC_META_CMD_BUF_SIZE, yet small enough to pass the old AVRC_MIN_CMD_LEN-based CHECK. The material does not show the callers of this static function or where vendor_len comes from (no caller found per index search)."
     ],
     "per_major": {
      "14": "same fix: commit 6b669f231d0f (cherry-pick of d5de235b461ec83e43a7db513e286d3204c4cedf) in android-security-14.0.0_r22, linked by bug_id. At android-security-14.0.0_r29 the patched region is present (4/5 lines matched, and the code context shows the new check).",
      "15": "not affected per the bulletin. At android-security-15.0.0_r17, 3/5 lines of the patched region are present. The material does not show whether the fix or an equivalent check is present.",
      "16": "not affected per the bulletin, although bulletin/OSV list 16-next as affected. At android-security-16.0.0_r8, 3/5 lines of the patched region are present. The material does not show whether the fix is present.",
      "17": "not affected per the bulletin. At android-security-17.0.0_r1, 3/5 lines of the patched region are present. The material does not show whether the fix is present."
     },
     "backport_notes": [
      "Single-file, self-contained change that uses only existing symbols (BT_HDR, AVCT_MSG_OFFSET, AVRC_META_CMD_BUF_SIZE). When backporting, check that every caller of avrc_vendor_msg handles a nullptr return, because before the fix the function never returned null; it aborted instead."
     ],
     "confidence": "high",
     "unknowns": [
      "The numeric values of AVRC_MIN_CMD_LEN, AVRC_META_CMD_BUF_SIZE and AVCT_MSG_OFFSET, and therefore the exact range of vendor_len that got past the old check.",
      "Which callers invoke avrc_vendor_msg, how they handle the new nullptr return, and whether a remote peer can influence vendor_len or only local clients can.",
      "The exact state of the patched region in the Android 15/16/17 tags (only a partial 3/5 line match is reported).",
      "How the fix is delivered beyond the listed 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-0076": {
     "cve": "CVE-2025-0076",
     "summary": "SystemUI's Quick Access Wallet tile and wallet screen now only load a WalletCard's card image and card icon when the Icon is TYPE_BITMAP or TYPE_ADAPTIVE_BITMAP; all other Icon types (URI, URI_ADAPTIVE_BITMAP, RESOURCE, DATA) produce a null drawable instead of being loaded in SystemUI's context.",
     "component": "platform/frameworks/base, SystemUI Quick Access Wallet (packages/SystemUI qs/tiles and wallet/ui)",
     "weakness": "Externally supplied Icon objects of unrestricted type (including content URIs) were loaded in SystemUI; a deny-list on Icon type is replaced by a bitmap-only allow-list (information disclosure per bulletin)",
     "root_cause": "WalletCard objects returned by the bound QuickAccessWalletService carry Icon objects for the card image and the card icon, and SystemUI resolved them with Icon.loadDrawable(context) using its own context. In QuickAccessWalletTile.WalletCardRetriever.onWalletCardsRetrieved, the switch nulled only TYPE_URI and TYPE_URI_ADAPTIVE_BITMAP and still loaded TYPE_RESOURCE and TYPE_DATA. In WalletScreenController.QAWalletCardViewInfo, the card image was nulled only for Icon.TYPE_URI (TYPE_URI_ADAPTIVE_BITMAP, TYPE_RESOURCE and TYPE_DATA were loaded), and the card icon (getCardIcon) was loaded for every Icon type, including content URIs. The commit message states that the drawable should only be loaded from a bitmap.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java : WalletCardRetriever.onWalletCardsRetrieved: the switch on mSelectedCard.getCardImage().getType() is reordered into an allow-list. TYPE_BITMAP and TYPE_ADAPTIVE_BITMAP call loadDrawable(mContext); TYPE_URI, TYPE_URI_ADAPTIVE_BITMAP, TYPE_RESOURCE and TYPE_DATA now all set mCardViewDrawable = null. The default branch is unchanged: it logs and sets null.",
      "packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java : QAWalletCardViewInfo constructor: the card image is loaded only if cardImageIcon.getType() is Icon.TYPE_BITMAP or Icon.TYPE_ADAPTIVE_BITMAP, otherwise mCardDrawable = null. Previously only TYPE_URI was excluded.",
      "packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java : QAWalletCardViewInfo constructor: the card icon from mWalletCard.getCardIcon() is now loaded only if it is non-null and of type TYPE_BITMAP or TYPE_ADAPTIVE_BITMAP, otherwise mIconDrawable = null. Previously it was loaded for any type.",
      "packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java: adds a @VisibleForTesting getIcon() accessor for mIcon, used only by the new test; no behavior change.",
      "Tests: new multivalentTests QuickAccessWalletTileTest (testQueryCards_invalidDrawable_noSideViewDrawable uses a content:// Icon and asserts there is no sideViewCustomDrawable) and WalletScreenControllerTest.queryCards_hasCards_showCarousel_invalidIconSource_noIcon (content:// card image and card icon; asserts the header icon drawable is null)."
     ],
     "completeness": [
      "Covers both SystemUI consumers of WalletCard images shown in the diff, the QS tile side-view drawable (card image) and the wallet screen's card image and card icon, with the same bitmap-only allow-list.",
      "Because the fix is an allow-list, any other or unknown Icon type is also rejected: the tile's default branch logs and sets null, and the controller's else branches set null.",
      "WalletView.getHeaderIcon consumes walletCard.getIcon() and is not changed itself. It is covered to the extent that QAWalletCardViewInfo.getIcon returns mIconDrawable, which the new test's null header icon assertion indicates.",
      "The material does not show how other Icon-bearing wallet objects are loaded in WalletScreenController or QuickAccessWalletController, for example the GetWalletCardsError icon or the tile icon from QuickAccessWalletClient.getTileIcon(). The tile's onWalletCardRetrievalError only sets the drawable to null and does not load the error icon.",
      "Per index search, the separate packages/apps/QuickAccessWallet plugin (WalletPanelViewController) also consumes WalletCard. Its drawable loading code is not in the material, so it cannot be determined here whether it needs an equivalent change."
     ],
     "behavior_changes": [
      "QS Wallet tile: cards whose image Icon is TYPE_RESOURCE or TYPE_DATA no longer get a sideViewCustomDrawable (previously loaded); URI types remain null as before.",
      "Wallet screen: card images of type TYPE_URI_ADAPTIVE_BITMAP, TYPE_RESOURCE or TYPE_DATA now render with a null card drawable.",
      "Wallet screen: card icons (header icon) of any non-bitmap type are no longer displayed; mIconDrawable is null.",
      "No new error codes or exceptions appear in the diff. For a card whose image and icon are invalid, the new test asserts that the carousel is visible, the empty state and error view are gone, and the header icon is null."
     ],
     "preconditions": [
      "A QuickAccessWalletService is bound by SystemUI and returns a GetWalletCardsResponse whose WalletCard carries a card image or card icon of a non-bitmap Icon type (for example created with Icon.createWithContentUri, as in the tests).",
      "For QuickAccessWalletTile: the tile is listening (handleSetListening(true) triggers mController.queryWalletCards) and the selected card index is valid.",
      "For WalletScreenController: queryWalletCards runs and QAWalletCardViewInfo is constructed on the background executor for each returned card."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base cac7c51cd276 in android-security-14.0.0_r18 (Change-Id I0033f6ba7ce7f9b9d592e7ce44e63ba804ceb348, cherry-picked from internal commits eb5a9b8a829d and 827947591b53); at android-security-14.0.0_r29, 8/8 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/base d6ad7f34eaf5 in android-security-15.0.0_r6. The code change is identical; only line offsets differ (tile hunk at @@ -243, controller hunk at @@ -330). At android-security-15.0.0_r17, 8/8 lines are present.",
      "16": "not affected (not listed by the bulletin); the patched region is present (8/8 lines) 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 production change is three small edits (the QuickAccessWalletTile switch and the two QAWalletCardViewInfo conditionals) plus a test-only accessor in WalletView. The new tile test is added as a new file under packages/SystemUI/multivalentTests."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact disclosure mechanism (which URI or resource target was abused, and with whose privileges it is resolved) is not described in the material; the bulletin only rates it High / ID.",
      "Whether Android 13 (listed as affected by the bulletin) received the same commit is not shown in the per-major facts.",
      "How WalletScreenController handles the GetWalletCardsError icon, and whether QuickAccessWalletController or the tile icon path load Icons by other means, is not shown.",
      "Whether the packages/apps/QuickAccessWallet plugin loads WalletCard Icons of non-bitmap types 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-2025-0089": {
     "cve": "CVE-2025-0089",
     "summary": "Adds a home-intent normalization step to ActivityStartInterceptor. ACTION_MAIN intents that carry CATEGORY_HOME/CATEGORY_SECONDARY_HOME in a non-standard format (extra categories, data/type, or an explicit component naming the default home) are rewritten into the canonical home intent and re-resolved before the activity is started.",
     "component": "platform/frameworks/base, WindowManager/ActivityTaskManager activity start (services/core/java/com/android/server/wm)",
     "weakness": "Missing validation/normalization of home intents: a home-category intent in a non-canonical format was not recognized by ActivityRecord.isHomeIntent, so the home activity could be started as a standard-type activity (activity type confusion).",
     "root_cause": "ActivityRecord.isHomeIntent only treats an intent as a home intent when it is ACTION_MAIN with exactly one category (HOME or SECONDARY_HOME) and no data/type. ActivityStarter had no step that put home-category intents into canonical form before they reached that check. So an intent such as ACTION_MAIN + CATEGORY_HOME + CATEGORY_LAUNCHER, one with data/type, or one with the default home component set explicitly was not classified as a home intent. Per the commit message, this let home activities be started as a standard-type activity.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityRecord.java: isHomeIntent(Intent) changed from private to package-private static, so the interceptor and tests can reuse the canonical home-intent definition.",
      "services/core/java/com/android/server/wm/ActivityStarter.java: the mInterceptor.intercept(...) call now passes request.componentSpecified, so the interceptor knows whether the caller set an explicit component.",
      "services/core/java/com/android/server/wm/ActivityStartInterceptor.java: intercept(...) gains a componentSpecified parameter, stored in the new field mComponentSpecified. A 9-arg overload that delegates with false is kept for tests. The new interceptHomeIfNeeded() is called after interceptLockedManagedProfileIfNeeded() and before the ActivityInterceptorCallback loop. If it returns true, intercept() returns true immediately.",
      "ActivityStartInterceptor.interceptHomeIfNeeded(): returns false if mService.mRootWindowContainer is null, or if the intent is not ACTION_MAIN with CATEGORY_HOME or CATEGORY_SECONDARY_HOME. If mComponentSpecified is set, it resolves the default home via mService.getHomeIntent() and mRootWindowContainer.resolveHomeActivity(mUserId, homeIntent), and returns false unless the intent's component equals that default home component. Then, if !ActivityRecord.isHomeIntent(mIntent) || mComponentSpecified, it calls normalizeHomeIntent() and marks the start as intercepted. When intercepted, it sets mCallingPid/mCallingUid to mRealCallingPid/mRealCallingUid and clears mResolvedType. It then re-resolves mRInfo via mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid, mRealCallingPid) and mAInfo via mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null).",
      "ActivityStartInterceptor.normalizeHomeIntent(): if there is more than one category, it removes all categories and re-adds only CATEGORY_SECONDARY_HOME (if it was present) or else CATEGORY_HOME. If type or data is set, it calls mIntent.setType(null) (Intent.setType also clears data (general platform knowledge)). If mComponentSpecified is set, it calls mIntent.setComponent(null). It always adds FLAG_ACTIVITY_NEW_TASK.",
      "services/tests/wmtests/.../ActivityStartInterceptorTest.java: the new testInterceptIncorrectHomeIntent checks that ACTION_MAIN + HOME + LAUNCHER is intercepted and afterwards satisfies ActivityRecord.isHomeIntent."
     ],
     "completeness": [
      "Covers home-category intents (HOME or SECONDARY_HOME) that go through the ActivityStarter interceptor with no explicit component. Also covers intents whose explicit component equals the resolved default home component for mUserId.",
      "Intents with an explicit component that names a home activity other than the resolved default home are left untouched (interceptHomeIfNeeded returns false, per the code comment 'Do nothing if the intent is not for the default home component'), so they still go through the standard path.",
      "The normalization only runs if the earlier interceptors in intercept() (such as quiet profile and locked managed profile, as shown in the diff) have not already intercepted the start. When normalization happens, intercept() returns true immediately, so the ActivityInterceptorCallback loop is not reached for that start.",
      "Normalization is skipped entirely when mService.mRootWindowContainer is null.",
      "interceptHomeIfNeeded dereferences the ActivityInfo returned by resolveHomeActivity without a null check. The material does not show whether that method can return null."
     ],
     "behavior_changes": [
      "Home-category intents with extra categories, data/type, or an explicit default-home component are rewritten in place: categories reduced to a single HOME/SECONDARY_HOME, type (and with it data) cleared, component cleared, FLAG_ACTIVITY_NEW_TASK added.",
      "When normalized, the start is treated as intercepted. callingPid/callingUid are replaced by the real calling pid/uid, resolvedType is cleared, and the intent is re-resolved with flags 0. Per existing ActivityStarter logic, intentGrants is set to null, so no grants from the original intent are applied.",
      "New Slog.w/Slog.d log lines under tag ActivityStartInterceptor when a home intent is normalized.",
      "ActivityStartInterceptor.intercept gains a boolean componentSpecified parameter. The old 9-arg overload is kept, for tests only."
     ],
     "preconditions": [
      "An activity start reaches ActivityStarter and the interceptor with an intent whose action is ACTION_MAIN and that has CATEGORY_HOME or CATEGORY_SECONDARY_HOME.",
      "The intent is not in canonical form (more than one category, or data/type present), or it carries an explicit component equal to the default home component resolved for the target user.",
      "mService.mRootWindowContainer is non-null, and no earlier interceptor in intercept() has already intercepted the start."
     ],
     "per_major": {
      "14": "same fix: commit ee0d85dd3f50 (RESTRICT AUTOMERGE cherry-pick, Change-Id I300f705798909014329f92118c266dbc04173e05) landed in android-security-14.0.0_r20. At android-security-14.0.0_r29, the code context shows the new imports, static isHomeIntent, the componentSpecified argument in ActivityStarter and the new test. 7/8 lines of the patched region are present.",
      "15": "resolved per bug_id at android-security-15.0.0_r10, and 7/8 lines of the patched region are present at android-security-15.0.0_r17. However, the Android 15 diff supplied in the material (f27918b39cff, mStrictModeCallbacks locking in ActivityManagerService) does not touch the home-intent code, so the exact Android 15 commit for this change is not shown.",
      "16": "not affected (the bulletin does not list Android 16). 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": [
      "Single commit. It depends on ActivityStarter.Request.componentSpecified, ActivityTaskManagerService.getHomeIntent(), RootWindowContainer.resolveHomeActivity(int, Intent), ActivityTaskSupervisor.resolveIntent(Intent, String, int, int, int, int) and resolveActivity(Intent, ResolveInfo, int, ProfilerInfo), with the signatures used in the diff. Adjust if a fork's signatures differ. Keep the 9-arg intercept overload, or update the existing ActivityStartInterceptorTest callers.",
      "The Android 15 commit shown in the material (f27918b39cff) is unrelated to this change. When backporting to a 15-based tree, find the cherry-pick of Change-Id I300f705798909014329f92118c266dbc04173e05 instead."
     ],
     "confidence": "high",
     "unknowns": [
      "Which home-specific checks were bypassed in practice when a home activity ran as a standard-type activity. The material only gives the outcome, from the commit message.",
      "Whether RootWindowContainer.resolveHomeActivity can return null in the path used by interceptHomeIfNeeded.",
      "Where the suspended-package and lock-task interceptors sit relative to interceptHomeIfNeeded inside intercept().",
      "Which of the 8 patched-region lines is missing in each latest security tag (7/8 present), and whether it matters functionally.",
      "The actual Android 15 commit carrying this change (the supplied Android 15 diff is a different change)."
     ],
     "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-26454": {
     "cve": "CVE-2025-26454",
     "summary": "ManagedProvisioning's DisclaimersParserImpl now validates each caller-supplied EXTRA_PROVISIONING_DISCLAIMER_CONTENT Uri before copying its content into the provisioning disclaimer cache. The scheme must be content or android.resource, and for content URIs the Binder caller must hold read permission. Any disclaimer that fails validation is skipped.",
     "component": "platform/packages/apps/ManagedProvisioning, provisioning parameter parser (DisclaimersParserImpl, disclaimer/terms content handling)",
     "weakness": "Missing permission/authorization check on a caller-supplied URI (confused deputy), plus missing URI scheme validation",
     "root_cause": "In src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java, the parse loop took the Uri from each disclaimer bundle (EXTRA_PROVISIONING_DISCLAIMER_CONTENT) and, after only a null check, passed it straight to saveDisclaimerContentIntoFile(uri, i). Nothing verified the URI scheme, or whether the app that supplied the provisioning extras was itself allowed to read that URI. ManagedProvisioning therefore copied the content into its disclaimer cache on the caller's behalf. The terms UI later uses that cache (the commit title names termsActivity). This let ManagedProvisioning act as a confused deputy for content the caller could not access directly.",
     "fix_analysis": [
      "Commit c341743982f0 (android-security-14.0.0_r21), DisclaimersParserImpl.java, parse loop: before saveDisclaimerContentIntoFile(uri, i), the loop now calls the new private method validateUriSchemeAndPermission(uri) inside a try/catch. On SecurityException it logs via ProvisionLogger.loge and calls `continue`, so that disclaimer is dropped and the loop moves on.",
      "Same commit, new DisclaimersParserImpl.validateUriSchemeAndPermission(Uri): any scheme other than \"android.resource\" (SCHEME_ANDROID_RESOURCE) or \"content\" (SCHEME_CONTENT) is rejected with a SecurityException. The method then calls mContext.checkUriPermission(uri, Binder.getCallingPid(), Binder.getCallingUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION) and throws SecurityException unless the result is PackageManager.PERMISSION_GRANTED.",
      "Commit 51723855c430 (android-security-14.0.0_r22), same function: wraps the checkUriPermission block in `if (Objects.equals(scheme, SCHEME_CONTENT))`. The read-permission check now applies only to content: URIs, and android.resource: URIs pass after the scheme check alone. The commit message cites the DevicePolicyManager documentation stating that FLAG_GRANT_READ_URI_PERMISSION is required only for content URIs.",
      "Net result at android-security-14.0.0_r29 (lines 122-144): the scheme allowlist is {content, android.resource}; content URIs are checked against the Binder calling pid/uid; a failure skips that one disclaimer rather than aborting parsing."
     ],
     "completeness": [
      "Covers the only place in the diff where disclaimer content URIs are consumed: the parse loop in DisclaimersParserImpl, before saveDisclaimerContentIntoFile. Both the scheme restriction and the caller permission check sit on that path.",
      "After the second commit, android.resource: URIs are accepted with no permission check at all; only content: URIs go through checkUriPermission. The commit message indicates this was intentional, matching the DPM documentation it cites.",
      "Permission is evaluated against Binder.getCallingPid()/Binder.getCallingUid(). The material does not show the call site of parse(), so the diff alone cannot confirm that these identify the provisioning initiator on every entry path.",
      "Validation failure is non-fatal: the offending disclaimer is skipped with a log entry, the remaining disclaimers are still processed, and provisioning is not rejected.",
      "No other ManagedProvisioning file is touched. The material shows no sibling URI-consuming path, so no such path can be judged either way."
     ],
     "behavior_changes": [
      "Disclaimer URIs with any scheme other than content: or android.resource: (for example file: or http:) are now dropped from the disclaimer list with a ProvisionLogger.loge entry. No error is returned to the caller.",
      "content: disclaimer URIs that the Binder caller lacks read permission for are dropped instead of being copied into the disclaimer cache.",
      "A new ProvisionLogger.logd line logs every disclaimer URI being validated.",
      "In android-security-14.0.0_r21 only (between the first and second commit), android.resource: URIs were also subject to checkUriPermission. From r22 onward they are accepted on scheme alone."
     ],
     "preconditions": [
      "An app supplies provisioning extras with EXTRA_PROVISIONING_DISCLAIMERS entries whose EXTRA_PROVISIONING_DISCLAIMER_CONTENT Uris reach the parse loop of DisclaimersParserImpl in ManagedProvisioning.",
      "Before the fix, the supplied Uri could use any scheme and could point to content the supplying app could not read itself.",
      "Per index search, the only semantic matches were DevicePolicyManager.createAndProvisionManagedProfile and provisionFullyManagedDevice, both system APIs gated by MANAGE_PROFILE_AND_DEVICE_OWNERS. Neither is shown to call DisclaimersParserImpl directly, so the material does not establish the concrete entry path."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin. No commit or tag material for Android 13 was provided, so how (or whether) the fix was applied there is unknown.",
      "14": "Fixed in two commits: c341743982f0 in android-security-14.0.0_r21 (scheme allowlist plus permission check) and 51723855c430 in android-security-14.0.0_r22 (permission check limited to the content scheme). The patched region is fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "Same fix: resolved at android-security-15.0.0_r10 by commit 03cadb65c0b6, which carries the identical second-step diff (base index 542bd7ce5 -> 9d8bb3b9d). The matching base suggests the first step was already in the 15 branch. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin). The fixed code is present (8/8 lines) at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). The fixed code is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply both commits in order (c341743982f0, then 51723855c430); the second edits only the function the first introduces. The combined change is confined to DisclaimersParserImpl.java and adds imports of android.os.Binder, android.content.Intent, android.content.pm.PackageManager and java.util.Objects. It targets the parse loop that calls saveDisclaimerContentIntoFile(uri, i).",
      "When backporting, confirm that parse() runs in a context where Binder.getCallingUid() reflects the provisioning initiator. Outside an incoming Binder transaction, Binder.getCallingUid() returns the process's own uid (general platform knowledge), so checkUriPermission would test ManagedProvisioning's own access instead of the caller's."
     ],
     "confidence": "high",
     "unknowns": [
      "The call site and thread/Binder context of DisclaimersParserImpl.parse (the material only shows the parser file), so it is not shown whether Binder.getCallingPid()/getCallingUid() always identify the provisioning initiator.",
      "How saveDisclaimerContentIntoFile opens the content (only its beginning is shown), and how termsActivity consumes the cached file (it is only named in the commit title).",
      "Whether ManagedProvisioning holds privileges that widen what it can read compared with the caller.",
      "How the fix was applied to Android 13, which the bulletin lists as affected.",
      "The reporter's test app and the referenced go/ and hsv links are not available, so the exact pre-fix impact is not shown beyond the bulletin's EoP/High rating."
     ],
     "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-32321": {
     "cve": "CVE-2025-32321",
     "summary": "In Settings' AccountTypePreferenceLoader.isSafeIntent, the fix keeps the existing check that the activity an intent resolves to belongs to the authenticator's uid. When the check passes, it now pins the intent to that resolved component with intent.setComponent(), so the intent becomes explicit and targets the validated activity.",
     "component": "platform/packages/apps/Settings, accounts (AccountTypePreferenceLoader)",
     "weakness": "Validation of an implicit intent without binding the result (check vs. launch mismatch): the uid check ran on a resolved activity, but the intent itself stayed implicit.",
     "root_cause": "isSafeIntent(PackageManager, Intent, String accountType) resolves the intent with pm.resolveActivityAsUser(intent, 0, user). It returns true when the resolved activity's applicationInfo.uid equals the uid of the authenticator package. It returned only a boolean and left the Intent unchanged, so an intent that passed the check stayed implicit. The commit message says the change prevents arbitrary intents from being launched from Settings.",
     "fix_analysis": [
      "src/com/android/settings/accounts/AccountTypePreferenceLoader.java:isSafeIntent: the line `return resolvedAppInfo.uid == authenticatorAppInf.uid;` is replaced by an if/else. When the uids match, the method calls intent.setComponent(resolvedActivityInfo.getComponentName()) and then returns true, which makes the intent explicit and points it at the resolved, authenticator-owned activity. Otherwise it returns false.",
      "The existing checks are unchanged: content-scheme intents are rejected, a null result from resolveActivityAsUser is rejected, and a NameNotFoundException for the authenticator package is logged and rejected.",
      "Single commit touching one file, with the same hunk content on 14, 15 and 16 (Change-Id I0e67f1258cb427c5b998e40a8a0c104af3ead042)."
     ],
     "completeness": [
      "Covers the uid-match path: any intent that passes isSafeIntent is now explicit and bound to the component that was validated.",
      "The fix assumes the caller launches the same Intent object it passed to isSafeIntent. The caller is not in the material, so the diff cannot confirm this.",
      "Only the authenticator-uid branch changes. The allowedFragments handling shown above the function is untouched.",
      "Resolution still uses flags 0 with resolveActivityAsUser. The fix does not change which activity is picked; it only pins the picked one."
     ],
     "behavior_changes": [
      "isSafeIntent now changes its Intent argument: on success, the intent gets an explicit ComponentName (the resolved authenticator activity).",
      "Intents that pass the check now target the specific resolved activity instead of staying implicit.",
      "No new error codes, logs or rejections. Intents that were rejected before are still rejected."
     ],
     "preconditions": [
      "An account authenticator whose account type supplies an Intent that Settings checks with isSafeIntent.",
      "The intent must resolve, at check time, to an activity with the authenticator's uid. The bulletin rates this as High severity, type EoP."
     ],
     "per_major": {
      "14": "same fix: commit fd19bf11ef08 in android-security-14.0.0_r22. The check finds 4/5 lines of the patched region at android-security-14.0.0_r29, and the code context there shows the setComponent fix.",
      "15": "same fix: commit bb6e84fd04fc in android-security-15.0.0_r10, same hunk content. The check finds 4/5 lines of the patched region at android-security-15.0.0_r17.",
      "16": "same fix: commit e985e294f15d in android-security-16.0.0_r1, same hunk content. The check finds 4/5 lines of the patched region at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The check finds 4/5 lines of the patched region at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The hunk is small, and its content is the same on 14, 15 and 16, differing only by a one-line offset. It should apply to branches that still have the `return resolvedAppInfo.uid == authenticatorAppInf.uid;` form. The bulletin lists Android 13 as affected, but the material includes no Android 13 commit.",
      "Make sure the caller starts the same Intent instance it passed to isSafeIntent, because the fix works by changing that instance."
     ],
     "confidence": "medium",
     "unknowns": [
      "The caller of isSafeIntent and how the Intent is launched afterwards.",
      "The exact way an implicit intent could end up launching an activity that was not validated. The commit message only mentions arbitrary intents and activities.",
      "The user flow that reaches this code (for example, which Settings screen loads the authenticator's preferences).",
      "Whether and how the fix was applied to Android 13, which the bulletin lists as affected."
     ],
     "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-32323": {
     "cve": "CVE-2025-32323",
     "summary": "DocumentsUI now sanitizes the calling app's label in Shared.getCallingAppName with TextUtils.makeSafeForPresentation (500-character limit, TRIM and SINGLE_LINE flags) and puts the name in double quotes in the open_tree_dialog_message string. This stops the label from reading as part of the folder-access confirmation text.",
     "component": "platform/packages/apps/DocumentsUI, Storage Access Framework picker (ACTION_OPEN_DOCUMENT_TREE confirmation dialog, Shared/ConfirmFragment)",
     "weakness": "Improper neutralization of app-controlled text shown in a security-relevant UI: an unsanitized application label of unbounded length, possibly containing newlines, is shown in a permission confirmation dialog (UI confusion/spoofing).",
     "root_cause": "Shared.getCallingAppName(Activity) in src/com/android/documentsui/base/Shared.java returned PackageManager.getApplicationLabel(ai).toString() unchanged. Only an empty label fell back to R.string.anonymous_application. Labels of any length and with any characters, including newlines, were passed through. According to the commit message, the name is presented in the ConfirmFragment. The open_tree_dialog_title and open_tree_dialog_message strings both contain an appName placeholder, and the message string placed the name inline in the sentence (\"This will let <appName> access current and future content stored in <dir>.\"), so the label could read as a continuation of the dialog text.",
     "fix_analysis": [
      "src/com/android/documentsui/base/Shared.java:getCallingAppName: the return type changes from String to CharSequence. After the empty-label check, the label goes through TextUtils.makeSafeForPresentation(result.toString(), 500, 0, SAFE_STRING_FLAG_TRIM | SAFE_STRING_FLAG_SINGLE_LINE). Per the new test, this caps the label at 500 characters and turns a newline into a space. Static imports for the two flags are added.",
      "res/values/strings.xml:open_tree_dialog_message: the %1$s appName placeholder is now in double quotes. open_tree_dialog_title is unchanged.",
      "src/com/android/documentsui/dirlist/DocumentsAdapter.java:Environment, DirectoryFragment.java:AdapterEnvironment, tests/common/.../TestEnvironment.java: the unused getCallingAppName() interface method and its two implementations are removed. The commit message describes this as cleanup to avoid false positives when looking for callers of the Shared function.",
      "tests/unit/com/android/documentsui/picker/ApplicationNameTest.kt (new): a parameterized test of Shared.getCallingAppName that covers a normal label, a label with a newline (expected 'label label'), a 600-character label (expected to be cut to 500) and an empty label (expected the anonymous string)."
     ],
     "completeness": [
      "The sanitization is applied inside the Shared.getCallingAppName helper, so every caller gets the sanitized value. The callers themselves (such as ConfirmFragment) are not shown in the material.",
      "Only open_tree_dialog_message gets quotation marks. open_tree_dialog_title (\"Allow %1$s to access files in %2$s?\", CHAR_LIMIT=80) still inserts the name without quotes and still accepts a label of up to 500 characters.",
      "The 500-character cap and the SINGLE_LINE flag limit length and line structure but do not otherwise restrict content (no SAFE_STRING_FLAG_FIRST_LINE; ellipsizeDip is 0). The material does not show whether 500 characters is short enough for the dialog layout.",
      "The material does not show any other DocumentsUI code that renders a third-party app label without going through Shared.getCallingAppName, and this commit does not change any."
     ],
     "behavior_changes": [
      "Application labels longer than 500 characters are cut to 500, newlines are replaced so the name stays on one line, and leading and trailing whitespace is trimmed.",
      "The dialog message now shows the app name in double quotes.",
      "Shared.getCallingAppName now returns CharSequence instead of String. Fork code that assigns the result to a String needs to be adjusted.",
      "DocumentsAdapter.Environment no longer declares getCallingAppName(). A downstream Environment implementation that overrides it will not compile until the @Override is removed."
     ],
     "preconditions": [
      "A third-party app with a chosen application label requests folder access through ACTION_OPEN_DOCUMENT_TREE, which leads to the confirmation dialog in DocumentsUI (per the commit message and the ApplicationNameTest comment).",
      "The label must be non-empty. An empty label uses the existing anonymous_application fallback."
     ],
     "per_major": {
      "14": "same fix: commit 1259beab2b8f in android-security-14.0.0_r22 (strings.xml quotes, Shared.getCallingAppName sanitization, Environment cleanup, new test). 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: commit f0336ada165f in android-security-15.0.0_r10. Same changes; strings.xml and DirectoryFragment differ only by line offsets. 7/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 35a75b245508 in android-security-16.0.0_r1. Same logic, but the Shared.java pre-image is different (extra RESTRICT_STORAGE_ACCESS_FRAMEWORK import, different offsets). 7/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 4/8 lines of the patched region are present at android-security-17.0.0_r1. The material does not show how the label is handled there."
     },
     "backport_notes": [
      "Single self-contained commit. The Shared.java and strings.xml hunks are the functional part. 14 and 15 share the Shared.java pre-image (index 5ac9de4d7), but 16 differs, so check the context when applying. The DocumentsAdapter, DirectoryFragment and TestEnvironment removals are optional cleanup. If a fork has other Environment implementers, remove their getCallingAppName override as well.",
      "The commit changes only res/values/strings.xml. Fork-specific or translated string resources are not part of it."
     ],
     "confidence": "high",
     "unknowns": [
      "The ConfirmFragment code that uses Shared.getCallingAppName and renders the dialog is not in the material, so the exact rendering path and any other callers are not shown.",
      "Why the bulletin rates this as EoP is not explained beyond the commit message's description of the label reading as a continuation of the dialog text.",
      "Whether localized open_tree_dialog_message strings get the quotation marks is not shown.",
      "The state of the corresponding code on Android 17 (4/8 patched lines present) and on Android 13, which the bulletin lists as affected but for which no commit is 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-32325": {
     "cve": "CVE-2025-32325",
     "summary": "Hardens Parcel::appendFrom in libbinder in three ways: it rejects appends that would overwrite existing objects in the destination, it only copies and acquires source objects that actually fall inside the requested [offset, offset+len) range (instead of a contiguous index range), and it keeps the object-ordering bookkeeping (mObjectsSorted, RPC mObjectPositions) accurate.",
     "component": "platform/frameworks/native, libs/binder (Parcel)",
     "weakness": "Missing validation in Parcel::appendFrom: it assumed the source objects were sorted and did not check for appending over existing objects, leading to inconsistent object offsets and object metadata",
     "root_cause": "In libs/binder/Parcel.cpp, the kernel binder path of Parcel::appendFrom chose which source objects to copy by recording the first and last index whose position fell in [offset, offset+len), then copying every entry in [firstIndex, lastIndex]. This implicitly assumed the source mObjects array was sorted by position. When it was not, entries outside the requested byte range were also copied. Their new offset (objects[i] - offset + startPos) could underflow or point past the appended bytes, and acquire_object was then called on a flat_binder_object read at that offset. Separately, appendFrom never checked whether the destination write range starting at mDataPos overlapped existing objects in the destination parcel, so an append at a rewound mDataPos could overwrite object data while the matching object entries stayed in place. In addition, mObjectsSorted was not cleared in some cases where ordering could be broken (appendFrom, and object writes not at the end of the data), and the RPC path added to mObjectPositions with push_back without keeping it sorted.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::appendFrom: before growing data, call validateReadData(mDataPos + len) and return its error if it is not OK, so an append over existing objects in the destination is refused (the new test expects PERMISSION_DENIED).",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (kernel path): replace the firstIndex/lastIndex range selection with a per-entry count (numObjects++ for each source object with pos >= offset and pos + sizeof(flat_binder_object) <= offset + len). The copy/acquire loop now goes through all source objects and skips any that fail the same range test, so only in-range objects are appended and acquired.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (kernel path): always set kernelFields->mObjectsSorted = false after appending objects.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (RPC path): insert newDataPos into rpcFields->mObjectPositions at std::upper_bound instead of using push_back, keeping the vector sorted.",
      "libs/binder/Parcel.cpp:Parcel::writeFileDescriptor (RPC path): likewise insert dataPos into rpcFields->mObjectPositions at std::upper_bound instead of using push_back.",
      "libs/binder/Parcel.cpp, object write path at the restart_write label (kernel path): after recording a new object, clear mObjectsSorted unless the write is at the end of the data (mObjectsSorted &= mDataPos == mDataSize).",
      "libs/binder/tests/binderParcelUnitTest.cpp: add TEST(Parcel, AppendOverObject). A parcel holds a dup'd FD, its position is rewound to 8, and appendFrom of a plain-int parcel must fail with PERMISSION_DENIED."
     ],
     "completeness": [
      "Covers both the kernel-binder and RPC object paths of Parcel::appendFrom, plus two other places that change object bookkeeping without preserving order: the object write at restart_write when not at the end, and RPC writeFileDescriptor.",
      "The overlap check (validateReadData(mDataPos + len)) is placed before growData, ahead of both paths. It relies on how validateReadData handles the object list, which the material does not show.",
      "The new unit test only covers the append-over-existing-object case. The unsorted-source selection change and the sorted-flag changes have no dedicated test in the diff (the commit cites binder_parcel_fuzzer).",
      "The Java Parcel and NDK AParcel_appendFrom wrappers are not changed. They inherit the new error return from the native function."
     ],
     "behavior_changes": [
      "Parcel::appendFrom now fails when the destination range starting at mDataPos would overlap existing objects. The error comes from validateReadData (PERMISSION_DENIED in the test). Previously such appends succeeded. The error propagates through AParcel_appendFrom (per index search) and any other wrapper.",
      "Source objects that lie outside [offset, offset+len) are no longer copied or acquired, even if their index lies between in-range objects.",
      "mObjectsSorted is cleared after every appendFrom that carries objects, and after object writes not at the end of the data.",
      "RPC mObjectPositions is kept sorted on insertion in appendFrom and writeFileDescriptor."
     ],
     "preconditions": [
      "A caller invokes Parcel::appendFrom on a destination Parcel. It is reachable from the NDK via AParcel_appendFrom in libs/binder/ndk/parcel.cpp (per index search), and from Java Parcel.appendFrom through JNI (general platform knowledge).",
      "For the object-selection issue: the source parcel's kernel mObjects array is not sorted by position, for example because objects were written at non-increasing positions after a setDataPosition rewind.",
      "For the overwrite issue: the destination parcel's mDataPos has been set before or inside existing object data (for example via setDataPosition) before appendFrom."
     ],
     "per_major": {
      "14": "Same fix: commit 7be10040534a in android-security-14.0.0_r22 (cherry-pick of the internal change, Change-Id I63715fdd81781aaf04f5fc0cb8bdb74c09d5d807). The test is placed after DebugReadAllFds. At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "Same fix: commit 96f0dd71bc07 in android-security-15.0.0_r10. Code changes are identical; only context differs (test placed after AppendWithFdPartial). At android-security-15.0.0_r17, 6/8 lines are present.",
      "16": "Same fix: commit 46e72b490edd in android-security-16.0.0_r1. Code changes are identical; context differs (the growData check uses mDataPos + len, and acquire_object takes a tagFds argument). At android-security-16.0.0_r8, 7/8 lines are present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "One commit per branch, with only context differences across 14/15/16: acquire_object takes tagFds on 16, and the growData condition uses mDataSize + len on 14/15 versus mDataPos + len on 16. The 14 commit places TEST(Parcel, AppendOverObject) after DebugReadAllFds.",
      "The fix depends on validateReadData being available and checking the destination's object list. Verify it is present in any older tree before backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of validateReadData, and whether it always returns PERMISSION_DENIED for the overlap case, are not shown (the test expects PERMISSION_DENIED).",
      "The material does not show why the latest security tags contain only 6/8 or 7/8 lines of the patched region (later refactoring vs. partial presence), or whether the fix is in the Android 17 tree.",
      "Whether the Android 14 test file at the fix point contains AppendWithFd* tests is not shown.",
      "The specific consequence reported for this bug is not described beyond the bulletin's EoP/High rating."
     ],
     "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-32326": {
     "cve": "CVE-2025-32326",
     "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is now launched only as a copy pinned to the component and package that the safety check resolved, instead of relaunching a copy of the original Intent after vetting.",
     "component": "platform/packages/apps/Settings, users / AppRestrictionsFragment (app restrictions UI)",
     "weakness": "Check/use mismatch on an untrusted Intent (the vetted resolution is not bound to the launched Intent; intent redirection from Settings)",
     "root_cause": "RestrictionsResultReceiver.onReceive reads an Intent from the broadcast result extras (results.getParcelable(Intent.EXTRA_RESTRICTIONS_INTENT)). assertSafeToStartCustomActivity resolved it with mPackageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) and threw a SecurityException if the resolved activity was not allowed, but it returned void. The caller then called startActivityForResult(new Intent(restrictionsIntent), ...) with a copy of the original Intent. Nothing tied what was launched to the ResolveInfo that passed the check.",
     "fix_analysis": [
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.assertSafeToStartCustomActivity now returns an Intent. After the existing resolveActivity call and the existing check pass, it builds vettedIntent = new Intent(intent), calls vettedIntent.setComponent(activityInfo.getComponentName()) and vettedIntent.setPackage(activityInfo.packageName), and returns it.",
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.onReceive stores the return value in a final Intent vettedIntent assigned inside the try block and passes vettedIntent to AppRestrictionsFragment.this.startActivityForResult instead of new Intent(restrictionsIntent). The existing catch of ActivityNotFoundException | SecurityException (log, EventLog 200688991, return) is unchanged."
     ],
     "completeness": [
      "Covers the EXTRA_RESTRICTIONS_INTENT launch in AppRestrictionsFragment.RestrictionsResultReceiver.onReceive, the only launch site shown in the material. The launched Intent's component and package are set from the vetted ActivityInfo.",
      "Other fields of the supplied Intent (action, data, extras, flags) are still copied into vettedIntent. The fix pins the target and does not sanitize the rest of the Intent.",
      "The resolve step (MATCH_DEFAULT_ONLY) and the existing check are unchanged by this fix (the check's body is not fully shown in the material).",
      "In the code shown at android-security-14.0.0_r29, 'final Intent vettedIntent = new Intent(intent);' sits at the top of assertSafeToStartCustomActivity (line 672). This layout differs from this commit."
     ],
     "behavior_changes": [
      "The custom restrictions activity is now started with an explicit ComponentName and package taken from the vetted resolution.",
      "No new error paths: failures still go through the existing ActivityNotFoundException/SecurityException catch, which logs and returns without calling startActivity."
     ],
     "preconditions": [
      "The app restrictions UI in Settings (AppRestrictionsFragment) receives a restrictions result, and the custom path is taken (invokeIfCustom is true and the fragment is resumed).",
      "The result bundle from the target app contains Intent.EXTRA_RESTRICTIONS_INTENT, which is app-supplied data.",
      "The supplied Intent must pass the existing resolveActivity and check for the launch to happen."
     ],
     "per_major": {
      "14": "same fix: commit 33a1cbf7bd0e in android-security-14.0.0_r22 (cherry-pick of d3e34060803c); 8/8 patched lines present at android-security-14.0.0_r29",
      "15": "same fix: commit e8a17485771e in android-security-15.0.0_r10, same diff content; 8/8 lines present at android-security-15.0.0_r17",
      "16": "same fix: commit e12156e2d805 in android-security-16.0.0_r1, same diff content; 8/8 lines present at android-security-16.0.0_r8",
      "17": "not affected (not listed by the bulletin); patched region present (8/8 lines) at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single self-contained change in one file; the same diff content was applied to 14, 15 and 16. Forks with a customized assertSafeToStartCustomActivity need to change its return type to Intent, return a copy with the component and package set from the vetted ActivityInfo, and have onReceive launch the returned copy.",
      "The code at android-security-14.0.0_r29 places the vettedIntent copy at a different spot in the function than this commit does, so compare carefully against the tree you are backporting from."
     ],
     "confidence": "high",
     "unknowns": [
      "The full body of the pre-existing check in assertSafeToStartCustomActivity.",
      "The concrete way the launched Intent could target something other than what was vetted.",
      "Why the code layout at android-security-14.0.0_r29 differs from this commit (whether a follow-up change exists is not shown).",
      "The contents of bugs 353680402 and 365739560."
     ],
     "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-32327": {
     "cve": "CVE-2025-32327",
     "summary": "PickerDbFacade no longer embeds unrecognized, caller-supplied projection column names into the SQL projection of queryMediaIdForApps; unknown columns are dropped from the SQL query and re-added as null-valued columns in a MatrixCursor built after the query.",
     "component": "platform/packages/providers/MediaProvider, Photo Picker database facade (PickerDbFacade)",
     "weakness": "Caller-controlled projection column name placed into the SQL projection (likely SQL injection; inferred from the diff, since getProjectionSimple's body is not shown and the bulletin only classifies it as EoP)",
     "root_cause": "In PickerDbFacade.getMediaStoreProjectionLocked, the default branch of the column switch handled any unsupported column name by emitting projection[i] = getProjectionSimple(\"NULL\", columns[i]), i.e. a projection entry built from NULL and the raw column string supplied by the caller. The column string was not validated before being placed in the projection array passed to SQLiteQueryBuilder.query in queryMediaIdForAppsInternal, so a caller-chosen string could become part of the SQL projection. The test change from \"testInvalidColumn\" to \"test invalid column\" (not a bare SQL identifier) suggests that column names previously reached the SQL layer.",
     "fix_analysis": [
      "PickerDbFacade.java:getMediaStoreProjectionLocked: the fixed-size String[] projection is replaced by an ArrayList<String>; the default branch no longer adds any projection entry for an unrecognized column (only the Log.w remains, with the comment updated to mention ACTION_GET_CONTENT takeover). Unknown column names therefore no longer reach the SQL statement.",
      "PickerDbFacade.java:queryMediaIdForAppsInternal (parameter renamed projection -> columns): after running qb.query with the filtered projection, it compares cursor.getColumnCount() to columns.length. If equal (or columns is null/empty) the raw cursor is returned unchanged. Otherwise it builds a MatrixCursor over the originally requested columns, iterates the result cursor, copies each row into a ContentValues via DatabaseUtils.cursorRowToContentValues, and adds a row with contentValues.get(column) for every requested column (null for the unknown ones), then closes the original cursor and returns the MatrixCursor. This keeps unknown columns present with null values, without involving SQL.",
      "PickerDbFacadeTest.java: the invalid column test now uses a name with spaces, asserts the column count is 2, that getLong on the unknown column is 0 and getString is null, and that DATE_TAKEN is still returned correctly."
     ],
     "completeness": [
      "Covers the only place in the diff where caller-provided column names were turned into SQL text: the default branch of getMediaStoreProjectionLocked, which feeds queryMediaIdForAppsInternal. All recognized PickerMediaColumns continue to map to fixed, internally defined projection strings.",
      "The MatrixCursor rebuild is driven purely by a count comparison (cursor.getColumnCount() == columns.length); since unknown columns are now always dropped, any mismatch means at least one unknown column was requested.",
      "The null check on columns inside queryMediaIdForAppsInternal comes after getMediaStoreProjectionLocked has already dereferenced columns.length, so it is effectively dead; callers passing null would still NPE as before (parameter is @NonNull).",
      "The material does not show other query paths in PickerDbFacade or their projection handling, so whether a similar pattern exists elsewhere cannot be judged from the diff."
     ],
     "behavior_changes": [
      "Unrecognized projection columns are no longer part of the executed SQL; the warning log 'Unexpected Picker column' is still emitted.",
      "When at least one unknown column is requested, the returned Cursor is a MatrixCursor rather than the SQLite cursor, with values copied through ContentValues; the test shows getLong returns 0 and getString returns null for the unknown column.",
      "When all requested columns are recognized, behavior is unchanged (the original cursor is returned directly)."
     ],
     "preconditions": [
      "A caller must reach PickerDbFacade.queryMediaIdForApps with a projection array containing at least one column name that is not one of the handled PickerMediaColumns constants.",
      "The fix comment ties this path to ACTION_GET_CONTENT takeover backward compatibility; the external entry point is not shown in the material.",
      "PickerDbFacade performs no permission checks itself (per the class comment); authorization is expected to occur in the calling MediaProvider code, which is not shown."
     ],
     "per_major": {
      "14": "same fix: commit 186cff75881c in android-security-14.0.0_r22 (RESTRICT AUTOMERGE cherry-pick chain), modifying queryMediaIdForAppsInternal and getMediaStoreProjectionLocked; at android-security-14.0.0_r29 7/8 lines of the patched region are present.",
      "15": "adapted fix: commit 3f48b77e7cb9 in android-security-15.0.0_r10 applies the same logic to queryMediaIdForAppsLocked and getMediaStoreProjectionLocked(columns, pickerSegmentType), which carry an extra pickerSegmentType parameter on 15; at android-security-15.0.0_r17 7/8 lines present.",
      "16": "not affected (not listed by the bulletin); at android-security-16.0.0_r8 6/8 lines of the patched region are present; no commit shown in the material.",
      "17": "not affected (not listed by the bulletin); at android-security-17.0.0_r1 6/8 lines of the patched region are present; no commit shown in the material."
     },
     "backport_notes": [
      "Single self-contained commit per branch; on branches where the query helper is named queryMediaIdForAppsLocked and getMediaStoreProjectionLocked takes pickerSegmentType (Android 15 shape), carry the extra parameter through as in 3f48b77e7cb9. The only new import is android.database.DatabaseUtils; the code at android-security-14.0.0_r29 shows it alongside the existing static import of com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar."
     ],
     "confidence": "medium",
     "unknowns": [
      "The body of getProjectionSimple is not in the material; that it produced a projection containing the raw column string unquoted, and thus that the flaw is SQL injection, is inferred from its usage and the test change.",
      "The external caller(s) of queryMediaIdForApps and how an app's projection reaches it (and what permission or URI checks sit in front of it) are not shown.",
      "Whether type reporting (getType) of the MatrixCursor differs from the original SQLite cursor is not shown.",
      "Whether other PickerDbFacade query methods have a comparable default-column branch is not visible in the material.",
      "The Android 16/17 code state is only shown as line-presence counts."
     ],
     "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-32330": {
     "cve": "CVE-2025-32330",
     "summary": "Replaces the LE audio broadcast (audio sharing) password generator in SettingsLib's LocalBluetoothLeBroadcast, which derived a 12-character hex string from UUID.randomUUID(), with a 16-character password drawn from an 89-symbol alphabet using java.security.SecureRandom.",
     "component": "platform/frameworks/base, packages/SettingsLib (Bluetooth LE audio broadcast / audio sharing)",
     "weakness": "Insufficient entropy / weak generated credential (password built from a small hex alphabet)",
     "root_cause": "LocalBluetoothLeBroadcast.generateRandomPassword() built the broadcast password by calling UUID.randomUUID().toString() and concatenating substring(0, 8) and substring(9, 13), i.e. 12 hexadecimal characters (16-symbol alphabet, at most 48 bits of entropy). The commit, tagged #security and titled 'Improve audio sharing password handling', replaces this generator (bulletin type ID, High).",
     "fix_analysis": [
      "packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java: adds constants VALID_PASSWORD_CHARACTERS (26 uppercase + 26 lowercase + 10 digits + 27 punctuation characters = 89 symbols) and PASSWORD_LENGTH = 16.",
      "LocalBluetoothLeBroadcast.generateRandomPassword(): rewritten to create a java.security.SecureRandom, loop PASSWORD_LENGTH times, pick random.nextInt(VALID_PASSWORD_CHARACTERS.length()) and append the chosen character to a StringBuilder, returning the 16-character string (about 103 bits of entropy versus about 48 before).",
      "Imports: java.security.SecureRandom added; on Android 14 the java.util.UUID import is removed and the method made private static. The Android 15 commit keeps the instance method signature and does not touch the UUID import.",
      "Commit message states this is a backport of ag/31824318 and that ag/31424453 (same bug) is superseded and need not be backported."
     ],
     "completeness": [
      "The fix changes only how the password string is generated; it does not change where or how the password is stored, displayed, or passed to BluetoothLeBroadcast (not shown in the diff).",
      "Only generateRandomPassword() is touched. The class still imports java.util.concurrent.ThreadLocalRandom and defines DEFAULT_CODE_MIN = 1000 / DEFAULT_CODE_MAX = 9999 (visible in the Android 14 r29 context); the material does not show what these are used for.",
      "No validation or minimum-strength check is added for passwords supplied by other paths, if any exist; the material does not show such paths."
     ],
     "behavior_changes": [
      "Generated broadcast passwords change from 12 hex characters to 16 characters that may include uppercase and lowercase letters, digits and punctuation such as ! @ # $ % ^ & * ( ) - _ = + [ ] { } | ; : , . < > ? /.",
      "Password generation now uses java.security.SecureRandom instead of UUID.randomUUID().",
      "Any UI or consumer that assumed a 12-character or hex-only password would observe the new format; the material does not show such consumers."
     ],
     "preconditions": [
      "The device supports LE audio broadcast and SettingsLib generates a broadcast password through LocalBluetoothLeBroadcast.generateRandomPassword(); the exact trigger path (when the method is invoked) is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit a9d98a5646f1 in android-security-14.0.0_r22 (cherry-pick, Merged-In If2ad220d...), method made private static and UUID import removed; 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix (adapted context): commit 5b10581d2a91 in android-security-15.0.0_r10, identical generator body, method stays non-static and the UUID import is not touched; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin); the patched region (8/8 lines) is 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": [
      "Single self-contained commit; the only new dependency is java.security.SecureRandom. Per the commit message, ag/31424453 for the same bug is superseded and need not be backported.",
      "If removing the java.util.UUID import as the Android 14 commit does, confirm UUID is not used elsewhere in the fork's LocalBluetoothLeBroadcast.java (the Android 15 commit does not remove it)."
     ],
     "confidence": "high",
     "unknowns": [
      "Where the generated password is consumed (e.g. how it is passed to BluetoothLeBroadcast or shown to the user) and whether any length/charset limit applies there.",
      "The concrete attack scenario behind the High/ID rating (the material gives only the weak-generation diff).",
      "What DEFAULT_CODE_MIN/DEFAULT_CODE_MAX and the retained ThreadLocalRandom import are used for in this class, and whether they produce any credential."
     ],
     "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-32333": {
     "cve": "CVE-2025-32333",
     "summary": "Settings' SpaActivity.startSpaActivityForApp now rejects an intent data scheme-specific part that does not match a Java-style package name regex before putting it into the SPA navigation destination string. For malformed values it returns false and launches no activity.",
     "component": "platform/packages/apps/Settings, SPA navigation, src/com/android/settings/spa/SpaActivity.kt",
     "weakness": "Improper input validation: a string taken from intent data (the URI scheme-specific part) was put into a '/'-delimited destination string without validation.",
     "root_cause": "In SpaActivity.kt, Context.startSpaActivityForApp(destinationPrefix, intent) used intent.data?.schemeSpecificPart as the package name. It then built the destination \"$destinationPrefix/$packageName/${UserHandle.myUserId()}\" without checking that value. A scheme-specific part containing characters such as '/' or '#' would change the shape of the destination string passed to startSpaActivity, instead of standing in as a plain package name. The added test uses \"package:package.name/10#\". How the destination is then parsed is not shown.",
     "fix_analysis": [
      "src/com/android/settings/spa/SpaActivity.kt: Context.startSpaActivityForApp: the package name is now read via intent.data?.schemeSpecificPart?.takeIf { Regex(\"^([a-zA-Z]\\\\w*\\\\.)*[a-zA-Z]\\\\w*$\").matches(it) }. If the value is absent or does not fully match (dot-separated segments, each starting with a letter followed by [A-Za-z0-9_]), the function returns false before calling startSpaActivity. No destination string is built and no activity is started.",
      "tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt: adds startSpaActivityForApp_hasMalformedPackageName. It passes data \"package:package.name/10#\" and verifies that context.startActivity is never called."
     ],
     "completeness": [
      "The check is applied only in startSpaActivityForApp. In the shown excerpt, that is the only function that builds a destination from intent data. Other callers of startSpaActivity in Settings are not shown.",
      "Context.startSpaActivity(destination) itself is still unvalidated and forwards whatever destination string it receives. Any caller that builds a destination from other external input (none is shown in the material) is not covered by this fix.",
      "The user segment comes from UserHandle.myUserId(), not from the intent. destinationPrefix is a caller-supplied parameter whose origin is not shown.",
      "The validation only checks format (regex). It does not check that the package exists or is visible to the caller."
     ],
     "behavior_changes": [
      "startSpaActivityForApp returns false and launches nothing when the scheme-specific part: contains any character outside [A-Za-z0-9_.]; has a segment starting with a digit or underscore; has empty segments (leading, trailing or double dots); or is empty. Before the fix, such values were forwarded to startSpaActivity.",
      "Callers now get false for such intents. Their fallback behavior is not shown in the material."
     ],
     "preconditions": [
      "A Settings code path must call Context.startSpaActivityForApp with an Intent whose data URI scheme-specific part is controlled by the sender. The specific callers are not shown in the material.",
      "The Intent must carry a data URI. A null schemeSpecificPart already returned false before the fix."
     ],
     "per_major": {
      "14": "same fix: commit 591ea09a63e5 in android-security-14.0.0_r22 (subject says 'for udc'; cherry-picked from two upstream changes). At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "not affected per the bulletin. At android-security-15.0.0_r17, 5/8 lines of the patched region are present. The material does not show the code in this branch.",
      "16": "not affected per the bulletin (the bulletin lists 16-next, not 16). At android-security-16.0.0_r8, 5/8 lines are present. The material does not show the code in this branch.",
      "17": "not affected per the bulletin. At android-security-17.0.0_r1, 5/8 lines are present. The material does not show the code in this branch."
     },
     "backport_notes": [
      "A one-line Kotlin change in SpaActivity.kt plus a unit test. The fix uses only kotlin.text.Regex.",
      "The test needs org.mockito.Mockito.any/never imports in tests/spa_unit."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which Settings entry points call startSpaActivityForApp, and with what destinationPrefix.",
      "How startSpaActivity/BrowseActivity parses the destination route, and so what a malformed segment could change.",
      "What the two upstream changes in the cherry-pick chain contain, and what code the Android 15/16/17 branches have in this region."
     ],
     "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-32347": {
     "cve": "CVE-2025-32347",
     "summary": "BiometricEnrollIntroduction now strips the MultiBiometricEnrollHelper extras (EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE, EXTRA_ENROLL_AFTER_FINGERPRINT) from its launch intent in onStart() whenever the calling package is not Settings itself, so PendingIntent extras supplied by external packages are dropped during enrollment.",
     "component": "platform/packages/apps/Settings, biometrics enrollment (BiometricEnrollIntroduction, shared by the face and fingerprint enroll introduction screens)",
     "weakness": "Missing validation of caller-supplied intent extras (bulletin type: EoP)",
     "root_cause": "Before the fix, BiometricEnrollIntroduction did not check who launched it before keeping the MultiBiometricEnrollHelper extras in its launch intent. These are EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT, which the tests populate with PendingIntents, and EXTRA_SKIP_PENDING_ENROLL. These extras were removed only in specific flows (removeEnrollNextBiometric / removeEnrollNextBiometricIfSkipEnroll), so a package other than Settings could launch the introduction activity with them still in the intent. The helper methods removeEnrollNextBiometricIfSkipEnroll and handleBiometricResultSkipOrFinished were also protected. The material does not show how the extras are used afterwards.",
     "fix_analysis": [
      "src/com/android/settings/biometrics/BiometricEnrollIntroduction.java:onStart (new override): after super.onStart(), it checks whether getPackageName() equals getCallingPackage(). If not, including when getCallingPackage() returns null, it loops over List.of(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE, EXTRA_ENROLL_AFTER_FINGERPRINT) and calls getIntent().removeExtra(key) on each.",
      "src/com/android/settings/biometrics/BiometricEnrollIntroduction.java: removeEnrollNextBiometricIfSkipEnroll and handleBiometricResultSkipOrFinished changed from protected to private.",
      "Adds import java.util.List. The Android 15 and 16 commits also add Robolectric tests named drops_pendingIntents to FaceEnrollIntroductionTest and FingerprintEnrollIntroductionTest, plus the helper newExternalPendingIntent. The tests assert that the three extras are absent after start. The Android 14 commit changes only BiometricEnrollIntroduction.java (1 file)."
     ],
     "completeness": [
      "The check lives in the shared base class BiometricEnrollIntroduction, and the tests cover both the face and fingerprint introduction activities.",
      "Only the three listed extras are dropped. Other extras in the launch intent are not filtered by this change.",
      "The filter runs in onStart(). The material does not show whether any of these extras are already used in onCreate(), before onStart() runs.",
      "The trust decision rests only on getCallingPackage() matching Settings' own package name. It does not check who created the PendingIntent.",
      "Other enrollment activities that might read MultiBiometricEnrollHelper extras are not changed by this commit. The material does not show whether they read them."
     ],
     "behavior_changes": [
      "When the enroll introduction activity is started by a package other than Settings, or with no calling package (getCallingPackage() == null), the EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT extras are silently removed from the intent in onStart. Any behaviour driven by these extras is therefore unavailable for such launches.",
      "removeEnrollNextBiometricIfSkipEnroll and handleBiometricResultSkipOrFinished are now private. Subclasses that override or call them will no longer compile."
     ],
     "preconditions": [
      "A package other than Settings must be able to launch the face or fingerprint enroll introduction activity. Whether these activities are exported, and any launch gating, are not shown in the material.",
      "The launch intent carries MultiBiometricEnrollHelper extras: PendingIntents in EXTRA_ENROLL_AFTER_FACE / EXTRA_ENROLL_AFTER_FINGERPRINT, or EXTRA_SKIP_PENDING_ENROLL."
     ],
     "per_major": {
      "14": "same fix: commit 214b82f7097b in android-security-14.0.0_r22, source change only (no test files). At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "same fix plus tests: commit 25cfacbe5ac2 in android-security-15.0.0_r10. Same onStart logic at different line offsets, plus additions to FaceEnrollIntroductionTest and FingerprintEnrollIntroductionTest. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "same fix plus tests: commit d50de07930ee in android-security-16.0.0_r1, same logic and tests. 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, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "A single self-contained commit touching one source file: apply the onStart() override and the visibility change to BiometricEnrollIntroduction.java. Check that any fork-specific subclasses do not override or call the two methods that are now private.",
      "The bulletin lists Android 13 as affected, but the material gives no Android 13 commit or tag status."
     ],
     "confidence": "medium",
     "unknowns": [
      "Where the PendingIntent extras are used or sent (the consuming code is not in the material).",
      "Whether the face and fingerprint enroll introduction activities are exported or otherwise reachable by third-party apps, and under what permission.",
      "Whether any of the three extras are read in onCreate(), before the new onStart() filter runs.",
      "How MultiBiometricEnrollHelper launches the introduction activity, and so whether getCallingPackage() matches Settings for legitimate internal launches (relevant for forks that change the launch path).",
      "Which line of the patched region is missing at each latest tag (7/8 lines present).",
      "Status and commit for Android 13, which the bulletin lists as affected."
     ],
     "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-32349": {
     "cve": "CVE-2025-32349",
     "summary": "WindowManager now forces the default Animation_Toast style on TYPE_TOAST windows from sessions without mCanAddInternalSystemWindow, and uses a fixed short alpha fade instead of the window's own exit animation when a non-system overlay is forcibly hidden, so an app can no longer control how long its overlay stays on screen during a forced hide.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm: DisplayPolicy, WindowState, WindowStateAnimator)",
     "weakness": "Missing trust restriction on caller-supplied window animation attributes (an untrusted app controlled the exit animation, and thus the visible duration, of a window the system was forcing hidden)",
     "root_cause": "In DisplayPolicy.adjustWindowParamsLw, the TYPE_TOAST case clamped hideTimeoutMilliseconds to TOAST_WINDOW_TIMEOUT and set FLAG_NOT_TOUCHABLE, but left attrs.windowAnimations as the app supplied it. Per the commit message, legacy apps and deprecated custom-view toasts show TYPE_TOAST windows from their own process, so an untrusted app could attach arbitrary WindowManager.LayoutParams#windowAnimations. WindowState.setForceHideNonSystemOverlayWindowIfNeeded(true) applies to system alert window types and TYPE_TOAST from sessions lacking mCanAddInternalSystemWindow. When it ran for such a window it called hide(true /* doAnimation */, true). Per the second commit message, the window's specified exit animation could then be used, so the forced hide could run for a caller-chosen duration instead of a short, system-defined one. The commit message notes that regular text toasts (target API 30+) are drawn by SystemUI, which has the internal permission and is unaffected.",
     "fix_analysis": [
      "Commit a93c95a17ae4 (Do not allow custom animation of untrusted Toast), DisplayPolicy.java:adjustWindowParamsLw, case TYPE_TOAST: after forcing FLAG_NOT_TOUCHABLE, adds `if (!win.mSession.mCanAddInternalSystemWindow) attrs.windowAnimations = R.style.Animation_Toast;`. Any TYPE_TOAST window from a session without the internal-system-window capability gets the stock toast animation style. The commit message cites config_longAnimTime=500ms for hiding.",
      "Commit b8401310e97f (Use consistent animation when forcibly hiding non system overlay), WindowState.java: adds the package-private getter `isForceHiddenNonSystemOverlayWindow()`, which returns the existing mForceHideNonSystemOverlayWindow field. setForceHideNonSystemOverlayWindowIfNeeded sets that field for system-alert-type and TYPE_TOAST windows from non-internal sessions, excluding system application overlays with mCanCreateSystemApplicationOverlay.",
      "Same commit, WindowStateAnimator.java, in the animation-selection code containing the `switch (transit)` / TRANSIT_ENTER cases: inserts `else if (!isEntrance && mWin.isForceHiddenNonSystemOverlayWindow())` after a branch that loads an animation via AnimationUtils.loadAnimation(mContext, anim) and before the `switch (transit)` branch. In that case it builds `new AlphaAnimation(1f, 0f)` with duration `config_shortAnimTime`. Also adds the AlphaAnimation import."
     ],
     "completeness": [
      "Commit 1 only touches the TYPE_TOAST case of adjustWindowParamsLw; other window types keep their caller-supplied windowAnimations. The gate is the session's mCanAddInternalSystemWindow, so trusted sessions keep custom toast animations.",
      "Commit 2 applies only to the exit direction (!isEntrance) and only while mForceHideNonSystemOverlayWindow is true, i.e. for the window types handled by setForceHideNonSystemOverlayWindowIfNeeded. For non-toast overlays (e.g. system alert windows), entrance animations and non-forced hides are not changed by these commits. Untrusted toasts are covered by commit 1 in all cases.",
      "In commit 2 the new branch is an else-if after the branch that calls AnimationUtils.loadAnimation(mContext, anim), so an animation loaded by that preceding branch still takes precedence over the fixed fade. That branch's condition is not shown.",
      "The existing hideTimeoutMilliseconds clamp to TOAST_WINDOW_TIMEOUT is unchanged; the fix adds the animation dimension that was not previously constrained.",
      "setHiddenWhileSuspended (a sibling path in WindowState, visible in the context) is not changed by either commit. The material does not show whether it has the same animation exposure."
     ],
     "behavior_changes": [
      "TYPE_TOAST windows added by sessions without mCanAddInternalSystemWindow always animate with R.style.Animation_Toast; adjustWindowParamsLw overwrites any WindowManager.LayoutParams#windowAnimations set by the app.",
      "When a non-system overlay or untrusted toast is forcibly hidden via setForceHideNonSystemOverlayWindowIfNeeded, it fades out with a fixed AlphaAnimation(1f, 0f) lasting config_shortAnimTime, unless the preceding loadAnimation branch applies. The commit message says this avoids jump cuts and inconsistent durations.",
      "No new error paths or exceptions; the changes only affect animation selection."
     ],
     "preconditions": [
      "An app whose WindowManager session lacks mCanAddInternalSystemWindow adds a TYPE_TOAST window directly (legacy toast or deprecated custom-view toast rendered in the app's own process, per the commit message) or a system-alert-type overlay, with custom windowAnimations in its LayoutParams.",
      "Something triggers WindowState.setForceHideNonSystemOverlayWindowIfNeeded(true) for that window (the commit test launches Settings). This calls hide(true /* doAnimation */, true /* requestAnim */)."
     ],
     "per_major": {
      "14": "same fix: both commits (a93c95a17ae4 DisplayPolicy; b8401310e97f WindowState + WindowStateAnimator) in android-security-14.0.0_r22; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix for the DisplayPolicy commit: 394acf2aa1da in android-security-15.0.0_r10, identical hunk at a different offset (line ~974); 4/4 patched lines present at android-security-15.0.0_r17. The material does not show the WindowStateAnimator/WindowState commit for this branch.",
      "16": "same fix for the DisplayPolicy commit: 3a76075721bb in android-security-16.0.0_r1, identical hunk at line ~985; 4/4 patched lines present at android-security-16.0.0_r8. The material does not show the WindowStateAnimator/WindowState commit for this branch.",
      "17": "not affected per the bulletin (not listed); the patched DisplayPolicy region is present 4/4 at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Two commits under bug 408215749. The DisplayPolicy change is self-contained; it relies on R.style.Animation_Toast and win.mSession.mCanAddInternalSystemWindow, both already present. The WindowStateAnimator change depends on the new WindowState.isForceHiddenNonSystemOverlayWindow() getter and the existing mForceHideNonSystemOverlayWindow field, and must sit before the `switch (transit)` branch.",
      "Only the DisplayPolicy commit is shown for Android 15/16; check whether the WindowStateAnimator commit is present on the target branch before considering the backport complete."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the WindowState/WindowStateAnimator commit (b8401310e97f) was applied on Android 15 and 16; only the DisplayPolicy commit is shown for those branches.",
      "Which system flows call setForceHideNonSystemOverlayWindowIfNeeded(true), beyond the Settings launch in the commit test; the callers are not in the material.",
      "The exact name of the enclosing method in WindowStateAnimator and the condition of the branch that precedes the new else-if.",
      "How the `switch (transit)` branch resolves animations from window attributes; the material only shows its start.",
      "Whether the setHiddenWhileSuspended path has a comparable animation exposure; the material does not show its hide call or animation handling."
     ],
     "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-32350": {
     "cve": "CVE-2025-32350",
     "summary": "Removes android:exported=\"true\" from SystemUI's ControlsActivity manifest entry and adds SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS to the window of the dialog created by ControlsSettingsDialogManager, so non-system windows are not drawn on top of it.",
     "component": "platform/frameworks/base, SystemUI device controls (packages/SystemUI, controls/settings)",
     "weakness": "Improperly exported activity combined with a system settings dialog lacking the overlay-hiding flag (overlay exposure)",
     "root_cause": "In packages/SystemUI/AndroidManifest.xml, the commit subject identifies the activity as ControlsActivity. The hunk shows launchMode singleInstance and visibleToInstantApps, and the element sits right before .wallet.ui.WalletActivity. It was declared android:exported=\"true\", so it was exposed to other apps. In addition, the AlertDialog built in ControlsSettingsDialogManager.kt (the dialog that prompts the user to enable Settings.Secure.LOCKSCREEN_SHOW_CONTROLS / LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS) was shown without SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, so non-system windows could be drawn over this prompt.",
     "fix_analysis": [
      "packages/SystemUI/AndroidManifest.xml: drops the android:exported=\"true\" attribute from the ControlsActivity <activity> element (per the commit subject). The hunk does not show whether the element has an intent-filter, so the resulting exported state is inferred from the commit subject ('Unexport ControlsActivity').",
      "packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt (dialog construction in the implementation class, the path exercised by maybeShowDialog): after the AlertDialog `d` is built and before SystemUIDialog.registerDismissListener/setDialogSize/setShowForAllUsers, the fix adds `d.window?.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)` and imports android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS.",
      "packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt: a new test, dialogHidesNonSystemOverlayWindows, sets attempts=0 and both settings to false, calls underTest.maybeShowDialog(context, completedRunnable), and asserts that the dialog window's attributes.privateFlags contain SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS."
     ],
     "completeness": [
      "Covers the two things in the diff: the one manifest activity is unexported, and the single dialog created in ControlsSettingsDialogManager.kt gets the overlay-hiding flag.",
      "No permission or caller check is added to the activity; the fix relies on removing the exported attribute. The material does not show whether the activity has an intent-filter elsewhere in the element (the hunk only shows trailing attributes), which would matter for the exported default.",
      "Only this dialog is hardened. The material does not show other controls-related dialogs or activities, so whether they already carry the flag is unknown from the diff."
     ],
     "behavior_changes": [
      "ControlsActivity is no longer declared exported, so third-party apps are expected to lose the ability to launch it directly. The exact failure mode for external callers is not shown in the material.",
      "Per the commit message, non-system windows are not drawn on top of the controls settings dialog while it is displayed.",
      "No change to the dialog's content, button handling, or attempt counting."
     ],
     "preconditions": [
      "Before the fix, ControlsActivity in SystemUI was declared android:exported=\"true\" (no permission attribute appears in the visible hunk).",
      "The dialog path is reached via ControlsSettingsDialogManager.maybeShowDialog when at least one of LOCKSCREEN_SHOW_CONTROLS / LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS is false and the stored attempt count is below MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG (per the interface doc and the new test's setup).",
      "Overlay exposure additionally required an app able to draw non-system windows."
     ],
     "per_major": {
      "14": "same fix: b08dcc947008 in android-security-14.0.0_r22 (linked by subject to the Android 16 reference d6b86833ee06). The manifest and Kotlin changes are identical. The only differences are the R import (com.android.systemui.R) and the test file also adding an androidx.test.ext.junit.runners.AndroidJUnit4 import. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: 47424521c49b in android-security-15.0.0_r10 (matched by Change-Id I6280b501b799596099935dc61ecef0b431dd8333); uses com.android.systemui.res.R. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: d6b86833ee06 in android-security-16.0.0_r1 (matched by bug id 404256832); same content as 15. 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 only version-specific detail is the SystemUI R import (com.android.systemui.R on 14 vs com.android.systemui.res.R on 15/16). When applying the manifest hunk, find the activity by the android:launchMode=\"singleInstance\" + android:visibleToInstantApps=\"true\" element immediately before .wallet.ui.WalletActivity, because the hunk context does not include the android:name line.",
      "The new test reads dialog.window.attributes.privateFlags and uses the existing TestableAlertDialog helper in the test tree. It is optional for the runtime fix."
     ],
     "confidence": "high",
     "unknowns": [
      "The manifest hunk does not show the android:name of the modified activity, whether it has an intent-filter, or whether it has a permission attribute. The identification as ControlsActivity comes from the commit subject.",
      "The material does not show what ControlsActivity does when launched externally, so the concrete EoP impact of the exported state is not shown.",
      "The material does not show the exact failure an external caller now gets when trying to launch the activity.",
      "The Android 14 commit is linked to the reference only by subject (probable match), not by Change-Id or bug id, although its content is identical apart from the imports."
     ],
     "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-48522": {
     "cve": "CVE-2025-48522",
     "summary": "AssociationRequest.Builder.setDisplayName now rejects display names longer than 1024 characters with an IllegalArgumentException. Before, it accepted an app-supplied string of any length, and that string is shown in the CompanionDeviceManager confirmation UI.",
     "component": "platform/frameworks/base, CompanionDeviceManager (core/java/android/companion/AssociationRequest.java)",
     "weakness": "missing input length validation (missing bounds check on an app-controlled string)",
     "root_cause": "AssociationRequest.Builder.setDisplayName only called requireNonNull on the caller-supplied CharSequence and stored it with no length limit. The class javadoc in the code context describes this display name as \"the Display name of the device to be shown in the CDM confirmation UI\" and says it must be non-null for self-managed associations. An application could therefore pass a display name of any length into an association request that the system shows to the user. The bulletin rates the issue as a High severity EoP. The material does not show what the oversized name actually causes downstream.",
     "fix_analysis": [
      "core/java/android/companion/AssociationRequest.java: adds a class constant DISPLAY_NAME_LENGTH_LIMIT = 1024.",
      "core/java/android/companion/AssociationRequest.java:Builder.setDisplayName: after checkNotUsed() and mDisplayName = requireNonNull(displayName), throws IllegalArgumentException(\"Length of the display name must be at most 1024 characters\") when displayName.length() > DISPLAY_NAME_LENGTH_LIMIT. The assignment to mDisplayName happens before the check, so the Builder field still holds the oversized value when the exception is thrown.",
      "Single-commit fix that touches only this one file."
     ],
     "completeness": [
      "Covers only the public Builder.setDisplayName path.",
      "mDisplayName is assigned before the length check. The material does not show build(), so it is not shown whether a caller that catches the exception could still build a request carrying the oversized value.",
      "The private AssociationRequest constructor (line 257 in the context) takes @Nullable CharSequence displayName with no length check, and the class implements Parcelable. The material does not show whether the unparceling path re-validates the length, so as shown the check runs only in the Builder.",
      "No change to the system-side CDM service or to the confirmation UI that consumes the display name."
     ],
     "behavior_changes": [
      "AssociationRequest.Builder.setDisplayName throws IllegalArgumentException for CharSequence inputs longer than 1024 characters. Before the fix, any length was accepted. Apps that pass longer names will get the exception from setDisplayName unless they catch it."
     ],
     "preconditions": [
      "Caller is an application that builds an AssociationRequest via AssociationRequest.Builder and calls setDisplayName with a display name over 1024 characters. Per the class javadoc, setDisplayName is required (non-null) for self-managed associations.",
      "The request is submitted to CompanionDeviceManager so the display name reaches the CDM confirmation UI."
     ],
     "per_major": {
      "14": "same fix: aa93bf9db684 in android-security-14.0.0_r22. 4/4 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: bdad29b85a6c in android-security-15.0.0_r10, identical diff with the same file index as 14. 4/4 lines present at android-security-15.0.0_r17.",
      "16": "same fix: eb3cf182f929 in android-security-16.0.0_r1. Same logic at different line offsets: the constant sits before the device icon field (around line 274) and the check is around line 503. 4/4 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 3/4 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Self-contained 7-line Java addition with no dependencies shown. Insert the check in Builder.setDisplayName after the checkNotUsed()/requireNonNull pair. The 14, 15 and 16 diffs differ only in line offsets."
     ],
     "confidence": "high",
     "unknowns": [
      "What an oversized display name actually caused in the CDM UI or service (why this was an EoP) is not shown in the material.",
      "Whether AssociationRequest's Parcelable CREATOR, the private constructor path or build() enforces the same 1024-character limit is not shown.",
      "Whether a Builder that keeps an oversized mDisplayName after a caught exception can still produce a request is not shown.",
      "Why 1024 was chosen, and whether a CTS test was added, are not in the material."
     ],
     "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-48523": {
     "cve": "CVE-2025-48523",
     "summary": "Removes the branch in SelectAccountActivity.onCreate that silently returned the sole writable account as RESULT_OK. When one or more accounts exist, the account selection dialog is now always shown, so the vCard import flow no longer picks the account automatically.",
     "component": "platform/packages/apps/Contacts, vCard import (src/com/android/contacts/vcard/SelectAccountActivity.java)",
     "weakness": "Missing user confirmation / consent step (automatic action without user interaction) on a flow that writes contact data",
     "root_cause": "SelectAccountActivity.onCreate fetched the writable accounts with AccountTypeManager.blockForWritableAccounts(). When exactly one account existed, it immediately built a result Intent with ACCOUNT_NAME, ACCOUNT_TYPE and DATA_SET, called setResult(RESULT_OK, intent) and finish(), without showing any UI. On single-account devices the caller therefore received a selected account with no user interaction. The commit message says the security team recommended this change to prevent auto-importing when a single account exists (b/388032224).",
     "fix_analysis": [
      "src/com/android/contacts/vcard/SelectAccountActivity.java:onCreate: deletes the `else if (accountList.size() == 1)` branch (9 lines) that auto-selected accountList.get(0), set RESULT_OK and finished.",
      "For any accountList.size() >= 1, control now falls through to the existing path. That path builds an AccountSelectionUtil.AccountSelectedListener over accountList and calls showDialog(R.string.import_from_vcf_file). The result Intent with RESULT_OK is only produced inside the listener's onClick, after the user selects an entry. A CancelListener that calls finish() is defined in the class; the shown context does not show how it is attached to the dialog.",
      "The size()==0 branch (logs 'Account does not exist' and calls finish()) is unchanged."
     ],
     "completeness": [
      "Covers only the single-account auto-selection in SelectAccountActivity.onCreate; this is the only code path changed.",
      "The zero-account path still finishes with no result and no dialog, unchanged by the fix.",
      "The comment at lines 57-60 of onCreate still says 'just one account -> use the account without asking the user', which no longer matches the code.",
      "The material does not show whether other entry points of the vCard import flow (for example callers that skip SelectAccountActivity) were reviewed; the diff touches only this activity."
     ],
     "behavior_changes": [
      "On devices with exactly one writable account, the vCard import flow now shows the account selection dialog (resource R.string.import_from_vcf_file) with a single entry instead of returning the account automatically.",
      "The RESULT_OK account is only returned after the user selects the entry in the dialog.",
      "The log line 'The number of available accounts: 1' is now emitted for the single-account case."
     ],
     "preconditions": [
      "SelectAccountActivity is started as part of the Contacts vCard import flow (the dialog string is import_from_vcf_file).",
      "AccountTypeManager.blockForWritableAccounts() returns exactly one account on the device.",
      "The material does not show how an untrusted caller reaches this activity (exported status, intent filters, or the calling import activity)."
     ],
     "per_major": {
      "14": "same fix: commit 6104890ea916 in android-security-14.0.0_r22 (Change-Id I83f046c24d45e076aa783e5551b42dcbaacd0ad6). At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "same fix: commit 7c8dbcffbc93 in android-security-15.0.0_r10, identical diff. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "same fix: commit c314edb9bde8 in android-security-16.0.0_r1, identical diff. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "not affected: the bulletin does not list it. At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The fix is a single commit that deletes 9 lines in SelectAccountActivity.onCreate. The diff is identical across 14, 15 and 16 (same blob change 8ead5fab3..797fc8670), so it should apply cleanly to any branch that contains the size()==1 branch. Optionally also update the outdated comment at lines 57-60."
     ],
     "confidence": "high",
     "unknowns": [
      "How an untrusted caller reaches the activity (exported attributes, intent filters, or the import activity that launches it) is not in the material.",
      "What the caller does with the RESULT_OK account (the actual import step) is not shown; the material only shows the account selection activity.",
      "Whether Android 13, which the bulletin lists as affected, received the same commit is not shown in the per-major facts.",
      "Which line of the patched region accounts for the 7/8 match in the latest tags is not stated."
     ],
     "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-48524": {
     "cve": "CVE-2025-48524",
     "summary": "WifiPermissionsUtil.isSystem() now returns false early when the calling uid is an SDK sandbox uid (on T+), instead of deriving system-app status from the ApplicationInfo flags of the supplied package name.",
     "component": "platform/packages/modules/Wifi, Wi-Fi service permission checks (WifiPermissionsUtil)",
     "weakness": "Incorrect authorization / privilege check: caller identity (SDK sandbox uid) not considered when classifying a caller as a system app",
     "root_cause": "WifiPermissionsUtil.isSystem(packageName, uid) decided whether a binder caller is a system app solely by looking up ApplicationInfo for the given packageName under the user derived from uid and testing APP_INFO_FLAGS_SYSTEM_APP. It never checked whether uid itself was an SDK sandbox uid, so a sandbox caller could be classified as a system app. The commit title ('Do not treat SdkSandBox as privileges App') says this classification led the service to treat such callers as privileged apps.",
     "fix_analysis": [
      "service/java/com/android/server/wifi/util/WifiPermissionsUtil.java:isSystem - inside the try block, before the PackageManager lookup, adds `if (SdkLevel.isAtLeastT() && Process.isSdkSandboxUid(uid)) return false;`, so a sandbox uid is never classified as a system app, whatever package name is passed in.",
      "The check runs after Binder.clearCallingIdentity() and inside the existing try/finally, so the calling identity is still restored on the early return.",
      "The SdkLevel.isAtLeastT() guard means the new check is skipped before T; on pre-T the method behaves exactly as before."
     ],
     "completeness": [
      "Only isSystem() is hardened; the fix is a single 3-line addition in one method.",
      "Other identity-based checks in the same class that are visible in the context (isDeviceOwner, isProfileOwnerOfOrganizationOwnedDevice, isOrganizationOwnedDeviceAdmin) are untouched; the material does not show whether they have a similar issue.",
      "The fix does not change how packageName is validated against uid in isSystem(); it only short-circuits for sandbox uids.",
      "Where SdkLevel.isAtLeastT() is false, the new check is not executed."
     ],
     "behavior_changes": [
      "Any Wi-Fi service path that consults WifiPermissionsUtil.isSystem() now treats SDK sandbox callers as non-system apps on Android T and later; such callers fall through to the handling that applies to non-system apps.",
      "No new exception or error code is introduced; the method simply returns false earlier."
     ],
     "preconditions": [
      "A binder call into the Wi-Fi service from a process running under an SDK sandbox uid that reaches WifiPermissionsUtil.isSystem(), on a device running Android T or later.",
      "The exact Wi-Fi service entry points that call isSystem() are not shown in the material (the index results returned were unrelated framework methods)."
     ],
     "per_major": {
      "14": "same fix: commit 980d442c5926 in android-security-14.0.0_r22 (cherry-pick of the same Change-Id); 5/5 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 298745e0cb23 in android-security-15.0.0_r10, identical diff; 5/5 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit c47809aec031 in android-security-16.0.0_r1, identical diff; 5/5 lines present at android-security-16.0.0_r8.",
      "17": "not affected: not listed by the bulletin; the patched code is present (5/5 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The patch is self-contained and applies identically on 14/15/16 (same hunk; only the line offset differs: 1138 on 14 vs 1126 on 15/16). The commit adds no imports, so SdkLevel and Process must already be imported in this file. The Process.isSdkSandboxUid call is guarded by SdkLevel.isAtLeastT(). The bulletin also lists Android 13 as affected, but no 13 commit is included in the material."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Wi-Fi service APIs call isSystem() and what privileged behaviour they gate; the material does not show the callers.",
      "How packageName is validated against the calling uid before isSystem() is reached (the catch comment references checkPackage, but that code is not shown).",
      "Why the bulletin classifies the impact as DoS; the diff alone only shows the privilege misclassification.",
      "The Android 13 fix commit, if any, is not in the material."
     ],
     "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-48526": {
     "cve": "CVE-2025-48526",
     "summary": "ChooserActivity now gives the adapter for the non-launched (cross-profile) profile a sanitized copy of the payload intents instead of the caller's original intents. In the copy, package and component are cleared, and so are the selector's package and component.",
     "component": "platform/packages/modules/IntentResolver, ChooserActivity (share sheet), cross-profile target resolution",
     "weakness": "Explicit intent targeting (package/component, including on the selector) is not removed before cross-profile resolution, which per the 15/16 kdoc could be used to bypass the cross-profile sharing policy (policy bypass)",
     "root_cause": "In ChooserActivity.java, the method that builds the profile pager adapters created the personal and the work ChooserGridAdapter with the same unmodified payload intents (mIntents on Android 14, request.getPayloadIntents() on 15/16). Explicit targeting supplied by the caller (package/component, or a selector carrying them) therefore reached the other profile's adapter unchanged. The 15/16 kdoc on ChooserRequest.crossProfilePayloadIntents says such explicit intents \"could otherwise be used to bypass the device's cross-profile sharing policy settings\". Only initialIntents were already limited to the launched-as profile; payload intents were not.",
     "fix_analysis": [
      "java/src/com/android/intentresolver/util/IntentUtils.kt (new): sanitizePayloadIntents(intents) returns a new list. Each intent is copied via Intent(intent), then setPackage(null) and setComponent(null) are called on the copy. If the copy has a selector, it is replaced with a copy of the selector that also has package and component cleared. On Android 14 the function takes List<Intent?> and keeps null entries; on 15/16 it takes List<Intent>.",
      "Android 14, java/src/com/android/intentresolver/ChooserActivity.java (the pager adapter creation method that takes filterLastUsed and TargetDataLoader): computes crossProfileIntents = sanitizePayloadIntents(mIntents) once. It then passes mIntents to the adapter for the profile equal to findSelectedProfile() (PROFILE_PERSONAL or PROFILE_WORK) and crossProfileIntents to the other adapter. initialIntents handling is unchanged: they still go only to the selected profile.",
      "Android 15/16, java/src/com/android/intentresolver/data/model/ChooserRequest.kt: adds val crossProfilePayloadIntents by lazy { sanitizePayloadIntents(payloadIntents) } next to payloadIntents.",
      "Android 15/16, ChooserActivity.createMultiProfilePagerAdapter: drops the initialIntents parameter (both call sites updated) and reads request.getInitialIntents() directly. For each profile it computes isCrossProfile = !profile.equals(launchedAs) and calls createChooserGridAdapter with request.getCrossProfilePayloadIntents() when isCrossProfile is true, otherwise request.getPayloadIntents(). Initial intents are passed only when isCrossProfile is false.",
      "Android 15/16, tests/unit/.../util/IntentUtilsTest.kt (new): checks that package and component are null on the sanitized intents and on their selectors, for four input shapes. The Profile.kt changes are trailing-comma and import formatting only, with no behavior change."
     ],
     "completeness": [
      "Covers the Chooser's payload intents (target intent plus additional targets) for every profile other than the launched-as profile. Package, component, and the selector's package and component are cleared on copies, so the launched-as profile still receives the original explicit intents.",
      "Only package and component (and the same two fields on the selector) are removed. Other intent fields (action, data, type, categories, extras, ClipData, flags) pass through unchanged. The material does not show whether any of those fields can carry targeting information.",
      "initialIntents are not sanitized. The existing code already passes them only to the launched-as profile adapter (null for the other), so they do not reach cross-profile resolution.",
      "On Android 14 the change is limited to the single adapter-creation path in ChooserActivity, which builds explicit personal/work adapters. The diff does not touch the ResolverActivity (non-chooser) path, and the material does not show whether that path has the same issue.",
      "At android-security-14.0.0_r29, ChooserActivity imports prepareCrossProfileIntents from IntentUtils, and IntentUtils.kt has a prepareCrossProfileIntents(contentResolver, targetIntent, ...) function that references IntentForwarderActivity. This points to a later rework of this logic whose full content is not in the material."
     ],
     "behavior_changes": [
      "Targets in the other profile's tab are now resolved from implicit versions of the payload intents. An explicit package/component (or explicit selector) supplied by the sharing app no longer constrains or selects targets in the cross-profile tab, so the set of cross-profile targets may differ from before.",
      "The intent delivered to a cross-profile target no longer carries the caller's package/component or an explicit selector.",
      "Android 15/16: the createMultiProfilePagerAdapter signature changed (initialIntents parameter removed). The sanitized list is computed lazily on first access via ChooserRequest.crossProfilePayloadIntents.",
      "Android 14: each call of the adapter-creation method makes one extra Intent copy per payload intent (and one per selector)."
     ],
     "preconditions": [
      "A device with more than one profile shown in the chooser (personal/work on 14; any non-launched profile such as work or private on 15/16).",
      "A local app opens the share sheet (ChooserActivity) with a payload intent, or an additional target intent, that carries an explicit package or component, or a selector with one."
     ],
     "per_major": {
      "14": "Same fix (original commit 4ffdecbbef8a, android-security-14.0.0_r22): adds IntentUtils.kt, and ChooserActivity's explicit personal/work adapter creation picks mIntents or sanitizePayloadIntents(mIntents) based on findSelectedProfile(). All 8/8 patched lines are present at android-security-14.0.0_r29. In that tag IntentUtils also has prepareCrossProfileIntents, which ChooserActivity imports (follow-up content not shown).",
      "15": "Adapted fix (923a5673ac9d, android-security-15.0.0_r10): the sanitization is exposed as ChooserRequest.crossProfilePayloadIntents (lazy). createMultiProfilePagerAdapter loops over profileHelper.getProfiles() and uses isCrossProfile to pick the list. The initialIntents parameter is removed and a unit test is added. 6/8 lines are present at android-security-15.0.0_r17, so the region has since changed.",
      "16": "Adapted fix (247af9e3e487, android-security-16.0.0_r1): same shape as 15, but the loop runs over the ProfileRecord collection. ChooserRequest.kt gets the same lazy property. 6/8 lines are present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). Only 3/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "For 14-style trees (explicit personal/work ChooserGridAdapter creation, no ChooserRequest model), the port is the new IntentUtils.kt (the 14 version takes List<Intent?> and keeps nulls) plus the two ternaries keyed on findSelectedProfile(). For 15+-style trees, use the ChooserRequest.crossProfilePayloadIntents form and the isCrossProfile loop. The 14 branch was changed again later (prepareCrossProfileIntents at r29), so consider porting from the latest security tag rather than this commit alone.",
      "No 'Fix in fork' section was provided, so no per-file fork verdict is available."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact policy bypass and the EoP outcome. The 14 commit message only says 'Sanitize cross-profile intents', and the 15/16 kdoc only mentions bypassing cross-profile sharing policy settings.",
      "The exact trigger path, including whether user interaction (selecting a target in the other profile's tab) is required.",
      "How the cross-profile sharing policy decides whether targets appear in the other profile's tab (for example, whether a blocked direction shows an empty state).",
      "Whether ResolverActivity (non-chooser resolution) has the same pattern; the diff does not cover it.",
      "The content and purpose of the later prepareCrossProfileIntents rework visible at android-security-14.0.0_r29.",
      "The Android 13 fix (13 is listed as affected by the bulletin) is not in the material.",
      "What the code at the patched region looks like on Android 17."
     ],
     "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-48527": {
     "cve": "CVE-2025-48527",
     "summary": "Settings' Notification History screen now replaces the title and text of history, snoozed and dismissed notifications with the generic \"notification_hidden_text\" string for any profile that is securely locked and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS disabled, instead of rendering the full content.",
     "component": "platform/packages/apps/Settings, notification history UI (src/com/android/settings/notification/history)",
     "weakness": "Missing visibility check leading to information disclosure (notification content of a locked profile shown without honoring the profile's \"hide sensitive content when locked\" setting)",
     "root_cause": "NotificationHistoryAdapter.onBindViewHolder and NotificationSbnAdapter.onBindViewHolder unconditionally called holder.setTitle()/holder.setSummary() with the notification's title and text (hn.getTitle()/hn.getText() and getTitleString()/getTextString()). NotificationHistoryActivity never consulted the per-user LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS setting or the lock state of the profiles whose notifications it lists. As a result, content from a locked profile (e.g. a work profile with a separate PIN, per the commit message) that had chosen to hide sensitive content when locked was displayed in full when the history screen was opened from the profile group.",
     "fix_analysis": [
      "NotificationHistoryActivity.java: adds a field mContentRestrictedUsers (ArrayList<Integer>). In onCreate, after mUm is obtained, it iterates mUm.getProfiles(getUserId()). A profile's user id is added to mContentRestrictedUsers when Settings.Secure.getIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, default 0, user.id) == 0 and new LockPatternUtils(this).isSecure(user.id) && KeyguardManager.isDeviceLocked(user.id).",
      "NotificationHistoryActivity.java: the list is passed to three adapter constructions: new NotificationHistoryAdapter(NotificationHistoryActivity.this, mNm, rv, listener, mUiEventLogger, mContentRestrictedUsers) for the per-package history lists, and new NotificationSbnAdapter(..., true/false, mUiEventLogger, mContentRestrictedUsers) for the snoozed (mSnoozedRv) and dismissed (mDismissedRv) lists.",
      "NotificationHistoryAdapter.java: the constructor gains Context and ArrayList<Integer> contentRestrictedUsers parameters, stored in mContext and mContentRestrictedUsers. In onBindViewHolder, if mContentRestrictedUsers.contains(hn.getUserId()), it only calls holder.setSummary(mContext.getString(com.android.internal.R.string.notification_hidden_text)). Otherwise the original setTitle/setSummary path runs.",
      "NotificationSbnAdapter.java: the constructor gains ArrayList<Integer> contentRestrictedUsers, stored in mContentRestrictedUsers. In onBindViewHolder, if mContentRestrictedUsers.contains(sbn.getNormalizedUserId()), it only sets the summary to notification_hidden_text. Otherwise the original getTitleString/getTextString path runs. Icon, background, package label and posted time are still set as before."
     ],
     "completeness": [
      "Covers all three lists rendered by NotificationHistoryActivity: per-package history via NotificationHistoryAdapter, and snoozed and dismissed via NotificationSbnAdapter.",
      "Only the title and text are redacted. Package label, app icon, posted time and the per-user handling in NotificationSbnAdapter are unchanged, and so is the item click handler in NotificationHistoryAdapter. The existence of a notification from a given app in the locked profile therefore remains visible.",
      "The restricted-user set is computed once in onCreate and is not recomputed if a profile locks or unlocks while the activity is alive. The Android 15 variant additionally clears the list in onDestroy but also does not refresh it.",
      "Redaction applies only when the profile has a secure lock (LockPatternUtils.isSecure), is currently locked (KeyguardManager.isDeviceLocked) and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS == 0. Unlocked profiles are shown in full.",
      "In the restricted branch holder.setTitle() is not called at all, so this code path does not explicitly reset the title view of a recycled ViewHolder. Whether the holder clears it elsewhere is not shown in the material.",
      "Only profiles returned by mUm.getProfiles(getUserId()) are considered. The adapters' own user handling (mEnabledProfiles, normalizeUserId) is untouched."
     ],
     "behavior_changes": [
      "Notification History entries from a locked, content-restricted profile now show the generic framework string notification_hidden_text as summary and no title.",
      "The constructor signatures of NotificationHistoryAdapter (new leading Context and trailing ArrayList<Integer>) and NotificationSbnAdapter (new trailing ArrayList<Integer>) changed. Any other instantiation would fail to compile.",
      "onCreate now performs additional per-profile Settings.Secure reads and LockPatternUtils/KeyguardManager queries."
     ],
     "preconditions": [
      "The user opens the Settings NotificationHistoryActivity.",
      "The device has a profile of the current user (e.g. a work profile with its own PIN, per the commit's test note) that is securely locked and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS set to 0.",
      "Notifications from that profile exist in history, or in the snoozed/dismissed lists rendered by NotificationSbnAdapter."
     ],
     "per_major": {
      "14": "same fix: commit cee46cf86cd9 in android-security-14.0.0_r22 (cherry-pick of 9df37c3f8be2). Patched region fully present at android-security-14.0.0_r29 (8/8 lines).",
      "15": "adapted fix: commit d8b1de7a18fc in android-security-15.0.0_r10. Same logic plus explanatory comments, a duplicated mContentRestrictedUsers.clear() before the profile loop in onCreate, and mContentRestrictedUsers.clear() in onDestroy. Fully present at android-security-15.0.0_r17 (8/8 lines).",
      "16": "same fix: commit 504a34c4d4a3 in android-security-16.0.0_r1. Logic identical to Android 14, applied on top of the newer activity/adapter layout (GONE constant, no setDividerVisible). Fully present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "not affected per the bulletin (not listed). The patched region is present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "The fix is self-contained to three files in packages/apps/Settings and uses hidden framework APIs (LockPatternUtils, KeyguardManager.isDeviceLocked(int), Settings.Secure.getIntForUser) and the internal resource com.android.internal.R.string.notification_hidden_text, which Settings can reference as a platform app (general platform knowledge). Update every call site of the two adapter constructors, since the signatures change.",
      "The 14 and 16 commits are the minimal form; the 15 commit adds onDestroy clearing and comments. The redaction logic is the same in all three."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether NotificationHistoryViewHolder/NotificationSbnViewHolder reset the title view when setTitle is not called for a recycled holder (the holder implementations are not in the material).",
      "Whether the history activity is reachable from a locked profile's own context or only from the parent user (only the parent-user flow is implied by the commit message).",
      "Whether notification history must be enabled for the snoozed/dismissed lists to be rendered.",
      "What the NotificationHistoryAdapter item click handler launches.",
      "Android 13 is listed as affected by the bulletin, but no Android 13 commit or branch status is provided in the material."
     ],
     "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-48529": {
     "cve": "CVE-2025-48529",
     "summary": "Removes the shared-preference storage, readback and channel migration of the voicemail ringtone URI in frameworks/opt/telephony, so TelephonyManager#setVoicemailRingtoneUri becomes a no-op and getVoicemailRingtoneUri only returns the voicemail NotificationChannel sound.",
     "component": "platform/frameworks/opt/telephony, voicemail notification settings (VoicemailNotificationSettingsUtil, NotificationChannelController)",
     "weakness": "A caller-supplied URI was persisted without validation and later applied as a phone process notification channel sound and returned via a public API; the bulletin classifies it as information disclosure (ID)",
     "root_cause": "VoicemailNotificationSettingsUtil.setRingtoneUri wrote any URI passed through TelephonyManager#setVoicemailRingtoneUri into the phone process default SharedPreferences. Per index search, that call goes through PhoneInterfaceManager.setVoicemailRingtoneUri, which is reachable by the default dialer or by callers that pass the modify-permission or carrier-privilege check. Two legacy paths then consumed the stored value. First, getRingtoneUri fell back to getRingTonePreference when the voicemail channel did not exist. Second, NotificationChannelController.migrateVoicemailNotificationSettings called getRingTonePreference and set the URI as the sound of the newly created CHANNEL_ID_VOICE_MAIL channel. The commit message states that a URI set via the setter could be migrated onto the notification channel, causing a cross-profile exploit. The pre-P shared preference mechanism was obsolete (the setting has lived on the NotificationChannel since Android P) but was still writable and readable.",
     "fix_analysis": [
      "src/java/com/android/internal/telephony/util/VoicemailNotificationSettingsUtil.java:setRingtoneUri: the body is replaced with a no-op comment; the URI is no longer written to SharedPreferences.",
      "VoicemailNotificationSettingsUtil.java:getRingtoneUri: the fallback to getRingTonePreference(context) is removed. It returns channel.getSound() if the CHANNEL_ID_VOICE_MAIL channel exists, otherwise null. A comment is added saying never to look at the shared preferences.",
      "VoicemailNotificationSettingsUtil.java: getRingTonePreference(Context) is deleted entirely (it read the pref, invoked migrateVoicemailRingtoneSettingsIfNeeded and parsed the stored string into a Uri). The unused imports android.provider.Settings and android.text.TextUtils are removed.",
      "src/java/com/android/internal/telephony/util/NotificationChannelController.java:migrateVoicemailNotificationSettings: removes the Uri sound = getRingTonePreference(context) lookup and the voiceMailChannel.setSound(...) call, so the voicemail channel is created without a sound taken from shared preferences. The android.net.Uri import and the javadoc reference to getRingTonePreference are removed.",
      "Single commit with an identical diff (same blob hashes) on 14, 15 and 16."
     ],
     "completeness": [
      "Covers the three uses of the ringtone shared preference shown in the diff: the setter, the getter fallback, and the channel migration.",
      "The vibration setting (setVibrationEnabled/getVibrationPreference) still uses SharedPreferences and is still applied to the channel in migrateVoicemailNotificationSettings. It is a boolean, not a URI, and is left unchanged.",
      "The diff does not show migrateVoicemailRingtoneSettingsIfNeeded or getVoicemailRingtoneSharedPrefsKey being removed. The VOICEMAIL_NOTIFICATION_RINGTONE_SHARED_PREFS_KEY_PREFIX constant remains in the code context at r29.",
      "Ringtone values stored before the fix are not deleted from the phone process shared preferences. The readers shown in the diff are removed.",
      "PhoneInterfaceManager.setVoicemailRingtoneUri (packages/services/Telephony) is not modified (per index search). It still performs permission checks and calls the now no-op setRingtoneUri."
     ],
     "behavior_changes": [
      "TelephonyManager#setVoicemailRingtoneUri silently does nothing (no error is returned to the caller).",
      "TelephonyManager#getVoicemailRingtoneUri returns null instead of the shared-preference or default notification URI when the voicemail NotificationChannel does not exist.",
      "When migrateVoicemailNotificationSettings creates the voicemail notification channel, it no longer calls setSound with a legacy preference value or DEFAULT_NOTIFICATION_URI."
     ],
     "preconditions": [
      "The caller reaches PhoneInterfaceManager.setVoicemailRingtoneUri. It must be the default dialer package or pass enforceCallingOrSelfModifyPermissionOrCarrierPrivilege for the subscription, and the FEATURE_TELEPHONY_CALLING check applies (per index search).",
      "For the stored URI to take effect, either the voicemail NotificationChannel is created via migrateVoicemailNotificationSettings, or the channel is absent so that getRingtoneUri falls back to the preference."
     ],
     "per_major": {
      "14": "same fix: commit 122251ced157 in android-security-14.0.0_r22; patched region present at android-security-14.0.0_r29 (7/8 lines).",
      "15": "same fix: commit e5cdca27526f in android-security-15.0.0_r10, identical diff; patched region present at android-security-15.0.0_r17 (7/8 lines).",
      "16": "same fix: commit f33f9ef6467b in android-security-16.0.0_r1, identical diff; patched region present at android-security-16.0.0_r8 (7/8 lines).",
      "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 in frameworks/opt/telephony. The diff is identical on 14, 15 and 16, with the same blob hashes (de1ddd3026 to ac6a385fa4 and d8988e3230 to 3dd3d375c9). Forks with unmodified VoicemailNotificationSettingsUtil and NotificationChannelController should be able to take it as-is. Forks that added other readers of the ringtone preference should check that none remain."
     ],
     "confidence": "high",
     "unknowns": [
      "The bulletin lists Android 13 as affected, but the material has no Android 13 fix commit.",
      "The exact cross-profile disclosure mechanism is not shown beyond the commit message statement.",
      "Whether migrateVoicemailRingtoneSettingsIfNeeded and getVoicemailRingtoneSharedPrefsKey still have callers after the fix, or remain as dead code, is not shown.",
      "The sound the voicemail channel gets when created without an explicit setSound call is not shown in the material."
     ],
     "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-48531": {
     "cve": "CVE-2025-48531",
     "summary": "Settings' CredentialStorage now identifies the caller of its install flow with ActivityManager.getLaunchedFromPackage(getActivityToken()) instead of Activity.getCallingPackage() when deciding whether the caller is com.android.certinstaller.",
     "component": "platform/packages/apps/Settings, security/CredentialStorage (credential install activity)",
     "weakness": "Improper caller identification in an authorization check (trust decision based on an unreliable package identity source)",
     "root_cause": "CredentialStorage.checkCallerIsCertInstallerOrSelfInProfile() used Activity.getCallingPackage() to decide whether the caller is \"com.android.certinstaller\", then compared that package's signature with Settings. Per the commit message, getCallingPackage() does not always return the package name of the app that actually called. As a result, the certinstaller branch of the authorization check could evaluate a package identity that was not the real caller.",
     "fix_analysis": [
      "src/com/android/settings/security/CredentialStorage.java: adds a private helper getCallingPackageName(). It calls ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) and returns null on RemoteException. The commit imports android.app.ActivityManager; RemoteException was already imported.",
      "CredentialStorage.checkCallerIsCertInstallerOrSelfInProfile(): reads the caller once into a local callingPackage via getCallingPackageName(). It compares that value to \"com.android.certinstaller\" and passes the same local to getPackageManager().checkSignatures(callingPackage, getPackageName()). Before the fix, the method called getCallingPackage() twice.",
      "The diff does not change the method's other branch (the launchedFromUserId-based profile check)."
     ],
     "completeness": [
      "Covers the certinstaller branch of checkCallerIsCertInstallerOrSelfInProfile(). Both the package-name comparison and the signature check now use one consistently obtained value.",
      "The helper returns null if ActivityManager cannot be reached. TextUtils.equals(\"com.android.certinstaller\", null) is false, so the check fails closed in that case.",
      "The second branch (self-in-profile via launchedFromUserId) is not touched. The material does not show its body, so the diff cannot confirm whether it already used a launched-from API.",
      "The material shows no other callers of getCallingPackage() in Settings; the fix is limited to this one method."
     ],
     "behavior_changes": [
      "The certinstaller path in CredentialStorage is taken only when the launching package recorded by ActivityManager is com.android.certinstaller and its signature matches Settings.",
      "Callers that previously passed the check through the getCallingPackage() value but are not the real launcher are now rejected by this branch.",
      "If the ActivityManager binder call fails, the certinstaller check is denied instead of throwing."
     ],
     "preconditions": [
      "An app launches the Settings CredentialStorage activity with an intent that reaches checkCallerIsCertInstallerOrSelfInProfile() (the credential install flow).",
      "Before the fix, the check relied on Activity.getCallingPackage(), which per the commit message does not always return the actual calling app."
     ],
     "per_major": {
      "14": "same fix: commit da4efc0a0615 in android-security-14.0.0_r22 (cherry-pick of 70bd3efe0674). At android-security-14.0.0_r29, 7/8 lines of the patched region are present, and the ActivityManager import is visible in the code context.",
      "15": "same fix: commit cc1b1b5e493a in android-security-15.0.0_r10, identical hunk content with a one-line offset (@@ -332). At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "same fix: commit 0f61e586873a in android-security-16.0.0_r1, identical hunk content at offset @@ -322. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "not affected per the bulletin (Android 17 is not listed). At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single self-contained commit that touches one file. ActivityManager.getService().getLaunchedFromPackage(IBinder) and Activity.getActivityToken() are hidden framework APIs that Settings can use because it builds against the platform (general platform knowledge). Only hunk line offsets differ between 14, 15 and 16."
     ],
     "confidence": "high",
     "unknowns": [
      "Beyond the commit message, the material does not show how a third-party app could cause a mismatched getCallingPackage() value.",
      "The material does not show the body of the launchedFromUserId branch of checkCallerIsCertInstallerOrSelfInProfile() or which API it uses.",
      "The material does not show which line of the 8-line patched region differs in the latest tags (7/8 present)."
     ],
     "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-48534": {
     "cve": "CVE-2025-48534",
     "summary": "CellBroadcastHandler.getDefaultCBRPackageName now queries broadcast receivers with PackageManager.MATCH_SYSTEM_ONLY instead of flags 0, so non-system packages can no longer be selected as the default CellBroadcastReceiver package.",
     "component": "platform/packages/modules/CellBroadcastService, CellBroadcastHandler (default CBR package resolution)",
     "weakness": "Insufficient verification of the target package (missing system-app restriction when resolving the receiver of cell broadcast intents)",
     "root_cause": "getDefaultCBRPackageName in src/com/android/cellbroadcastservice/CellBroadcastHandler.java called packageManager.queryBroadcastReceivers(intent, 0). That call did not restrict results to system packages. The only filter after it, isAppInCBApexOrAlternativeApp, is a string heuristic on ApplicationInfo.sourceDir (startsWith(CB_APEX_PATH) or contains(CB_APP_PLATFORM_NAME)) and does not require a system package. A non-system app whose receiver matched the intent and whose sourceDir passed the heuristic could therefore be a candidate for the default CBR package. The commit's test step installs a 'fake cellbroadcast app'. The bulletin classifies the issue as DoS.",
     "fix_analysis": [
      "src/com/android/cellbroadcastservice/CellBroadcastHandler.java:getDefaultCBRPackageName: the flags argument of the queryBroadcastReceivers call changes from 0 to PackageManager.MATCH_SYSTEM_ONLY. Only receivers in system packages are now candidates. The existing sourceDir filter (isAppInCBApexOrAlternativeApp) and the empty/multiple-result handling still run after the query.",
      "src/com/android/cellbroadcastservice/CellBroadcastHandler.java:getDefaultCBRPackageName: visibility changes from package-private static to public static with @VisibleForTesting, so the new unit test can call it.",
      "tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java: adds testGetDefaultCBRPackageName, which calls the method with an ACTION_SMS_EMERGENCY_CB_RECEIVED intent and verifies that queryBroadcastReceivers is called once with PackageManager.MATCH_SYSTEM_ONLY. Also adds the android.content.pm.PackageManager import."
     ],
     "completeness": [
      "Covers the single queryBroadcastReceivers call in getDefaultCBRPackageName. Any package not flagged as system is excluded, whatever its sourceDir.",
      "The sourceDir string heuristic in isAppInCBApexOrAlternativeApp (startsWith(CB_APEX_PATH) || contains(CB_APP_PLATFORM_NAME)) is unchanged. The fix relies on MATCH_SYSTEM_ONLY instead of tightening that check.",
      "This commit does not touch the implicit broadcast path visible in the context (CellBroadcastIntents.sendSmsCbReceivedBroadcast to UserHandle.ALL, commented as reaching 'various 3rd party carrier apps'). The material does not show whether that path depends on getDefaultCBRPackageName.",
      "The material does not show other queryBroadcastReceivers callers or other package-resolution helpers in the module, so coverage beyond this function cannot be judged."
     ],
     "behavior_changes": [
      "Non-system packages are never returned as the default CBR package, even if their sourceDir matches the heuristic. If no system package passes the filters, the method logs ERRTYPE_NOTFOUND_DEFAULTCBRPKGS and returns null.",
      "getDefaultCBRPackageName becomes public (annotated @VisibleForTesting). No functional change for callers inside the module.",
      "New unit test testGetDefaultCBRPackageName in CellBroadcastHandlerTest."
     ],
     "preconditions": [
      "A package installed on the device declares a broadcast receiver for the cell broadcast intent (for example Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED, the action used in the new test) and its sourceDir satisfies isAppInCBApexOrAlternativeApp.",
      "CellBroadcastHandler resolves the default CBR package while processing a cell broadcast. The material does not show the exact caller.",
      "The commit's test step installs an app ('Install fake cellbroadcast app'). The exact installation requirements are not shown."
     ],
     "per_major": {
      "14": "same fix: commit af24a165e952 in android-security-14.0.0_r22 (RESTRICT AUTOMERGE, cherry-picked from f24f40e2512f). At android-security-14.0.0_r29, 7/8 lines of the patched region are present, and the shown code contains the MATCH_SYSTEM_ONLY call.",
      "15": "same fix: commit 584cec4f17ea in android-security-15.0.0_r10. The source hunk is identical; the test hunk differs only in surrounding imports and line offsets. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "not affected (the bulletin does not list it). At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "not affected (the bulletin does not list it). At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit with a two-line functional change: in getDefaultCBRPackageName, replace the flags argument 0 with PackageManager.MATCH_SYSTEM_ONLY. The new test needs the method to be accessible (public) and uses the mocked mMockedContext and mMockedPackageManager."
     ],
     "confidence": "high",
     "unknowns": [
      "The values of CB_APEX_PATH and CB_APP_PLATFORM_NAME, and so exactly how a non-system app's sourceDir satisfied the heuristic, are not shown.",
      "Which line of the 8-line patched region differs at the latest security tags (7/8 present) is not shown.",
      "The callers of getDefaultCBRPackageName inside CellBroadcastService, and whether the implicit sendSmsCbReceivedBroadcast path depends on it, are not shown.",
      "The concrete effect on alert delivery when a non-system package was selected is not shown beyond the bulletin's DoS classification.",
      "Whether the fix is also delivered through a mainline module update is not shown.",
      "Whether Android 13 received an equivalent commit is not shown (the bulletin lists 13 as affected, but the material has no 13 commit)."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48535": {
     "cve": "CVE-2025-48535",
     "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is no longer launched as received after vetting. Settings now launches a copy pinned to the exact component and package that passed the resolveActivity/package check, and a second commit makes that same copy the object that is resolved.",
     "component": "platform/packages/apps/Settings, users/AppRestrictionsFragment (app restrictions UI for restricted users)",
     "weakness": "Intent redirection / check-vs-use mismatch: the Intent object that was vetted was not the one launched, and the vetted component was not pinned on the launched Intent (confused deputy, TOCTOU style)",
     "root_cause": "RestrictionsResultReceiver.onReceive takes an Intent from the broadcast result extras (Intent.EXTRA_RESTRICTIONS_INTENT, supplied by the target app). If the fragment is resumed and invokeIfCustom is set, it calls assertSafeToStartCustomActivity(restrictionsIntent). That method resolved the intent with mPackageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY) and checked that the resolved activityInfo.packageName equals the expected packageName, but it returned nothing. The caller then called startActivityForResult(new Intent(restrictionsIntent), requestCode) on a fresh copy of the untrusted intent, and never set the vetted component or package on that copy. The check and the launch therefore ran on different Intent objects, and the launch did not carry the verified component. As a result, the activity that Settings (a highly privileged app) actually started was not guaranteed to be the one that passed the package check.",
     "fix_analysis": [
      "Commit 33a1cbf7bd0e, src/com/android/settings/users/AppRestrictionsFragment.java: RestrictionsResultReceiver.assertSafeToStartCustomActivity now returns an Intent. Once the existing resolveActivity and packageName equality checks pass, it creates new Intent(intent), calls setComponent(activityInfo.getComponentName()) and setPackage(activityInfo.packageName) using the ActivityInfo that was vetted, and returns that Intent.",
      "Commit 33a1cbf7bd0e, RestrictionsResultReceiver.onReceive: stores the return value in a final Intent vettedIntent and passes vettedIntent to AppRestrictionsFragment.this.startActivityForResult instead of new Intent(restrictionsIntent). The ActivityNotFoundException/SecurityException catch path (log, EventLog 200688991, return) is unchanged.",
      "Commit ca8f012da894 (applied after the first), assertSafeToStartCustomActivity: moves final Intent vettedIntent = new Intent(intent) to the top of the method, before resolveActivity, and resolves vettedIntent instead of the incoming intent. The same copy is now resolved, checked, pinned and launched. The later declaration of vettedIntent is removed."
     ],
     "completeness": [
      "Covers the single launch site in RestrictionsResultReceiver.onReceive. The launched Intent now has an explicit component and package equal to what resolveActivity returned and what the package check validated.",
      "Neither commit strips URI permission grant flags, data or ClipData from the untrusted Intent. At android-security-14.0.0_r29 the method also calls removeFlags(FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION), setData(null) and setClipData(null), but that hardening comes from a change not included in this material.",
      "The package check itself (packageName.equals(activityInfo.packageName) with MATCH_DEFAULT_ONLY) is unchanged. In these two commits, other extras of the app-provided Intent are still forwarded as-is.",
      "Per index search, no other caller of assertSafeToStartCustomActivity was found. The method is private to the receiver."
     ],
     "behavior_changes": [
      "The custom restrictions activity is now started with an explicit ComponentName and package on the Intent, so the launched activity is always the one resolveActivity returned.",
      "No new error paths: on failure, Settings still logs 'Cannot start restrictionsIntent', writes EventLog tag 0x534e4554/200688991 and returns without launching.",
      "assertSafeToStartCustomActivity now returns Intent instead of void (internal, private)."
     ],
     "preconditions": [
      "AppRestrictionsFragment is in use in Settings, and a RestrictionsResultReceiver with invokeIfCustom true receives a result while the fragment is resumed.",
      "The target app's restrictions broadcast result Bundle contains Intent.EXTRA_RESTRICTIONS_INTENT (an app-controlled Intent).",
      "The app-controlled Intent must pass resolveActivity(MATCH_DEFAULT_ONLY) with activityInfo.packageName equal to the app's package. The launch then happens from the Settings process (Settings runs as the system UID; general platform knowledge)."
     ],
     "per_major": {
      "14": "same fix: two cherry-picks in android-security-14.0.0_r22, first 33a1cbf7bd0e (use vetted component), then ca8f012da894 (copy before resolve). 8/8 patched lines present at android-security-14.0.0_r29, plus later extra hardening (removeFlags/setData/setClipData) not in this material.",
      "15": "same fix: 1e4423730f87 'startActivityForResult with earlier new Intent' in android-security-15.0.0_r10, same diff as ca8f012da894. Its context shows the vetted-component change was already present, but the material does not show the commit that added it. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: 25ed933c4cf5 in android-security-16.0.0_r1, same diff as 15. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). Patched region present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply in order: first 33a1cbf7bd0e (assertSafeToStartCustomActivity returns a pinned copy and onReceive launches it), then ca8f012da894 (moves the new Intent(intent) copy above resolveActivity). The second diff's context depends on the first. Both touch only RestrictionsResultReceiver in AppRestrictionsFragment.java.",
      "Branches based on a later tag may already contain the additional removeFlags/setData(null)/setClipData(null) lines seen at 14.0.0_r29. Those come from a separate change and should be kept if present."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact mechanism by which the unpinned launch reached a different target than the vetted one. The commits only say 'See bug' for bugs 353680402 and 365739560.",
      "Which commit added the URI-grant flag, data and ClipData stripping visible at android-security-14.0.0_r29, and whether it is tied to this CVE or another one.",
      "Which commit brought the vetted-component change to Android 15 and 16.",
      "Whether Android 13 (listed as affected) received the same commits. The material includes no Android 13 commit.",
      "Which user actions cause RestrictionsResultReceiver to be created with invokeIfCustom true."
     ],
     "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-48537": {
     "cve": "CVE-2025-48537",
     "summary": "ZenModeConditions.evaluateRule now maps condition ids to built-in system ConditionProviderServices only for system-owned or manual rules, rejects non-system rules whose component is in the \"android\" package, and tracks subscriptions by (Uri, ComponentName) pairs instead of by Uri alone.",
     "component": "platform/frameworks/base, NotificationManagerService / Zen mode (ZenModeConditions, ConditionProviders)",
     "weakness": "Improper authorization / identifier confusion: a condition Uri was treated as if it uniquely identified a ConditionProviderService. This let a non-system rule be bound to a system CPS and let rules of different CPSes that share a Uri collide.",
     "root_cause": "In services/core/java/com/android/server/notification/ZenModeConditions.java, evaluateRule looped over mConditionProviders.getSystemProviders() for every rule. When sp.isValidConditionId(id) matched, it called ensureRecordExists for the system CPS and overwrote rule.component with the system component, no matter which package owned the rule. Nothing stopped a non-system rule from carrying a component in the SYSTEM_AUTHORITY (\"android\") package. In addition, mSubscriptions was an ArrayMap<Uri, ComponentName> and the `current` set in evaluateConfig was an ArraySet<Uri>. Subscription bookkeeping (subscribeIfNecessary / unsubscribeIfNecessary) was therefore keyed by Uri only, which mixed up rules that share a conditionId but belong to different CPSes.",
     "fix_analysis": [
      "ZenModeConditions.java:evaluateRule: computes isSystemRule = isManual || ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getPkg()). If the rule is not a system rule but rule.component.getPackageName() equals SYSTEM_AUTHORITY, it logs Slog.w and returns before recording or subscribing the rule.",
      "ZenModeConditions.java:evaluateRule: the loop over getSystemProviders() that calls ensureRecordExists and rewrites rule.component to the system CPS now runs only when isSystemRule is true. Non-system rules fall through to the existing findConditionProvider(rule.component) path. On Android 14 that path still sets rule.enabled = false and returns when the CPS is not alive.",
      "ZenModeConditions.java:evaluateRule / evaluateConfig: `current` becomes ArraySet<Pair<Uri, ComponentName>>. evaluateRule adds new Pair<>(id, rule.component) to it and to mSubscriptions, which is now ArraySet<Pair<Uri, ComponentName>>. The cleanup loop in evaluateConfig compares whole pairs and calls unsubscribeIfNecessary(subscription.second, subscription.first), so unsubscribe decisions are made per (Uri, CPS) rather than per Uri.",
      "ZenModeConditions.java (Android 14 only): evaluateRule gains the isManual parameter. evaluateConfig passes true for config.manualRule and false for automatic rules. On 15/16 this parameter already existed.",
      "ConditionProviders.java: adds a @VisibleForTesting getRecord(Uri, ComponentName) wrapper around getRecordLocked(id, component, false) and makes ConditionRecord package-private. This is test plumbing.",
      "Tests: a new ZenModeConditionsTest covers three cases: a system rule with a system CPS is evaluated; a package rule with a system CPS is ignored; a package rule with its own CPS but a system-looking condition id binds to the package CPS, not the system CPS. ZenModeHelperTest.testRulesWithSameUri is reworked to use a registered package CPS instead of the schedule provider."
     ],
     "completeness": [
      "Covers both issues named in the commit message, within ZenModeConditions: binding to a system CPS is now gated on rule ownership, and subscription tracking is keyed by (Uri, ComponentName).",
      "The new ownership check is enforced in evaluateRule. The diff does not touch the rule creation or update paths.",
      "Rules with configurationActivity != null still return early, before any of the new checks (this early return is unchanged).",
      "ConditionProviders.notifyConditions / getRecordLocked already key records by (id, component), per the code context, and are not changed.",
      "Rules owned by the system package (rule.getPkg() == SYSTEM_AUTHORITY) and the manual rule keep the previous behaviour, including rewriting the component to the matching system CPS.",
      "The commit does not touch other ZenModeConditions callbacks or ZenModeHelper. Whether those match rules by condition Uri alone is not shown in the material."
     ],
     "behavior_changes": [
      "A non-system automatic rule whose component is in package \"android\" is no longer evaluated or subscribed. A Slog.w line (\"Rule ... belongs to package ... but has component=... which is not allowed!\") is logged instead.",
      "A non-system rule whose conditionId looks like a built-in system condition (schedule, event, etc.) is no longer redirected to the system CPS. It is resolved through its own rule.component. If that CPS is not alive, the existing not-alive path handles it (rule.enabled = false on Android 14).",
      "Unsubscribe now works per (Uri, ComponentName): a rule from another CPS that shares the same Uri no longer keeps alive or removes a different CPS's subscription.",
      "The @VisibleForTesting field mSubscriptions changes type from ArrayMap<Uri, ComponentName> to ArraySet<Pair<Uri, ComponentName>>."
     ],
     "preconditions": [
      "A ZenRule in the ZenModeConfig that ZenModeConditions.evaluateConfig processes: either the manual rule or an automatic rule, with a non-null conditionId, a null configurationActivity, and (for automatic rules) a non-null component.",
      "For the ownership issue: the rule is owned by a non-system package and either has a conditionId that a SystemConditionProviderService.isValidConditionId accepts, or has a component whose package is \"android\".",
      "For the subscription mixup: two rules with the same conditionId bound to different ConditionProviderServices."
     ],
     "per_major": {
      "14": "adapted fix: commit 49078c4bfdce in android-security-14.0.0_r22. Same logic, but it also adds the isManual parameter to evaluateRule and updates both evaluateConfig call sites. 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 63aab59ce138 in android-security-15.0.0_r10. isManual already existed; only the type of the `current` parameter changes. 7/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 57451c039840 in android-security-16.0.0_r1. 7/8 lines present at android-security-16.0.0_r8.",
      "17": "not listed by the bulletin. 7/8 lines of the patched region are present at android-security-17.0.0_r1, which suggests the fix is already in the base code."
     },
     "backport_notes": [
      "On Android 14 the fix also adds the isManual parameter to evaluateRule (true for config.manualRule, false for automatic rules); on 15/16 that parameter already exists. Any downstream code or tests that use the protected mSubscriptions field must adapt to the ArraySet<Pair<Uri, ComponentName>> type. The commit also updates ZenModeHelperTest.testRulesWithSameUri to register a package CPS; the test comment notes that without a valid CPS, ZenModeConditions clears rule.condition."
     ],
     "confidence": "high",
     "unknowns": [
      "What information is disclosed and through which path (the bulletin only classifies it as ID / High).",
      "Which line of the 8-line patched region differs in the latest security tags (7/8 reported), and whether that difference matters functionally.",
      "Whether the rule creation or update paths validate rule.component or conditionId; the diff does not touch them.",
      "Whether other ZenModeConditions / ZenModeHelper paths (e.g. condition-changed callbacks) match rules by Uri alone; not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48538": {
     "cve": "CVE-2025-48538",
     "summary": "Extends the protected-package check in PackageManagerService's package-hiding path so that, in addition to \"android\", the SystemUI package (resolved via PackageManagerInternal.getSystemUiServiceComponent()) can no longer be hidden.",
     "component": "platform/frameworks/base, services/core PackageManagerService (package hiding)",
     "weakness": "Missing validation: the denylist of packages that must not be hidden covered only \"android\", not the SystemUI package.",
     "root_cause": "In services/core/java/com/android/server/pm/PackageManagerService.java, the explicit name check in the hide-package path rejected only packageName \"android\" (\"Cannot hide package: android\"). The SystemUI package was not covered by that check. Per the commit message, hiding SystemUI makes the phone impossible to use even when done inside a managed profile (tested with a custom DPC). This is the DoS the bulletin rates High.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageManagerService.java, in the method that emits \"Cannot hide package\" (the hunk header shows only the class; the enclosing method name is not in the material): the condition `\"android\".equals(packageName)` is extended with `|| LocalServices.getService(PackageManagerInternal.class).getSystemUiServiceComponent().getPackageName().equals(packageName)`.",
      "When either matches, the method logs `Slog.w(TAG, \"Cannot hide package: \" + packageName)` and returns false, so the SystemUI package is not hidden by this path.",
      "The SystemUI package name is resolved at runtime from the configured SystemUI service component rather than hard-coded, so a renamed SystemUI package is covered as long as getSystemUiServiceComponent() returns it."
     ],
     "completeness": [
      "Covers: the hide-package path shown in PackageManagerService, for exactly two packages: \"android\" and the package of getSystemUiServiceComponent().",
      "Does not cover: any other system package in this same path; only these two names are rejected by this check.",
      "The material shows only this hide path; whether other state-changing paths (e.g. disabling or suspending packages) have an equivalent SystemUI protection is not visible.",
      "The check compares against a single SystemUI package (the one returned by getSystemUiServiceComponent())."
     ],
     "behavior_changes": [
      "Requests to hide the SystemUI package now fail: the method returns false, for any caller reaching this check (including a DPC in a managed profile, per the commit message).",
      "The warning log now includes the actual package name (\"Cannot hide package: <name>\") instead of the fixed string \"Cannot hide package: android\".",
      "When packageName is not \"android\", the check now looks up PackageManagerInternal via LocalServices."
     ],
     "preconditions": [
      "Caller must be able to reach PackageManagerService's hide-package path for the SystemUI package; the commit message refers to a custom DPC and says hiding is harmful even inside a managed profile.",
      "Target package must be the SystemUI package (\"android\" was already blocked)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 36348592fd27 in android-security-14.0.0_r22, a single hunk (5 insertions, 3 deletions) in PackageManagerService.java at ~line 5765 (resolved via bug_id). At android-security-14.0.0_r29, 1/3 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/base bd7578b738a0 in android-security-15.0.0_r10, identical diff at ~line 5867. At android-security-15.0.0_r17, 1/3 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base 4e1be9d5eb9b in android-security-16.0.0_r1, identical diff at ~line 5957. At android-security-16.0.0_r8, 1/3 lines of the patched region are present.",
      "17": "not listed by the bulletin (not affected per the bulletin); at android-security-17.0.0_r1, 2/3 lines of the patched region are present. The actual code there is not in the material."
     },
     "backport_notes": [
      "Single-file, single-hunk change that depends on PackageManagerInternal.getSystemUiServiceComponent() and LocalServices; the same diff appears in 14, 15 and 16 at different line offsets, so a fork needs to locate its existing `\"android\".equals(packageName)` hide check."
     ],
     "confidence": "high",
     "unknowns": [
      "The name of the enclosing method is not shown (the hunk header shows only the class).",
      "The per-major facts report only 1/3 patched-region lines present at the latest 14/15/16 security tags even though the fix is resolved there; whether this is a line-matching artifact or a later rewrite of the region is not shown.",
      "What the patched region looks like at android-security-17.0.0_r1 (2/3 lines matched) is not shown.",
      "Whether other package state paths (disable, suspend, uninstall for user) have equivalent SystemUI protection is not visible in the material.",
      "The exact API/permission used to trigger the hide is not shown beyond the commit message's reference to a custom DPC."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48540": {
     "cve": "CVE-2025-48540",
     "summary": "Three small commits to libs/binder/RpcState.cpp make RPC Binder terminate the session (session->shutdownAndWait(false)) on error paths that previously only returned an error code and left the session alive. The paths are: a TRANSACT command received when a non-ANY CommandType was expected; a bad object-table span in an incoming transaction; and CommandData allocation failure in processTransact and waitForReply.",
     "component": "platform/frameworks/native, libbinder RPC Binder (libs/binder/RpcState.cpp)",
     "weakness": "Improper handling of exceptional conditions: unrecoverable RPC wire-protocol errors returned an error status without shutting down the session, which could leave the non-self-synchronizing wire stream desynchronized",
     "root_cause": "RpcState processes a byte stream from a remote peer, and its wire protocol is not self-synchronizing (the existing comment in processCommand says there is no way to tell where one command ends and the next begins). Several error paths detected malformed or unexpected input but only returned a status code (BAD_TYPE, BAD_VALUE, NO_MEMORY) without calling session->shutdownAndWait(false): (1) processCommand received RPC_COMMAND_TRANSACT while the caller requested a CommandType other than ANY, and returned BAD_TYPE before processTransact could read the body; (2) in the incoming-transaction path (processTransactInternalTailCall label), the object-table bytes inferred from parcelDataSize could not be reinterpreted as uint32_t, and the code returned BAD_VALUE; (3) processTransact and waitForReply failed to allocate CommandData for the body and returned NO_MEMORY without reading it. In each case the session continued, so the stream could be left desynchronized. The SPAN commit calls its error return 'not recoverable', and the ENOMEM comment says 'the wire protocol gets messed up'.",
     "fix_analysis": [
      "Commit 995eb96a80a2 (RPC Binder: shutdown on SPAN error), libs/binder/RpcState.cpp, transaction path (hunk at the processTransactInternalTailCall label): when objectTableBytes->reinterpret<const uint32_t>() yields no value, it adds (void)session->shutdownAndWait(false) before return BAD_VALUE.",
      "Commit 71342a41e519 (RPC Binder: clearer errors for wrong transact type), libs/binder/RpcState.cpp:processCommand: in the RPC_COMMAND_TRANSACT case with type != CommandType::ANY, the code now logs ALOGE(\"CommandType %d, but got RPC command %d.\") and calls (void)session->shutdownAndWait(false) before return BAD_TYPE. This mirrors the existing unknown-command handling at the end of processCommand, which already ended the session.",
      "Commit e73e9de0288c (RPC Binder: shutdown on ENOMEM), libs/binder/RpcState.cpp:waitForReply and libs/binder/RpcState.cpp:processTransact: when CommandData(...).valid() is false, the code calls (void)session->shutdownAndWait(false) before return NO_MEMORY. A comment (b/404210068) explains that the wire protocol gets messed up, because recovering gracefully would mean reading the body and still sending a reply.",
      "Return codes are unchanged. Only session shutdown (and one log line) is added."
     ],
     "completeness": [
      "Covered: a wrong-type TRANSACT in processCommand; a bad object-table span in the incoming transaction path; CommandData allocation failure in processTransact and waitForReply.",
      "The existing size checks in waitForReply (bodySize < rpcReplyWireSize; parcelDataSize larger than the available bytes) already called shutdownAndWait. These commits do not change them.",
      "Not covered: in waitForReply at android-security-14.0.0_r29 (lines 689-697), the sibling check where objectTableBytes->reinterpret<const uint32_t>() fails on an RpcWireReply still returns BAD_VALUE without session->shutdownAndWait(false), unlike the fixed transaction-side check.",
      "The ENOMEM commit message says 'More work needs to be done to ignore transactions in out of memory conditions'. The fix ends the session rather than recovering gracefully."
     ],
     "behavior_changes": [
      "An RPC session is now shut down (shutdownAndWait(false)) when a TRANSACT command arrives while a non-ANY CommandType was expected. Before, only BAD_TYPE was returned.",
      "A new ALOGE line is logged for the wrong-type TRANSACT case.",
      "The session is shut down when the object-table span of an incoming transaction is malformed. Before, only BAD_VALUE was returned.",
      "The session is shut down when the body buffer for a transaction or a reply cannot be allocated. Before, only NO_MEMORY was returned. Return codes themselves are unchanged."
     ],
     "preconditions": [
      "The process uses RPC Binder (RpcSession/RpcServer, RpcState) with a remote peer that sends wire data.",
      "Wrong-type case: processCommand is invoked with a CommandType other than ANY while the peer sends RPC_COMMAND_TRANSACT. The context shows getAndExecuteCommand(connection, session, type) called in a loop (line 768); the call chain into processCommand is inferred. waitForReply calls processCommand with CommandType::ANY (line 637), so that call is not affected.",
      "SPAN case: the session protocol version is >= RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE, and the transaction's parcelDataSize leaves an object-table byte count that cannot be reinterpreted as uint32_t.",
      "ENOMEM case: command.bodySize is such that CommandData allocation fails."
     ],
     "per_major": {
      "14": "Same fix: three commits (995eb96a80a2, 71342a41e519, e73e9de0288c) landed in android-security-14.0.0_r22. At android-security-14.0.0_r29, 5/5 patched-region lines are present. The shown context confirms the processCommand wrong-type change and the waitForReply ENOMEM change.",
      "15": "Same fix: 7fb4755c9d93 (clearer errors for wrong transact type) in android-security-15.0.0_r10, with an identical hunk at a shifted line (824). 5/6 patched-region lines are present at android-security-15.0.0_r17. The material does not show whether the SPAN and ENOMEM commits are on 15.",
      "16": "Same fix: a9e6d5a3c679 (clearer errors for wrong transact type) in android-security-16.0.0_r1, with an identical hunk at line 827. 5/6 patched-region lines are present at android-security-16.0.0_r8. The material does not show whether the SPAN and ENOMEM commits are on 16.",
      "17": "Not affected per the bulletin. At android-security-17.0.0_r1 only 3/6 patched-region lines match; the material does not show the Android 17 code."
     },
     "backport_notes": [
      "Apply in order: 995eb96a80a2 (SPAN), then 71342a41e519 (wrong type), then e73e9de0288c (ENOMEM). The diff index hashes chain 03fa69973d -> 5935e95f50 -> 11868a5bbc -> 6bdd385822. Each hunk only adds (void)session->shutdownAndWait(false), plus one ALOGE in the wrong-type commit.",
      "Consider also adding the same shutdown to the reply-side reinterpret<const uint32_t>() failure in waitForReply. The shown Android 14 code leaves it without shutdownAndWait."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact security consequence (how a desynchronized session leads to the bulletin's EoP) is not shown.",
      "Whether the SPAN (995eb96a80a2) and ENOMEM (e73e9de0288c) commits were also applied on Android 15 and 16. Only the wrong-type commit is shown for those branches.",
      "The Android 17 code state (3/6 lines) is not shown.",
      "The name and signature of the function containing the SPAN hunk; only the processTransactInternalTailCall label is shown."
     ],
     "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-48541": {
     "cve": "CVE-2025-48541",
     "summary": "FaceSettings now only honours the hw_auth_token, sensor id, challenge and EXTRA_USER_ID intent extras when the activity's initial calling package is the Settings app itself; for any other (or unknown) caller it ignores them and uses the current user.",
     "component": "platform/packages/apps/Settings, biometrics/face (FaceSettings fragment)",
     "weakness": "Missing caller validation / trust of untrusted intent extras (improper authorization leading to cross-user action)",
     "root_cause": "In src/com/android/settings/biometrics/face/FaceSettings.java the fragment unconditionally read KEY_TOKEN (\"hw_auth_token\"), BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, BiometricEnrollBase.EXTRA_KEY_CHALLENGE and Intent.EXTRA_USER_ID from the launching intent, without checking who launched it. The commit's test line (adb shell am start -a android.settings.FACE_SETTINGS --ei android.intent.extra.USER_ID 0, run from a user other than 0) shows that an external launch could make the face settings screen target a different user than the caller.",
     "fix_analysis": [
      "FaceSettings.java, field declarations: mSensorId now defaults to -1 and mChallenge to 0 so that they hold safe values when the extras are not read (mToken stays null).",
      "FaceSettings.java, the initialization method that obtains mUserManager/mFaceManager/mDevicePolicyManager (hunk around line 176 in 14; method name not shown in the hunk header): the activity is obtained via requireActivity() cast to SettingsActivity and activity.getInitialCallingPackage() is compared to activity.getPackageName().",
      "If the calling package is null or differs from Settings' own package, only mUserId = UserHandle.myUserId() is set; the token, sensor id, challenge and EXTRA_USER_ID extras are never read.",
      "Only when the initial caller is Settings itself are the four extras read as before (mToken, mSensorId, mChallenge, mUserId with myUserId fallback).",
      "The rest of the flow (managed-profile check via mUserManager.getUserInfo(mUserId), controllers) is unchanged and now sees either the trusted values or the safe defaults."
     ],
     "completeness": [
      "Covers all four intent extras consumed by FaceSettings in this method (token, sensor id, challenge, user id) with one gate on the initial calling package.",
      "The gate relies on SettingsActivity.getInitialCallingPackage(); the diff does not show how that value is derived or whether it survives activity recreation, so correctness of the check depends on that helper (not shown).",
      "Only FaceSettings is changed; whether sibling biometric settings fragments read the same extras in the same way is not shown in the material.",
      "Single-commit fix; no tests added (Test: manual)."
     ],
     "behavior_changes": [
      "Launching android.settings.FACE_SETTINGS from any package other than Settings now ignores EXTRA_USER_ID and always targets the calling user (UserHandle.myUserId()).",
      "External callers can no longer supply hw_auth_token, EXTRA_KEY_SENSOR_ID or EXTRA_KEY_CHALLENGE; those fields stay null/-1/0 for such callers.",
      "Internal navigation from Settings to FaceSettings is unchanged."
     ],
     "preconditions": [
      "A package other than Settings starts the face settings screen (e.g. action android.settings.FACE_SETTINGS) with EXTRA_USER_ID and/or token/sensor/challenge extras set.",
      "Per the commit's test, the caller runs as a user other than the targeted one (e.g. a non-zero user passing EXTRA_USER_ID 0)."
     ],
     "per_major": {
      "14": "same fix: f3cc1073e51e in android-security-14.0.0_r22 (cherry-pick, same Change-Id); 7/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: aba2077a6c03 in android-security-15.0.0_r10; identical logic, only context differs (FeatureFactory.getFeatureFactory()); 7/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: 2aa27461a40b in android-security-16.0.0_r1; identical logic; 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 fix is self-contained in FaceSettings.java but depends on SettingsActivity.getInitialCallingPackage() existing and on the hosting activity being a SettingsActivity (requireActivity() is cast unconditionally); confirm both in the target branch before applying.",
      "Also keep the field initializers (mSensorId = -1, mChallenge = 0) since the external-caller branch no longer assigns them."
     ],
     "confidence": "high",
     "unknowns": [
      "Implementation of SettingsActivity.getInitialCallingPackage() and how it is populated is not in the material.",
      "What a caller-supplied token, challenge or user id enabled downstream (e.g. in the remove/enroll controllers) is not shown; the bulletin only rates it High EoP.",
      "The name of the patched method is not shown in the hunk header.",
      "Whether other biometric settings fragments read the same extras unconditionally is not shown.",
      "Which line of the patched region is missing in the latest tags (7/8) is not shown.",
      "Any hardware requirement (e.g. a face sensor) for reaching the screen is not 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."
     }
    },
    "CVE-2025-48542": {
     "cve": "CVE-2025-48542",
     "summary": "Adds per-account storage accounting to AccountManagerService. Once an account's estimated user-data and auth-token storage exceeds 30,000,000 divided by the number of accounts, new user-data writes are dropped silently and auth-token writes return false. Passwords longer than 1,000,000 characters are rejected on account add and on password change.",
     "component": "platform/frameworks/base, system_server AccountManagerService (services/core/java/com/android/server/accounts/AccountManagerService.java)",
     "weakness": "Missing resource limit (uncontrolled growth of per-account data stored in the AccountManager database and its in-memory caches)",
     "root_cause": "AccountManagerService accepted user data (setUserdataInternal), auth tokens (the save path calling accounts.accountsDb.insertAuthToken) and passwords with no size limit. Only the account name was length-limited (existing 200-character check). Nothing tracked how much storage an account used in the accounts DB or in userDataCache/authTokenCache, so a caller able to write to an account could grow the database and the system_server caches without limit. The bulletin rates this as a High severity DoS.",
     "fix_analysis": [
      "AccountManagerService.java: adds constants MAXIMUM_PASSWORD_LENGTH = 1000*1000 and STORAGE_LIMIT_PER_USER = 30*1000*1000, and a new per-UserAccounts map mCacheSizeForAccount (Account -> estimated size), documented as protected by cacheLock.",
      "AccountManagerService.java:computeEntrySize(key, value): returns key.length() + value.length() + 20 (a null key or value counts as 1) as the size estimate for one key/value entry.",
      "AccountManagerService.java:recomputeCacheSizeForAccountLocked(accounts, account): sums computeEntrySize over the account's entries in accounts.userDataCache and accounts.authTokenCache and stores the result in mCacheSizeForAccount.",
      "AccountManagerService.java:shouldBlockDatabaseWrite(accounts, account, key, value): adds the new entry's estimated size to the running total. It returns false early if the total is below STORAGE_LIMIT_PER_USER/100. Otherwise it counts the accounts in accounts.accountCache (minimum 1) and returns false if the total is below STORAGE_LIMIT_PER_USER/numberOfAccounts. If still over, it recomputes the size from the caches, adds the entry again and re-checks the same threshold. If the total is still over, it logs a warning and returns true.",
      "AccountManagerService.java:setUserdataInternal: inside dbLock, takes cacheLock. When value != null and shouldBlockDatabaseWrite returns true, it logs and returns without touching the DB (fail silently).",
      "AccountManagerService.java: auth-token save path (the method containing deleteAuthtokensByAccountIdAndType/insertAuthToken; its name is not shown in the hunk header): computes shouldBlockWrite under cacheLock before beginTransaction. After deleteAuthtokensByAccountIdAndType, if authToken != null and shouldBlockWrite is true, it logs and returns false before insertAuthToken, so setTransactionSuccessful is never reached.",
      "AccountManagerService.java: the account add path (the region with the existing 'Name longer than 200 chars' check) returns false when password != null and its length exceeds MAXIMUM_PASSWORD_LENGTH. The set-password path ('New password is too long' log) returns early for the same condition.",
      "AccountManagerService.java:writeUserDataIntoCacheLocked and writeAuthTokenIntoCacheLocked: when the per-account cache map has just been loaded from the DB (findUserExtrasForAccount / findAuthTokensByAccount), they call recomputeCacheSizeForAccountLocked. The lazy-load branches in readAuthTokenInternal and in the user-data read path do the same.",
      "AccountManagerService.java: the rename path calls recomputeCacheSizeForAccountLocked for renamedAccount. Two cache-cleanup sites remove the account from mCacheSizeForAccount alongside the other per-account caches; their method names are not shown in the hunk headers."
     ],
     "completeness": [
      "Covers the two key/value write paths (user data via setUserdataInternal, auth tokens via the insertAuthToken path) and limits password length on account add and on set-password.",
      "Password size is limited separately (1,000,000 characters) and is not included in the storage estimate. The account name was already limited to 200 characters by existing code.",
      "The estimate is approximate by design ('Estimation is not precise for updates to existing values'). The running total is incremented on every check, including overwrites, and is corrected by recomputeCacheSizeForAccountLocked only near the threshold. The recount uses only the in-memory caches.",
      "Deletions (value == null) are never blocked. Only userDataCache and authTokenCache entries are counted. Other per-account state (visibilityCache, previousNameCache, accountTokenCaches) is not counted by this change.",
      "The limit is per account (STORAGE_LIMIT_PER_USER / numberOfAccounts), and the fix does not cap the number of accounts. A code comment calls 100 the upper bound on accounts, but the material does not show where, or whether, that bound is enforced.",
      "In the auth-token path, the storage decision is made before the transaction under its own cacheLock acquisition, and both steps are inside dbLock."
     ],
     "behavior_changes": [
      "User-data writes for an account whose estimated user data plus auth tokens exceed 30,000,000/numberOfAccounts (and at least 300,000) are dropped silently with a Log.w. No error is returned to the caller.",
      "Auth-token writes over the same limit return false from the save path, and setTransactionSuccessful is skipped. The pending deleteAuthtokensByAccountIdAndType is therefore not committed, assuming the transaction is ended in a finally block that the diff does not show.",
      "Account adds with a password longer than 1,000,000 characters fail (return false, Log.w). A set-password call with such a password is ignored (Log.w, early return).",
      "Writes near the limit add CPU work in system_server: a full recount of the account's cached entries and a count of all accounts in accountCache."
     ],
     "preconditions": [
      "A caller able to use the AccountManager write APIs that reach setUserdataInternal, the auth-token save path, the account add path or the set-password path for a given account. The diff does not show the permission checks on those paths."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 9a4d7193f893 in android-security-14.0.0_r22 (cherry-pick of 6b8138ca5279, Change-Id I88a0fef8e2e7bc232768bd5f7aa3f4bf87cb1c2c); 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 7ed50e5b5d56 in android-security-15.0.0_r10. The hunks are the same; only the cancelNotification context line differs. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 50ae729881da in android-security-16.0.0_r1. setUserdataInternal there takes an extra callingUid parameter, but the inserted block is the same. 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 that touches only AccountManagerService.java. It relies on the existing userDataCache, authTokenCache, accountCache, cacheLock and dbLock fields, and on the lazy-load branches in writeUserDataIntoCacheLocked, writeAuthTokenIntoCacheLocked and readAuthTokenInternal. On 16 the only difference is the setUserdataInternal signature (extra callingUid)."
     ],
     "confidence": "high",
     "unknowns": [
      "The bulletin lists Android 13 as affected, but the material includes no Android 13 commit.",
      "The hunk headers (all 'public class AccountManagerService') do not name the enclosing methods of the auth-token save path, the account add path, the set-password path, the rename path or the cache-cleanup sites.",
      "Where, or whether, the comment-stated upper bound of 100 accounts per user is enforced is not shown.",
      "The material does not show which callers or permission levels can reach the affected write paths, or which resource is exhausted (DB size or system_server heap), beyond the bulletin's DoS classification. The commit was tested manually only.",
      "The finally block of the auth-token transaction is not shown, so the rollback of the preceding delete is inferred rather than visible."
     ],
     "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-48543": {
     "cve": "CVE-2025-48543",
     "summary": "Adds an instantiability check to three ART JNI object creation entry points in jni_internal.cc (AllocObject, NewObjectV and NewObjectA, per the matching CheckJNI call sites). Creating an instance of an abstract class or interface through them now throws java.lang.InstantiationException. The fix also relaxes the CheckJNI helper so it only rejects array classes.",
     "component": "platform/art, runtime JNI (runtime/jni/jni_internal.cc, runtime/jni/check_jni.cc)",
     "weakness": "Missing validation: JNI object creation did not check that the target class is instantiable, so objects of abstract classes or interfaces could be created (type-safety violation).",
     "root_cause": "In runtime/jni/jni_internal.cc, three object creation functions in class JNI decoded the jclass, returned if it was nullptr, and then went straight to allocation, with a special case for the String class. None of them checked that the class is instantiable; the test's info.txt says \"we forgot to check if a class is instantiable\". The only guard was in CheckJNI: ScopedCheck::CheckInstantiableNonArray aborted on !IsInstantiableNonArray(). CheckJNI is a debugging layer that is normally off on production builds (general platform knowledge). The regression test (test/863-serialization) reaches the unchecked path from Java through ObjectInputStream.readObject on a serialized stream that refers to java.text.DateFormat. On the fixed runtime this fails with an InvalidClassException whose cause is an InstantiationException.",
     "fix_analysis": [
      "runtime/jni/jni_internal.cc, first hunk in class JNI (AllocObject, the variant that picks gc::AllocatorType for the String case): after `if (c == nullptr) return nullptr;` and before the IsStringClass() branch, adds `if (UNLIKELY(!c->IsInstantiable()))`. When the class is not instantiable it calls soa.Self()->ThrowNewExceptionF(\"Ljava/lang/InstantiationException;\", \"Can't instantiate %s %s\", ...) and returns nullptr. The message says \"interface\" or \"abstract class\" depending on c->IsInterface(), followed by c->PrettyDescriptor().",
      "runtime/jni/jni_internal.cc, second and third hunks (NewObjectV and NewObjectA, the paths that replace String.<init> with a StringFactory call): the same check is inserted at the same position.",
      "runtime/jni/check_jni.cc: ScopedCheck::CheckInstantiableNonArray is renamed to CheckNonArray, and its condition changes from `!c->IsInstantiableNonArray()` to `c->IsArrayClass()`. Its three call sites (CheckJNI::AllocObject, CheckJNI::NewObjectV and CheckJNI::NewObjectA) now call CheckNonArray. CheckJNI therefore still aborts for array classes but no longer aborts for abstract classes or interfaces; it forwards those to the base JNI, which now throws.",
      "test/863-serialization (info.txt, Main.java, empty expected-stdout and expected-stderr): regression test that deserializes a fixed byte stream (ConcurrentHashMap with java.text.DateFormat entries). It expects an InvalidClassException whose cause is an InstantiationException."
     ],
     "completeness": [
      "Covers the three JNI object creation implementations touched in jni_internal.cc. The variadic NewObject entry point does not appear in the diff.",
      "The check uses mirror::Class::IsInstantiable(). The exception message treats the failing cases as interfaces and abstract classes. Array classes are still rejected by CheckJNI (CheckNonArray); the diff does not show how the base JNI path handles them.",
      "Allocation paths outside JNI (reflection, Unsafe, interpreter or compiler new-instance) are not touched by this commit. The material does not show whether they already have equivalent checks."
     ],
     "behavior_changes": [
      "JNI AllocObject, NewObjectV and NewObjectA now return nullptr with a pending java.lang.InstantiationException (\"Can't instantiate interface|abstract class <descriptor>\") when the jclass is an abstract class or interface.",
      "Under CheckJNI, passing an abstract class or interface no longer aborts with \"can't make objects of type ...\"; only array classes still trigger that abort.",
      "Java deserialization of the test stream now fails with InvalidClassException caused by InstantiationException (test 863-serialization)."
     ],
     "preconditions": [
      "A caller reaches one of the three patched JNI functions with a jclass for an abstract class or interface. The regression test shows this is reachable from Java via ObjectInputStream.readObject on a specific serialized stream."
     ],
     "per_major": {
      "14": "Same fix: platform/art caeef036e31b in android-security-14.0.0_r22 (Change-Id I4ccf22f85b4ae0325e9f8e29503149bbda533e86). 6/8 lines of the patched region are reported present at android-security-14.0.0_r29.",
      "15": "Same fix: platform/art 444fc40dfb04 in android-security-15.0.0_r10. The hunks are identical; only line offsets differ (jni_internal.cc at 924/951/977 instead of 921/948/974). 6/8 lines present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/art 3eb64751e5a7 in android-security-16.0.0_r1. Identical hunks at offsets 922/949/975. 6/8 lines present at android-security-16.0.0_r8.",
      "17": "Not affected: the bulletin does not list Android 17. 6/8 lines of the patched region are reported present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. The three jni_internal.cc insertions and the check_jni.cc rename apply unchanged on 14, 15 and 16; only line offsets differ. In a customized tree, insert each check right after the `if (c == nullptr) return nullptr;` that comes before the IsStringClass() branch in each of the three functions. Keep the check_jni.cc change so that CheckJNI builds behave the same as the base path."
     ],
     "confidence": "high",
     "unknowns": [
      "The memory-safety or privilege consequence of holding an instance of an abstract class or interface (how the EoP arises) is not described in the material.",
      "Which system process or IPC surface could reach this path is not shown; only the standalone test is given.",
      "The bulletin lists Android 13 as affected, but the material contains no Android 13 fix commit, so it cannot show whether the hunks apply there.",
      "Whether the variadic JNI NewObject goes through one of the patched functions is not shown in the diff.",
      "How the base JNI path handles array classes is not shown.",
      "Whether allocation paths outside JNI already enforce instantiability is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48545": {
     "cve": "CVE-2025-48545",
     "summary": "AccountManagerService.isSystemUid() now returns false immediately for SDK sandbox UIDs instead of deriving system status from the packages that PackageManager associates with that UID.",
     "component": "platform/frameworks/base, AccountManagerService (services/core/java/com/android/server/accounts)",
     "weakness": "Improper privilege / caller-identity check (SDK sandbox UID could be classified as a system UID)",
     "root_cause": "AccountManagerService.isSystemUid(int callingUid) decides whether a caller is \"system\" by calling mPackageManager.getPackagesForUid(callingUid) and returning true if any returned package has ApplicationInfo.FLAG_SYSTEM. It did not exclude SDK sandbox UIDs. A sandbox caller could therefore be classified as system if PackageManager returned a system-flagged package for its UID, and then pass checks in AccountManagerService that rely on isSystemUid(). The material does not show which package is returned for a sandbox UID. The bulletin rates this as High severity information disclosure.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountManagerService.java:isSystemUid(): adds an early `if (Process.isSdkSandboxUid(callingUid)) return false;` before the package lookup. SDK sandbox callers are therefore never classified as system, whichever packages PackageManager reports for that UID.",
      "The rest of the method is unchanged: clearCallingIdentity, getPackagesForUid, the FLAG_SYSTEM loop and restoreCallingIdentity. Only the sandbox case is short-circuited."
     ],
     "completeness": [
      "The fix covers every caller of isSystemUid() in AccountManagerService, because the check is inside the helper itself.",
      "It only addresses the SDK sandbox UID case. The underlying approach of inferring system status from FLAG_SYSTEM on any package sharing the UID is otherwise unchanged.",
      "Other privilege checks in the file that do not go through isSystemUid() are not touched by this commit. One example is the direct `UserHandle.getAppId(callerUid) == Process.SYSTEM_UID` comparison at line 5969 of the surrounding context. The material does not show whether those checks needed changes.",
      "The material does not show which AccountManagerService entry points call isSystemUid(), and the index search returned no caller chunks. The exact set of protected operations therefore cannot be listed here."
     ],
     "behavior_changes": [
      "isSystemUid() now treats callers running in an SDK sandbox process as non-system. Any AccountManagerService operation gated on that helper will take the non-system path for sandbox callers.",
      "No new error codes, logging or API changes, and no behavior change for non-sandbox UIDs."
     ],
     "preconditions": [
      "The caller is a process running under an SDK sandbox UID (Process.isSdkSandboxUid(callingUid) is true) for which the package lookup in isSystemUid() would otherwise find a FLAG_SYSTEM package.",
      "The caller reaches an AccountManagerService code path that uses isSystemUid() to grant elevated treatment. The specific entry points are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 30dc386d4a57 in android-security-14.0.0_r22, a 3-line early return in isSystemUid(). 3/3 lines are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 66ac17909252 in android-security-15.0.0_r10, identical hunk at a different line offset (6080). 3/3 lines are present at android-security-15.0.0_r17.",
      "16": "same fix: commit c21f79312d3f in android-security-16.0.0_r1, identical hunk at line offset 6146. 3/3 lines are present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin. The 3 patched lines are already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained 3-line change, identical on 14, 15 and 16. It needs isSystemUid() in AccountManagerService.java and Process.isSdkSandboxUid() (added with the SDK sandbox in Android 13; general platform knowledge). The bulletin lists Android 13 as affected, but the material includes no 13 commit."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which AccountManagerService methods call isSystemUid(), and therefore what account data or operations were exposed.",
      "Which packages PackageManager.getPackagesForUid() returns for an SDK sandbox UID, and so exactly how a sandbox caller was classified as system before the fix.",
      "Whether an Android 13 fix commit exists (the bulletin lists 13 as affected, but no 13 commit is in the material).",
      "The original bug report details (Bug 397438392) and any accompanying test changes."
     ],
     "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-48546": {
     "cve": "CVE-2025-48546",
     "summary": "SafeActivityOptions now rejects with a SecurityException any activity start whose ActivityOptions carry setLaunchWindowingMode(WINDOWING_MODE_PINNED), pointing callers to ActivityOptions#makeLaunchIntoPip instead.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (services/core/java/com/android/server/wm/SafeActivityOptions.java)",
     "weakness": "Missing validation of a caller-supplied launch parameter (ActivityOptions launch windowing mode PINNED not rejected in SafeActivityOptions)",
     "root_cause": "SafeActivityOptions validated several caller-controlled ActivityOptions fields (e.g. launch activity type gated on isSystemOrSystemUI; remote transition, per the existing SafeActivityOptionsTest cases) but did not validate the launch windowing mode. A caller could set WINDOWING_MODE_PINNED through ActivityOptions#setLaunchWindowingMode, and the removed tests testKeepsPictureInPictureLaunchModeInOptions and testKeepsPictureInPictureLaunchModeWithBoundsInOptions in TaskLaunchParamsModifierTests show that TaskLaunchParamsModifier kept WINDOWING_MODE_PINNED from the options (with or without launch bounds). The new comment names ActivityOptions#makeLaunchIntoPip as the public API to use instead. The bulletin rates this High / EoP.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/SafeActivityOptions.java (the validation method that emits 'Permission Denial: starting ...' messages; the method name is not shown in the hunk header): adds a check after the existing permission-denial block and before the launch-activity-type check. If options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED, it builds a 'Permission Denial ... with setLaunchWindowingMode=PINNED' message with the intent, callerApp, callingPid and callingUid, logs it with Slog.w(TAG, msg) and throws new SecurityException(msg).",
      "Within the hunk the check has no caller condition: unlike the adjacent activity-type check it is not gated on isSystemOrSystemUI or a permission. The comment directs callers to ActivityOptions#makeLaunchIntoPip.",
      "Adds a static import of android.app.WindowConfiguration.WINDOWING_MODE_PINNED.",
      "services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java: removes testKeepsPictureInPictureLaunchModeInOptions and testKeepsPictureInPictureLaunchModeWithBoundsInOptions, which asserted the now-rejected behavior.",
      "On Android 15 and 16 the commit also adds a regression case in SafeActivityOptionsTest.java (ActivityOptions.makeBasic(); setLaunchWindowingMode(WINDOWING_MODE_PINNED); verifySecureExceptionThrown). The Android 14 commit does not include this test change."
     ],
     "completeness": [
      "Covers exactly one field: options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED, checked at this validation point in SafeActivityOptions. An activity start whose ActivityOptions go through this check can no longer request PINNED.",
      "Other windowing modes requested via setLaunchWindowingMode are not restricted by this change; only PINNED is rejected.",
      "The public PiP route (ActivityOptions#makeLaunchIntoPip) is untouched; its own validation is not part of this diff.",
      "Whether any activity-start path consumes ActivityOptions without going through this SafeActivityOptions validation is not shown in the material."
     ],
     "behavior_changes": [
      "An activity start validated by this method whose ActivityOptions set setLaunchWindowingMode(WINDOWING_MODE_PINNED) now gets a SecurityException. Before the fix, the removed tests show TaskLaunchParamsModifier kept the PINNED mode.",
      "A new Slog.w line 'Permission Denial: starting <intent> from <callerApp> (pid=, uid=) with setLaunchWindowingMode=PINNED' is logged on rejection.",
      "The check runs before the launch-activity-type check, so a PINNED request is rejected whatever activity type is supplied."
     ],
     "preconditions": [
      "Caller starts an activity with an ActivityOptions bundle whose launch windowing mode is WINDOWING_MODE_PINNED (set via ActivityOptions#setLaunchWindowingMode), and the start is validated by SafeActivityOptions in system_server.",
      "The removed tests show the mode was kept by TaskLaunchParamsModifier on a freeform display, with or without launch bounds."
     ],
     "per_major": {
      "14": "same fix: commit 41bdf43266da in android-security-14.0.0_r22 (RESTRICT AUTOMERGE cherry-pick, Change-Id I9d37d41af3d86f785fd5d85503b544552b39cac4). Adds the PINNED check in SafeActivityOptions.java and removes two TaskLaunchParamsModifierTests; does not include the SafeActivityOptionsTest regression case. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 8b660e887005 in android-security-15.0.0_r10, identical SafeActivityOptions.java hunk at a different offset, plus removal of the two TaskLaunchParamsModifierTests and a PINNED regression case in SafeActivityOptionsTest.java. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 3668e1d4bbec in android-security-16.0.0_r1, identical SafeActivityOptions.java hunk plus the same test changes as Android 15. 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": [
      "Single self-contained commit: an 11-line block plus a static import in SafeActivityOptions.java, placed between the existing permission-denial block and the launch-activity-type check; the same hunk applies at different offsets on 14/15/16. Remove testKeepsPictureInPictureLaunchModeInOptions / ...WithBoundsInOptions from TaskLaunchParamsModifierTests if present; optionally add the SafeActivityOptionsTest case from the 15/16 commits (absent from the 14 commit)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the SafeActivityOptions method containing the new check (the hunk header shows only the class).",
      "Which callers or start paths could reach the unchecked PINNED launch, and what permissions they needed; the material shows only the removed TaskLaunchParamsModifier tests.",
      "Whether every caller (including system/SystemUI) reaches this check, and whether any activity-start path bypasses SafeActivityOptions validation."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48547": {
     "cve": "CVE-2025-48547",
     "summary": "PermissionController now stops a package's one-time permission session on revoke only when no remaining permission of that package is still granted as one-time, and the Kotlin helper that makes this decision is rewritten so its grant and one-time checks evaluate correctly.",
     "component": "platform/packages/modules/Permission, PermissionController (one-time permission session handling in AppPermissionGroup.java and KotlinUtils.kt)",
     "weakness": "Logic error in permission-state lifecycle handling: a shared per-package one-time session was stopped when a single one-time permission was revoked, even while other one-time grants in the same package remained. The Kotlin helper's grant and one-time checks were also computed incorrectly.",
     "root_cause": "A package has a single one-time permission session covering all of its one-time grants (commit message). In AppPermissionGroup.java, the else branch of the revoke path called PermissionManager.stopOneTimePermissionSession(packageName) unconditionally. In KotlinUtils.kt, the revoke path gated the stop on anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup), but that helper was flawed in four ways. It skipped every permission belonging to the group passed in. It returned true early when group.isOneTime was true. It masked the permission flags with FLAG_PERMISSION_ONE_TIME before testing FLAG_PERMISSION_REVOKED_COMPAT, so that test always passed. It tested grant state with requestedPermissionsFlags[idx] == PackageManager.PERMISSION_GRANTED instead of testing the REQUESTED_PERMISSION_GRANTED bit. As the commit message states, stopping the session on one revoke terminated the only session running for all of the package's one-time grants.",
     "fix_analysis": [
      "AppPermissionGroup.java (revoke path, else branch around line 1648): the unconditional call to stopOneTimePermissionSession(packageName) becomes `else if (!anyPermsOfPackageOneTimeGranted(getApp()))`, so the session is stopped only when no permission in the package is still granted as one-time.",
      "AppPermissionGroup.java: adds a new private helper anyPermsOfPackageOneTimeGranted(PackageInfo), annotated @SuppressLint(\"MissingPermission\"). It returns false if requestedPermissions or requestedPermissionsFlags is null. Otherwise it walks requestedPermissions and skips entries whose requestedPermissionsFlags[i] lacks PackageInfo.REQUESTED_PERMISSION_GRANTED. For each remaining entry it reads the full flags via mPackageManager.getPermissionFlags(perm, packageName, getUser()) and returns true if FLAG_PERMISSION_REVOKED_COMPAT is clear and FLAG_PERMISSION_ONE_TIME is set.",
      "KotlinUtils.kt (revoke path around line 1042): the call becomes anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo). Dropping the group argument means the scan no longer excludes the permissions of the group being revoked.",
      "KotlinUtils.kt:anyPermsOfPackageOneTimeGranted: removes the optional LightAppPermGroup parameter and the early `group?.isOneTime == true` return. The user is now always UserHandle.getUserHandleForUid(packageInfo.uid). The grant test now checks the REQUESTED_PERMISSION_GRANTED bit in requestedPermissionsFlags[index]. Flags are no longer pre-masked with FLAG_PERMISSION_ONE_TIME, so the FLAG_PERMISSION_REVOKED_COMPAT test is meaningful. isGrantedOneTime requires REVOKED_COMPAT clear and ONE_TIME set. This is the same grant and one-time test as the new Java helper, although the Java helper also has a null guard and takes the user from getUser()."
     ],
     "completeness": [
      "Both revoke paths in the material that call stopOneTimePermissionSession (the Java AppPermissionGroup model and KotlinUtils.kt) are now gated on a package-wide one-time check.",
      "The Java check takes the grant bit from the requestedPermissionsFlags snapshot in getApp() and reads flags live. Whether getApp() is refreshed before this point is not shown.",
      "The fix does not change framework-side session handling (PermissionManager.stopOneTimePermissionSession itself). It changes only when PermissionController decides to call it.",
      "Any other callers of stopOneTimePermissionSession outside these two sites are not covered by the diff and are not shown in the material."
     ],
     "behavior_changes": [
      "Revoking one one-time permission group no longer stops the package's one-time session while another permission of the package is still granted as one-time.",
      "The Java path now makes extra PackageManager.getPermissionFlags calls during revoke, one per granted requested permission.",
      "In the Kotlin path, the package-wide scan now includes the permissions of the group being revoked (previously skipped), and the grant and flag tests now use the REQUESTED_PERMISSION_GRANTED bit and unmasked flags. So the set of situations in which the stop call is issued changes."
     ],
     "preconditions": [
      "A package holds more than one permission granted as one-time (FLAG_PERMISSION_ONE_TIME set, FLAG_PERMISSION_REVOKED_COMPAT clear) under its single one-time session.",
      "One of those one-time permission groups is revoked through PermissionController (the AppPermissionGroup revoke path or the KotlinUtils revoke path) and reaches the stopOneTimePermissionSession branch.",
      "The material does not show the exact UI or entry points that drive these revoke paths, or what happens to the remaining one-time grants once the session is stopped, beyond what the commit message says."
     ],
     "per_major": {
      "14": "same fix: 122e39ced57a in android-security-14.0.0_r22 (cherry-pick of the internal change). The patched region is fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "same fix: 5dca0ccb26f2 in android-security-15.0.0_r10. The Java and helper changes are the same. The Kotlin caller context differs only by the existing ContextCompat.createDeviceContext lines. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: 78fc913decb3 in android-security-16.0.0_r1, matching the 15 change. 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 in two PermissionController files. Android 13 is listed as affected by the bulletin, but the material includes no 13 commit.",
      "When backporting the Kotlin hunk, remove the LightAppPermGroup parameter from anyPermsOfPackageOneTimeGranted and update the caller shown in the diff. The functional parts are: dropping the group skip and the isOneTime early return, removing the flag pre-mask, and switching to the REQUESTED_PERMISSION_GRANTED bit test. The variable rename from idx to index is cosmetic."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the name of the method in AppPermissionGroup.java that encloses the patched else branch.",
      "The framework-side effect of stopOneTimePermissionSession on the remaining one-time grants is not shown. The material only states that the shared session was terminated.",
      "The fix ships without a test (Test: manual), so the exact scenario Google validated is unknown.",
      "Whether getApp() returns a refreshed PackageInfo after the revoke, and how a stale grant bit would affect the Java check, is not shown.",
      "Whether other callers of stopOneTimePermissionSession exist is not shown.",
      "Whether an Android 13 commit exists is not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48548": {
     "cve": "CVE-2025-48548",
     "summary": "Makes audioserver's silencing of a recording client match the AppOps op state. startRecording now returns the exact permission result (granted/soft/hard denied). startInput un-silences a client only when the op was granted. finishRecording is called only for clients that were not silenced. Sources that do not need the record op are checked against their own op instead of having their result ignored. Companion frameworks/base changes fix AppOps attribution-chain preflight and cleanup, and the SystemUI mic indicator suppression logic.",
     "component": "platform/frameworks/av (audiopolicy service, media/utils ServiceUtilities) and platform/frameworks/base (PermissionManagerService AppOps checks, SystemUI AppOpsControllerImpl)",
     "weakness": "Inconsistent permission/AppOps state tracking. Recording was un-silenced and op start/finish calls were unbalanced when the RECORD op was soft-denied (refcount mismatch). The startRecording result was also ignored for some virtual sources.",
     "root_cause": "In media/utils/ServiceUtilities.cpp, checkRecordingInternal reduced the PermissionChecker result to a bool that was true for anything other than PERMISSION_HARD_DENIED, so a soft-denied result looked like success. AudioPolicyService::startInput (AudioPolicyInterfaceImpl.cpp) then always called setAppState_l(client, APP_STATE_TOP), forcing the client un-silenced whatever the real op state was. stopInput and the startInput failure path always called finishRecording, even for silenced clients, so op start/finish counts did not match. startInput also ignored a failing startRecording result for AUDIO_SOURCE_FM_TUNER, REMOTE_SUBMIX and ECHO_REFERENCE through an OR condition. In setAppState_l, a failed startRecording only set a local silenced flag and the function carried on. In AudioRecordClient.cpp, OpRecordAudioMonitor also monitored OP_RECORD_AUDIO whenever its op was different, even for sources that do not need it. A failed target SDK lookup returned -1, which was treated as pre-U, so WATCH_FOREGROUND_CHANGES was not used. On the frameworks/base side, the attribution-chain loop in PermissionManagerService did not finish proxy ops it had already started when a later link failed during startDataDelivery. The preflight path in performOpTransaction checked the first chain entry (the datasource, e.g. audioserver), so preflight could fail where delivery would succeed. SystemUI AppOpsControllerImpl suppressed the microphone indicator if any recording for a UID was paused, rather than only when all of them were.",
     "fix_analysis": [
      "av c962764b8234 (preparatory refactor): moves OpRecordAudioMonitor and AudioRecordClient out of AudioPolicyService.h/.cpp into new services/audiopolicy/service/AudioRecordClient.h/.cpp (namespace android::media::audiopolicy). It adds the new file to Android.bp and makes AudioCommandThread and AudioClient public so the new classes can reference them. The code is moved, not changed.",
      "av fa795ebf0e65, media/utils/ServiceUtilities.cpp: getOpForSource now returns OP_NONE for AUDIO_SOURCE_FM_TUNER. A new isRecordOpRequired(source) returns false for FM_TUNER, ECHO_REFERENCE, REMOTE_SUBMIX and VOICE_DOWNLINK. checkRecordingInternal now returns an int permission result. For record-op sources on start, it first calls checkPermissionForPreflightFromDatasource. Only if that returns GRANTED does it call checkPermissionForStartDataDeliveryFromDatasource; otherwise it calls checkPermissionForDataDeliveryFromDatasource and discards the result. For non-record-op sources it calls AppOpsManager::startOpNoThrow on the attributed op directly, and returns GRANTED if the op is OP_NONE. finishRecording mirrors this: finishDataDeliveryFromDatasource for record-op sources, AppOpsManager::finishOp for the others. startRecording now returns int; recordingAllowed keeps bool semantics (!= HARD_DENIED).",
      "av fa795ebf0e65, AudioPolicyInterfaceImpl.cpp AudioPolicyService::startInput: removes the FM_TUNER/REMOTE_SUBMIX/ECHO_REFERENCE OR condition and returns PERMISSION_DENIED only when permitted == PERMISSION_HARD_DENIED. It calls setAppState_l(client, APP_STATE_TOP) only on PERMISSION_GRANTED and APP_STATE_IDLE otherwise. In the error path it calls finishRecording only if !client->silenced. AudioPolicyService::stopInput: calls finishRecording only if !client->silenced.",
      "av fa795ebf0e65, AudioPolicyService.cpp AudioPolicyService::setAppState_l: when startRecording != PERMISSION_GRANTED, the function returns instead of setting silenced = true and continuing.",
      "av fa795ebf0e65, AudioRecordClient.cpp/.h: OpRecordAudioMonitor gains a const bool mShouldMonitorRecord, set from isRecordOpRequired and passed in by createIfNeeded. onFirstRef registers for OP_RECORD_AUDIO, and checkOp also requires OP_RECORD_AUDIO, only when mShouldMonitorRecord is set. When the package manager call fails, getTargetSdkForPackageName now returns __ANDROID_API_FUTURE__ instead of -1, so WATCH_FOREGROUND_CHANGES is used.",
      "base bbd8db54b3c9, PermissionManagerService.java attribution-chain loop: tracks prev. When startDataDelivery is set, opMode != MODE_ALLOWED and prev != null, it truncates the chain after prev.next and calls finishDataDelivery.",
      "base 2c18e5f08282, PermissionManagerService.performOpTransaction (preflight, !forDataDelivery): when skipProxyOperation && !singleReceiverFromDatasource, it uses MODE_ALLOWED for the first chain entry instead of unsafeCheckOpRawNoThrow. This matches the data-delivery path. Per the commit message, it avoids preflight failing where delivery would succeed, e.g. for audioserver.",
      "base 00344da68fce (Android 15 commit), packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java: isAnyRecordingPausedLocked is replaced by isAllRecordingPausedLocked. The mic indicator item is disabled only if mMicMuted is set or every AudioRecordingConfiguration for the UID reports isClientSilenced(). Tests are updated in AppOpsControllerTest.java."
     ],
     "completeness": [
      "Covers startInput and stopInput in AudioPolicyInterfaceImpl.cpp and the un-silence path in setAppState_l. finishRecording is gated on client->silenced in the startInput error path and in stopInput; setAppState_l returns early when startRecording is not GRANTED.",
      "All audio_source_t values go through getOpForSource/isRecordOpRequired, so the old OR condition in startInput is gone. FM_TUNER now maps to OP_NONE, so no op is checked for it.",
      "The code comment calls the preflight-then-start sequence in checkRecordingInternal 'technically racy' and a workaround for the AppOps paused-op behavior (TODO b/294609684). This material does not change the AppOps pause state itself.",
      "The frameworks/base chain cleanup runs only when prev != null.",
      "The SystemUI indicator change is shown only as an Android 15 commit. The material does not show whether an equivalent change was applied to Android 14."
     ],
     "behavior_changes": [
      "startInput still returns PERMISSION_DENIED on hard denial. On a non-granted, non-hard-denied result, the input now starts silenced (APP_STATE_IDLE) instead of being forced un-silenced.",
      "REMOTE_SUBMIX and ECHO_REFERENCE recordings now go through AppOpsManager::startOpNoThrow/finishOp for OP_RECORD_AUDIO_OUTPUT. VOICE_DOWNLINK uses OP_RECORD_INCOMING_PHONE_AUDIO without also requiring OP_RECORD_AUDIO. FM_TUNER has no op (OP_NONE).",
      "When a start is restricted, a data-delivery (note) check is issued instead of a start, so op-rejected listeners still fire (per the code comment).",
      "finishRecording is skipped for silenced clients.",
      "If the target SDK lookup fails, the package is treated as targeting a future SDK and WATCH_FOREGROUND_CHANGES is applied.",
      "PermissionManagerService preflight no longer checks the op on the first chain entry when skipProxyOperation is set and the source is not singleReceiverFromDatasource. A failing later link during startDataDelivery now finishes the proxy op started for the previous link.",
      "SystemUI shows the microphone indicator unless all recordings for the UID are silenced or the mic is muted (Android 15 commit)."
     ],
     "preconditions": [
      "A client whose UID is not audioserver, mediaserver, system_server or root obtains an audio input and calls startInput/stopInput. These are AudioPolicyService binder methods (AudioPolicyInterfaceImpl.cpp).",
      "The RECORD_AUDIO op, or the source-specific op, is restricted or soft-denied at start time, or changes while recording, so the permission result is neither GRANTED nor HARD_DENIED.",
      "For the frameworks/base chain fix: an attribution chain in which a later link fails the op check during startDataDelivery.",
      "Per index search, SensorPrivacyPolicy::onSensorPrivacyChanged in AudioPolicyService.cpp calls updateUidStates."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin. The material contains no commit or code for Android 13.",
      "14": "Fixed at android-security-14.0.0_r22 by RESTRICT AUTOMERGE cherry-picks: av c962764b8234 (refactor), av fa795ebf0e65 (core fix), base 2c18e5f08282 (preflight skip) and base bbd8db54b3c9 (chain cleanup). At android-security-14.0.0_r29 the patched region is fully present (8/8 lines) and the code context matches the post-fix state.",
      "15": "Resolved at android-security-15.0.0_r10. The Android 15 commit shown is base 00344da68fce (SystemUI AppOpsControllerImpl isAllRecordingPausedLocked). At android-security-15.0.0_r17, 6/8 lines of the patched region are present. The material does not show whether the av or PermissionManagerService commits were applied to 15.",
      "16": "Not affected (not listed by the bulletin). At android-security-16.0.0_r8 the patched region is present (8/8 lines).",
      "17": "Not affected (not listed by the bulletin). At android-security-17.0.0_r1 the patched region is present (8/8 lines)."
     },
     "backport_notes": [
      "Apply in this order: av c962764b8234, then an intermediate change not in the material, then av fa795ebf0e65. The fa795ebf0e65 pre-image of AudioRecordClient.cpp already contains reg()/check() lambdas and OP_RECORD_AUDIO co-monitoring, which c962764b8234 does not produce. The startRecording return type changes from bool to int in ServiceUtilities.h, so every caller must be checked.",
      "base 2c18e5f08282 changes preflight behavior that, per its commit message, audioserver relies on. Carry it together with av fa795ebf0e65, which adds a checkPermissionForPreflightFromDatasource call. The material does not show how bbd8db54b3c9 depends on the av commits."
     ],
     "confidence": "medium",
     "unknowns": [
      "Google's description of the exact trigger and impact is not in the material. The commit message only says the AppOps op state and audioserver's silenced state could diverge.",
      "An intermediate frameworks/av commit between c962764b8234 and fa795ebf0e65 (adding the reg()/check() lambdas in OpRecordAudioMonitor) is implied by the diff context but not provided.",
      "The material does not show whether the av and PermissionManagerService commits were applied to Android 15, or whether the SystemUI change was applied to Android 14.",
      "The material does not show whether a single-link failure in the chain can leave a started op behind.",
      "Why the bulletin lists '16-next' while the per-major facts mark Android 16 as not listed is not explained."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 11,
      "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-48549": {
     "cve": "CVE-2025-48549",
     "summary": "Four coordinated changes make microphone app-op enforcement and the mic indicator consistent across a full attribution chain: audioserver's OpRecordAudioMonitor now checks and watches every link of the chain (and always OP_RECORD_AUDIO); PermissionManagerService finishes already-started proxy ops when a later link fails and aligns the preflight check with the data-delivery path; and SystemUI only suppresses the mic indicator when all of a UID's recordings are silenced.",
     "component": "platform/frameworks/av (audiopolicy service, AudioRecordClient / OpRecordAudioMonitor); platform/frameworks/base (PermissionManagerService app-op attribution chain handling; SystemUI AppOpsControllerImpl mic indicator)",
     "weakness": "Incomplete authorization check: only the first entry of the recording attribution chain was validated and watched for app-op changes, and indicator suppression used an any-silenced rather than an all-silenced rule",
     "root_cause": "In services/audiopolicy/service/AudioRecordClient.cpp, OpRecordAudioMonitor::onFirstRef registered an AppOps watch only for mAttributionSource.packageName (the top of the chain) and only for mAppOp. OpRecordAudioMonitor::checkOp called mAppOpsManager.checkOp only for mAttributionSource.uid/packageName. Links further down the AttributionSourceState.next chain were neither checked nor watched, and OP_RECORD_AUDIO was not checked when mAppOp was a different op (TODOs in the code noted this). In PermissionManagerService.checkRuntimePermission, if a later link of a chain failed during startDataDelivery, proxy ops already started higher in the chain were left running, which split the op state. In SystemUI AppOpsControllerImpl, isAnyRecordingPausedLocked disabled the mic indicator for a UID as soon as any one of its AudioRecordingConfigurations was silenced, even if another recording for the same UID was not.",
     "fix_analysis": [
      "frameworks/base bbd8db54b3c9, PermissionManagerService.java:checkRuntimePermission: adds a prev pointer. When startDataDelivery is set and performOpTransaction returns an opMode other than MODE_ALLOWED part-way through a chain, it copies prev.asState(), truncates next[0].next to an empty array, and calls finishDataDelivery on that cut chain so the already-started proxy op is finished. prev is set to current at the end of each loop iteration.",
      "frameworks/base 2c18e5f08282, PermissionManagerService.java:performOpTransaction (the !forDataDelivery preflight branch): when skipProxyOperation is true and singleReceiverFromDatasource is false, opMode for the first accessor is set to MODE_ALLOWED instead of calling appOpsManager.unsafeCheckOpRawNoThrow. This matches the data-delivery path, which does not check the first link of a non-trivial chain. Per the commit message, audioserver otherwise fails this preflight because it lacks a valid Uid/PackageState. The check of next still runs.",
      "frameworks/av db3bbd5a03e4, AudioRecordClient.cpp: adds a forward iterator AttrSourceItr over AttributionSourceState.next in the anonymous namespace. OpRecordAudioMonitor::onFirstRef now calls mAppOpsManager.startWatchingMode for every link's packageName (same flags and mOpCallback), for mAppOp and also for AppOpsManager::OP_RECORD_AUDIO when mAppOp differs. OpRecordAudioMonitor::checkOp now computes hasIt with std::all_of over the chain (checkOp(op, x.uid, x.packageName) == MODE_ALLOWED) for mAppOp, and also for OP_RECORD_AUDIO when mAppOp differs.",
      "frameworks/base feedcb492661, SystemUI AppOpsControllerImpl.java: isAnyRecordingPausedLocked is renamed isAllRecordingPausedLocked. It returns true if mMicMuted, false if there are no configs for the uid, and otherwise returns false as soon as one config is not silenced, so it returns true only if all are silenced. Both call sites (AppOpItem creation for an active microphone op, and updateSensorDisabledStatus) now use the new method."
     ],
     "completeness": [
      "The av change covers both the op-state computation (checkOp) and change notifications (watch registration in onFirstRef) across every link in the chain, and always includes OP_RECORD_AUDIO.",
      "The preflight relaxation in performOpTransaction only applies to the !forDataDelivery branch with skipProxyOperation && !singleReceiverFromDatasource. The forDataDelivery and startDataDelivery branches are unchanged.",
      "The finish-on-failure cleanup in checkRuntimePermission only runs when startDataDelivery is true. Other delivery paths are not touched by that commit.",
      "The SystemUI change still relies on AudioRecordingConfiguration listening (the added TODO b/365843152 says this is to be removed), so the indicator logic still depends on audioserver reporting isClientSilenced correctly.",
      "The av commit changes only AudioRecordClient.cpp and has no test changes. The Android 15 SystemUI commit adds AppOpsControllerTest coverage; the Android 14 diff shown does not."
     ],
     "behavior_changes": [
      "audioserver treats a recording as lacking the op if any link of the attribution chain lacks the governing op or OP_RECORD_AUDIO, not just the top link.",
      "audioserver registers more AppOps watch callbacks (one per chain link per op, plus OP_RECORD_AUDIO), so op changes for any link reach RecordAudioOpCallback::opChanged.",
      "Preflight (non-data-delivery) checks of non-trivial chains with skipProxyOperation no longer fail because the first link (e.g. audioserver) lacks the op.",
      "When a later chain link fails during startDataDelivery, previously started proxy ops are finished.",
      "The SystemUI mic indicator is shown for a UID unless all of its known recordings are silenced (or the mic is muted). Previously a single silenced recording hid the indicator."
     ],
     "preconditions": [
      "A recording whose AttributionSourceState has a non-empty next chain (more than one link), so that lower links were previously not checked or watched by OpRecordAudioMonitor.",
      "OpRecordAudioMonitor applies to app-op audio sources: isAppOpSource returns false for FM_TUNER, ECHO_REFERENCE and REMOTE_SUBMIX, and the file comments refer to creation in createIfNeeded.",
      "For the SystemUI path: a UID with multiple AudioRecordingConfigurations where at least one is silenced and another is not.",
      "The exact trigger scenario for the EoP is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: RESTRICT AUTOMERGE cherry-picks in android-security-14.0.0_r22 (av db3bbd5a03e4; base bbd8db54b3c9, 2c18e5f08282, feedcb492661). At r29 the patched AudioRecordClient.cpp and PermissionManagerService code is present. The resolver names feedcb492661 as the fix and reports 6/8 lines of the patched region present.",
      "15": "resolved in android-security-15.0.0_r10 with SystemUI commit 00344da68fce: the same code change as on 14, plus AppOpsControllerTest updates. Android 15 counterparts of the av and PermissionManagerService commits are not shown in the material. At r17, the resolver reports 6/8 lines of the patched region present.",
      "16": "not affected (not listed by the bulletin); at android-security-16.0.0_r8, 8/8 lines of the patched region 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": [
      "Apply the base commits in order: bbd8db54b3c9 (finish all on failure, parent index 297ad73e054b) then 2c18e5f08282 (preflight skip, parent index 693d7b348b55). The 2c18e5f08282 commit message says audioserver implicitly relies on the preflight behavior, so ship it together with the av change.",
      "The av change relies on AttributionSourceState.next and on the existing startWatchingMode-with-flags and doesPackageTargetAtLeastU code in AudioRecordClient.cpp. Trees that differ there need an adapted version."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not describe the concrete trigger scenario or which process the privilege escalation targets.",
      "The bulletin lists Android 13 as affected, but the material has no Android 13 commit or branch status.",
      "The material does not show why the resolver reports only 6/8 lines of the patched region at android-security-14.0.0_r29 and 15.0.0_r17, or which file that region is in.",
      "Whether Android 15 also received the av and PermissionManagerService counterparts is not shown.",
      "The material does not say how the bulletin's '16-next' entry relates to the 16.0.0_r8 state."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48550": {
     "cve": "CVE-2025-48550",
     "summary": "Adds package-name validation to both SlicePermissionManager.PkgUser constructors so that a malformed package string is rejected with IllegalArgumentException instead of being accepted.",
     "component": "platform/frameworks/base, system_server slice permissions (services/core/java/com/android/server/slice/SlicePermissionManager.java)",
     "weakness": "Missing input validation of a package name",
     "root_cause": "SlicePermissionManager.PkgUser stored whatever package string it was given, both in PkgUser(String pkg, int userId) and in PkgUser(String pkgUserStr), which parses a string and wraps parse failures in IllegalArgumentException. Neither constructor checked that mPkg is a well-formed package name or filename. The commit title links this to granting slice permissions. The bulletin classifies the result as a High severity DoS. The material shows neither the exact failure mode nor how mPkg is used downstream.",
     "fix_analysis": [
      "SlicePermissionManager.java:PkgUser.enforceValidPackage (new): calls FrameworkParsingPackageUtils.validateName(mPkg, false /* requireSeparator */, true /* requireFilename */) and throws IllegalArgumentException with the returned error string when it is non-empty.",
      "SlicePermissionManager.java:PkgUser(String pkg, int userId): calls enforceValidPackage() after assigning mPkg and mUserId.",
      "SlicePermissionManager.java:PkgUser(String pkgUserStr): calls enforceValidPackage() after the existing parse/try-catch.",
      "New imports: android.content.pm.parsing.FrameworkParsingPackageUtils and android.text.TextUtils."
     ],
     "completeness": [
      "Both PkgUser constructors are instrumented, so every PkgUser instance is validated at construction. The material shows no other way to set mPkg.",
      "Validation is syntactic only (validateName with requireSeparator=false, requireFilename=true). It does not check that the package exists, is installed for the user, or belongs to the caller. Any identity or ownership check would have to come from the callers, which are not shown.",
      "The string-parsing constructor now also throws for invalid entries. The diff does not show how its callers handle the new IllegalArgumentException.",
      "Nothing changes in the userId handling or in the rest of SlicePermissionManager."
     ],
     "behavior_changes": [
      "PkgUser construction now throws IllegalArgumentException for package names that fail FrameworkParsingPackageUtils.validateName.",
      "PkgUser(String pkgUserStr) now throws IllegalArgumentException when the parsed package component is malformed, where before it accepted it."
     ],
     "preconditions": [
      "A caller reaches a PkgUser construction in SlicePermissionManager with a package string it controls. Per the commit title, this is the slice permission grant path. Per index search, TvSettings SliceManagerCompat.grantPermissionFromUser calls SliceManager.grantPermissionFromUser.",
      "The material does not show which binder entry points in SliceManagerService construct PkgUser, or what permission they require."
     ],
     "per_major": {
      "14": "same fix: commit 54158e2925dc in android-security-14.0.0_r22; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 354820f6ec38 in android-security-15.0.0_r10, same blob hashes (343d2e353abb..27578a01959f) as on 14; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit bae8ab23b03d in android-security-16.0.0_r1, identical hunk content at shifted line offsets (424/434 instead of 410/420); 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 in one file. It depends on FrameworkParsingPackageUtils.validateName(String, boolean, boolean), which the 14, 15 and 16 diffs import and call.",
      "14 and 15 share identical pre/post blob hashes; on 16 the hunk content is the same and only the line offsets differ."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact DoS failure mode (which operation failed on the invalid name) is not shown.",
      "Which SliceManagerService entry points construct PkgUser, and what permissions their callers need, is not in the material.",
      "How callers of the PkgUser constructors (grant paths and persisted-state loading) handle the newly thrown IllegalArgumentException.",
      "Where the strings passed to PkgUser(String pkgUserStr) come from, and their format.",
      "Contents of the SliceManagerTest changes referenced in the commit message.",
      "Android 13 is listed as affected, but the material contains no Android 13 fix 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."
     }
    },
    "CVE-2025-48551": {
     "cve": "CVE-2025-48551",
     "summary": "The Sharesheet \"edit image\" action in IntentResolver's ChooserActivity is now launched as the user that launched the sharesheet instead of always as the personal profile user.",
     "component": "platform/packages/modules/IntentResolver, Sharesheet (ChooserActivity / ChooserActionFactory edit-image action)",
     "weakness": "Activity launched under the wrong user handle (cross-user/profile launch of shared content): the code hardcoded the personal profile instead of the launching user",
     "root_cause": "In ChooserActivity.createChooserActionFactory(), the anonymous ChooserActionFactory.ActionActivityStarter used by the image-editor action called safelyStartActivityAsUser(targetInfo, getPersonalProfileUserHandle()), and the shared-element variant did the same. The interface methods were named safelyStartActivityAsPersonalProfileUser / ...WithSharedElementTransition, and the call site in ChooserActionFactory carries the comment \"Action bar is user-independent; always start as primary.\" As a result, when the sharesheet ran in a non-personal user or profile, the edit target was started in the personal profile rather than in the user that launched the sharesheet. The bulletin rates this as an information disclosure (ID) issue.",
     "fix_analysis": [
      "ChooserActivity.java:createChooserActionFactory(): the ActionActivityStarter implementation now calls safelyStartActivityAsUser(targetInfo, UserHandle.of(UserHandle.myUserId())) in both the plain and the shared-element-transition variants. The editor target is therefore started as the user the ChooserActivity itself runs as, not as getPersonalProfileUserHandle().",
      "ChooserActionFactory.java:ActionActivityStarter: the interface methods are renamed from safelyStartActivityAsPersonalProfileUser / safelyStartActivityAsPersonalProfileUserWithSharedElementTransition to safelyStartActivityAsLaunchingUser / safelyStartActivityAsLaunchingUserWithSharedElementTransition. The default implementation of the shared-element method delegates to the renamed method.",
      "ChooserActionFactory.java (edit action lambda, around the firstImageView check): the two call sites are updated to the renamed methods. There is no logic change there.",
      "Per the commit message, this change covers the case where the use_preferred_image_editor flag is off or does not exist. The flag-on case was fixed separately in ag/34038177, which is not included in this material."
     ],
     "completeness": [
      "Covers both launch paths of the edit action (with and without the shared element transition) in ChooserActivity's ActionActivityStarter.",
      "Only the ActionActivityStarter used for the image-editor action is changed. The commit explicitly addresses the flag-off / flag-absent path. The separate flag-on fix (ag/34038177) is not shown here, so on branches where use_preferred_image_editor exists, that other change is also required.",
      "The stale comment \"Action bar is user-independent; always start as primary.\" remains in ChooserActionFactory, and the Log.d string in the Android 15/16 versions still says safelyStartActivityAsPersonalProfileUser. Both are cosmetic only.",
      "Any other ActionActivityStarter implementations (e.g. in tests) must be updated for the rename. None are shown in the material."
     ],
     "behavior_changes": [
      "The image editor is now launched in the user/profile that launched the sharesheet (UserHandle.myUserId() on 14, mUserInteractor.getLaunchedAs() on 15/16) instead of in the personal profile.",
      "Public interface method names in ChooserActionFactory.ActionActivityStarter changed (a source-incompatible rename within the module)."
     ],
     "preconditions": [
      "The Sharesheet (ChooserActivity) is shown for image content such that the edit action (editSharingTarget) is offered.",
      "ChooserActivity is running as a user/profile different from the personal profile. The behavior differs only when getPersonalProfileUserHandle() differs from the launching user.",
      "The user selects the edit action."
     ],
     "per_major": {
      "14": "same fix (75f41ba2e89d in android-security-14.0.0_r22): uses UserHandle.of(UserHandle.myUserId()) in place of getPersonalProfileUserHandle(). The fixed code is visible at android-security-14.0.0_r29.",
      "15": "adapted fix (13c30b464d04 in android-security-15.0.0_r10): same rename. The launch user changes from mProfiles.getPersonalHandle() to mUserInteractor.getLaunchedAs(). The surrounding edit-lambda condition (requireFullVisibility/isFullyVisible) was already there before the fix.",
      "16": "adapted fix (9810de0fcb5a in android-security-16.0.0_r1): same approach as 15, using mUserInteractor.getLaunchedAs() in place of mProfiles.getPersonalHandle().",
      "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": [
      "Android 14 has no mUserInteractor, so the fix uses UserHandle.of(UserHandle.myUserId()). A backport to any 14-based fork should follow the 14 variant, and any other implementation of ActionActivityStarter (including tests) must adopt the renamed methods.",
      "If the target branch has the use_preferred_image_editor flag, the companion change ag/34038177 (flag-on path) is also needed. It is not included in this material."
     ],
     "confidence": "high",
     "unknowns": [
      "The content of the companion fix for the use_preferred_image_editor flag-on path (ag/34038177), and whether that flag exists on Android 14.",
      "Exactly what information is disclosed and the concrete trigger scenario. The bulletin only gives type ID and severity High.",
      "Whether the edit intent carries URI permission grant flags, and where the editor component is sourced from. Neither is shown in the material.",
      "Whether the 2 lines of the patched region missing from later tags reflect refactoring or a partial state. The r29 context shows the fixed launch calls present."
     ],
     "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-48552": {
     "cve": "CVE-2025-48552",
     "summary": "Wraps the three Settings.Global proxy writes in DevicePolicyManagerService.saveGlobalProxyLocked in a try/catch, so an exception from SettingsProvider while applying a device admin's global proxy no longer aborts the caller (for example the removal of an ActiveAdmin).",
     "component": "platform/frameworks/base, DevicePolicyManagerService (services/devicepolicy), global HTTP proxy handling",
     "weakness": "Unhandled exception in a policy re-apply/reset path (improper handling of exceptional conditions) that aborts a security-relevant operation",
     "root_cause": "saveGlobalProxyLocked(String proxySpec, String exclusionList) parses an admin's global proxy spec and checks ProxyInfo.isValid(). It then calls mInjector.settingsGlobalPutString/PutInt for GLOBAL_HTTP_PROXY_HOST, GLOBAL_HTTP_PROXY_PORT and GLOBAL_HTTP_PROXY_EXCLUSION_LIST with no exception handling. If SettingsProvider threw while storing one of these values, the exception propagated out of saveGlobalProxyLocked into its caller. That caller iterates admins, applies the proxy of the first admin with specifiesGlobalProxy set, or otherwise resets the proxy to null. Per the commit message, this broke callers performing security-sensitive operations such as removing an ActiveAdmin.",
     "fix_analysis": [
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:saveGlobalProxyLocked: the three mInjector.settingsGlobalPutString/settingsGlobalPutInt calls (host, port, exclusion list) are moved into a try block. Any Exception is caught, logged with Slogf.w(LOG_TAG, \"Fail to save proxy\", e) and otherwise ignored, so control returns normally to the caller.",
      "The same change also rewrites the Settings.Global.* constant references to Global.* in the 14 and 15 branches. This is cosmetic: the 16 branch already used Global.*.",
      "The ProxyInfo.isValid() early return before the writes was already there and is not changed. The fix only adds resilience against failures of the settings writes themselves."
     ],
     "completeness": [
      "Covers only the three Settings.Global writes inside saveGlobalProxyLocked. The parsing of proxySpec (split on ':', Integer.parseInt with NumberFormatException swallowed) and the ProxyInfo.isValid() check were already present and are not changed.",
      "The catch is catch (Exception), so any exception from the writes is swallowed, not a specific exception type.",
      "If the first or second put succeeds and a later one throws, the global proxy settings are left partially updated. The fix does not roll back or clear them.",
      "Other uses of mInjector.settingsGlobalPut* elsewhere in DevicePolicyManagerService are not touched by this commit. The material does not show whether similar failures there would matter."
     ],
     "behavior_changes": [
      "A failure to persist the global proxy settings is now logged at warning level (\"Fail to save proxy\") and swallowed instead of propagating out of saveGlobalProxyLocked.",
      "Operations that re-apply or reset the global proxy (per the commit message, such as admin removal) now complete even when SettingsProvider rejects the stored proxy values.",
      "The proxy settings may be left partially written when a later put fails after an earlier one succeeded."
     ],
     "preconditions": [
      "A device admin (ActiveAdmin) with specifiesGlobalProxy set and a globalProxySpec/globalProxyExclusionList that passes ProxyInfo.isValid() but causes SettingsProvider to throw on settingsGlobalPutString/PutInt. The exact triggering values are not shown in the material.",
      "A code path that reaches the caller that iterates admins and calls saveGlobalProxyLocked. Per the commit message, this includes removing an ActiveAdmin."
     ],
     "per_major": {
      "14": "same fix: commit 58e0b9a15338 in android-security-14.0.0_r22 (cherry-pick of 2ea32931ef9a). The code context at android-security-14.0.0_r29 shows the try/catch present in saveGlobalProxyLocked (lines 8499-8507), even though the line-fingerprint resolver reports 1/4 lines.",
      "15": "same fix: commit bb6d2f17243d in android-security-15.0.0_r10, an identical diff (including the Settings.Global -> Global rename) at a different line offset.",
      "16": "same fix: commit d5fee58e64ae in android-security-16.0.0_r1. This branch already used Global.*, so only the try/catch is added. 4/4 patched lines are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The resolver reports 1/4 patched lines at android-security-17.0.0_r1. The 17 source is not shown."
     },
     "backport_notes": [
      "Single self-contained hunk in saveGlobalProxyLocked: a try/catch around the three mInjector.settingsGlobalPut* calls. The Settings.Global -> Global qualifier change in the diff is cosmetic, so the target tree's existing qualifier can be kept."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which exception SettingsProvider throws, and for which proxy host/port/exclusion-list values. The material only says invalid proxy settings cause errors (b/365975561).",
      "The name of the caller of saveGlobalProxyLocked and the full set of security-sensitive paths that reach it, beyond the commit message's example of removing an ActiveAdmin.",
      "Why the per-major resolver reports 1/4 patched lines for 14 and 15 while the 14 code context shows the full fix.",
      "Whether the Android 17 tree contains the try/catch (the source is not 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."
     }
    },
    "CVE-2025-48553": {
     "cve": "CVE-2025-48553",
     "summary": "DevicePolicyManagerService.handlePackagesChanged now re-parses the device admin metadata of an active admin's receiver (via a new DeviceAdminInfo) after a package change and removes the admin if the package, receiver or metadata is missing or malformed; DeviceAdminInfo's metadata parser additionally converts OutOfMemoryError into XmlPullParserException so that failure is catchable.",
     "component": "platform/frameworks/base, DevicePolicyManagerService / DeviceAdminInfo (device policy)",
     "weakness": "Insufficient validation of a retained privileged registration after package update (stale active-admin state); plus an uncaught OutOfMemoryError in metadata parsing",
     "root_cause": "In handlePackagesChanged, when an active admin's package changed, DPMS only verified via mIPackageManager.getPackageInfo and getReceiverInfo (without GET_META_DATA) that the package and the receiver component still existed. It never re-read or re-parsed the receiver's device admin metadata, so an ActiveAdmin stayed registered with its previously stored DeviceAdminInfo even if the updated package's receiver no longer carried well-formed device admin metadata. Additionally, DeviceAdminInfo(Context, ActivityInfo) only caught NameNotFoundException around the XML parse; an OutOfMemoryError during parsing propagated as an Error rather than a parse failure.",
     "fix_analysis": [
      "core/java/android/app/admin/DeviceAdminInfo.java: constructor DeviceAdminInfo(Context, ActivityInfo) adds `catch (OutOfMemoryError e)` that rethrows as XmlPullParserException(\"Out of memory when parsing\", null, e). This turns an OOM during metadata parsing into a checked parse exception that callers catching Exception can handle.",
      "services/devicepolicy/.../DevicePolicyManagerService.java: new @GuardedBy(getLockObject()) helper isActiveAdminPackageValid(ActiveAdmin admin). It (1) returns false if mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) is null, (2) fetches the receiver with getReceiverInfo(component, GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle) and returns false if null, (3) constructs `new DeviceAdminInfo(mContext, ai)` and returns false if any Exception is thrown (logged as 'contains malformed metadata'). The userHandle comes from admin.getUserHandle().getIdentifier().",
      "DevicePolicyManagerService.handlePackagesChanged: replaces the inline existence check with `if (!isActiveAdminPackageValid(aa))`; on failure it removes the admin from policy.mAdminList and policy.mAdminMap, calls pushActiveAdminPackagesLocked and pushMeteredDisabledPackages, and sets removedAdmin/removedAdminPackage as before. The log message is changed to 'not found or invalid'.",
      "The two file changes are coupled: isActiveAdminPackageValid catches Exception only, so without the DeviceAdminInfo change an OutOfMemoryError in parsing would still escape the check."
     ],
     "completeness": [
      "Covers the package-change path only: validation is performed in handlePackagesChanged for admins whose package matches (or all admins when packageName is null).",
      "The newly parsed DeviceAdminInfo is discarded; the stored aa.info is not refreshed in this path, so if the metadata parses but differs (e.g. different uses-policies), the admin keeps its previously stored info as far as this code shows.",
      "Only Exception is caught around the DeviceAdminInfo constructor; Errors other than the now-converted OutOfMemoryError are not handled.",
      "Other places that construct DeviceAdminInfo or load admins (e.g. at boot/settings load) are not shown in the material; whether they already re-validate metadata is unknown."
     ],
     "behavior_changes": [
      "Active admins whose updated package has a receiver but missing/malformed device admin metadata are now removed on package change (previously retained).",
      "getReceiverInfo in this path now requests GET_META_DATA.",
      "DeviceAdminInfo(Context, ActivityInfo) now throws XmlPullParserException instead of propagating OutOfMemoryError during metadata parsing; this affects every caller of that constructor, not only DPMS.",
      "New error log lines: '<pkg> no longer installed', '<pkg> no longer has the receiver', '<pkg> contains malformed metadata', and 'Admin package %s not found or invalid for user %d, removing active admin'."
     ],
     "preconditions": [
      "A device admin (ActiveAdmin in DevicePolicyData.mAdminList) is active for the user.",
      "A package change for that admin's package (or a change that triggers handlePackagesChanged with packageName == null) occurs, i.e. the admin package is updated or otherwise changed while still installed and still declaring the receiver component.",
      "Pre-fix, the updated package's receiver metadata could be absent or malformed without the admin being removed."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 0f9ca51bfd42 in android-security-14.0.0_r22; both files changed; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: 660c7075dc00 in android-security-15.0.0_r10; same diff content at different line offsets; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: c0978bc04a25 in android-security-16.0.0_r1; same except the removed code already used unqualified MATCH_DIRECT_BOOT_* constants; 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": [
      "Backport both files together: the `catch (Exception e)` in isActiveAdminPackageValid relies on DeviceAdminInfo converting OutOfMemoryError to XmlPullParserException. The new method uses unqualified GET_META_DATA, MATCH_DIRECT_BOOT_AWARE and MATCH_DIRECT_BOOT_UNAWARE; the diff adds no imports, so confirm those static imports exist in the target DevicePolicyManagerService (Android 14's removed code used the PackageManager.-qualified form, so check the import block)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact privilege gained (what the retained admin could do with stale DeviceAdminInfo after a malformed update) is not shown; the bulletin only rates it High EoP.",
      "Whether other admin-loading paths (boot-time settings load, setActiveAdmin) already validate metadata is not shown in the material.",
      "The commit lists two bugs (384514657, 414603411); which one maps to this CVE is not shown.",
      "The visibility of the DeviceAdminInfo(Context, ActivityInfo) constructor and its full set of callers are not shown."
     ],
     "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-48554": {
     "cve": "CVE-2025-48554",
     "summary": "DevicePolicyManagerService now re-parses an active admin's device-admin metadata whenever its package changes and drops the admin if the metadata is malformed, and DeviceAdminInfo converts an OutOfMemoryError raised while parsing that metadata into an XmlPullParserException so callers can handle it.",
     "component": "platform/frameworks/base, DevicePolicyManagerService / DeviceAdminInfo (device admin metadata handling)",
     "weakness": "Missing validation of updated package metadata plus unhandled OutOfMemoryError during XML metadata parsing (DoS per bulletin)",
     "root_cause": "In DevicePolicyManagerService.handlePackagesChanged, when an active admin's package changed, the service only used mIPackageManager.getPackageInfo and getReceiverInfo to check that the package and the receiver component still existed. It never re-validated the admin's device-admin meta-data, so an updated DPC APK could remain an active admin while carrying malformed metadata. Separately, the metadata parsing block of the DeviceAdminInfo(Context, ActivityInfo) constructor caught only NameNotFoundException. An OutOfMemoryError thrown while parsing the meta-data XML therefore propagated as an Error instead of an XmlPullParserException.",
     "fix_analysis": [
      "core/java/android/app/admin/DeviceAdminInfo.java: DeviceAdminInfo(Context, ActivityInfo) adds 'catch (OutOfMemoryError e)' next to the existing NameNotFoundException catch and rethrows it as XmlPullParserException(\"Out of memory when parsing\", null, e). Callers that catch Exception or XmlPullParserException can now handle the failure.",
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java: adds a new helper, isActiveAdminPackageValid(ActiveAdmin admin), annotated @GuardedBy(getLockObject()). It (1) returns false if mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) is null, (2) fetches the receiver with GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE (GET_META_DATA is new compared with the old inline check) and returns false if the result is null, (3) constructs new DeviceAdminInfo(mContext, ai) inside try/catch(Exception) and, if parsing throws, logs 'contains malformed metadata' and returns false. userHandle is taken from admin.getUserHandle().getIdentifier().",
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java: handlePackagesChanged replaces the inline getPackageInfo/getReceiverInfo null checks with '!isActiveAdminPackageValid(aa)'. On failure the existing removal path runs unchanged: remove the admin from policy.mAdminList and policy.mAdminMap, then call pushActiveAdminPackagesLocked and pushMeteredDisabledPackages. The log message now reads 'not found or invalid' and passes format arguments to Slogf.e directly instead of using String.format.",
      "The two parts depend on each other. isActiveAdminPackageValid catches Exception, which does not catch OutOfMemoryError. The DeviceAdminInfo change is what makes an OOM during parsing surface as a catchable XmlPullParserException."
     ],
     "completeness": [
      "Covers the package-change path (handlePackagesChanged) for every active admin in policy.mAdminList of the affected user, both for a specific packageName and for the packageName == null (check all) case.",
      "The OutOfMemoryError conversion is limited to the metadata parsing try block of DeviceAdminInfo(Context, ActivityInfo). It does not cover other Error subclasses, and the catch (Exception e) in isActiveAdminPackageValid would not catch any Error that is not converted there.",
      "This commit does not change other places that construct DeviceAdminInfo. The material does not show whether those places already tolerate XmlPullParserException.",
      "Validation is triggered only through handlePackagesChanged. Admins are not re-validated by this change outside that path."
     ],
     "behavior_changes": [
      "An active admin whose updated package has device-admin meta-data that fails DeviceAdminInfo parsing is now removed as an active admin. The side effects are the same as for an uninstalled admin: it is removed from mAdminList and mAdminMap, and the active admin packages and metered-disabled packages are pushed.",
      "getReceiverInfo in this path now requests GET_META_DATA.",
      "DeviceAdminInfo(Context, ActivityInfo) now throws XmlPullParserException, with the OutOfMemoryError as its cause, instead of letting OutOfMemoryError propagate from the metadata parsing block.",
      "New error log lines: '<pkg> no longer installed', '<pkg> no longer has the receiver', '<pkg> contains malformed metadata', and the reworded 'Admin package %s not found or invalid for user %d, removing active admin'."
     ],
     "preconditions": [
      "A package must be registered as an active admin, meaning it is present in DevicePolicyData.mAdminList for the user.",
      "A package change for that user must reach handlePackagesChanged, either for the admin's package name or as a check-all pass with packageName == null.",
      "The admin package's device-admin meta-data must make DeviceAdminInfo parsing fail, including by running out of memory while parsing. The commit title points to DPC package updates as the context."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 0f9ca51bfd42 in android-security-14.0.0_r22 (cherry-picked, Change-Id I1743c111f8e8b5f4c1f878a61b88b8f1ed6b86a1); 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 660c7075dc00 in android-security-15.0.0_r10, same content at different line offsets; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c0978bc04a25 in android-security-16.0.0_r1; the only difference is that the pre-fix inline check already used bare MATCH_DIRECT_BOOT_* names; 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": [
      "Apply both hunks together. The DevicePolicyManagerService helper catches Exception only, so without the DeviceAdminInfo change that converts OutOfMemoryError to XmlPullParserException, an OOM during parsing is still uncaught.",
      "The helper uses bare GET_META_DATA / MATCH_DIRECT_BOOT_AWARE / MATCH_DIRECT_BOOT_UNAWARE names. On a branch whose source file lacks the matching static imports, add them or qualify the names (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Where the malformed or oversized metadata was parsed and caused the DoS before the fix, which process was affected, and whether the effect persisted across reboots.",
      "Whether other DeviceAdminInfo construction sites in DevicePolicyManagerService already handle XmlPullParserException; the material does not show them.",
      "The specific nature of the malformed metadata that triggered the OutOfMemoryError (the commit says 'Test: manual' and includes no test case)."
     ],
     "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-48558": {
     "cve": "CVE-2025-48558",
     "summary": "BatteryService now resolves the ACTION_REQUEST_SHUTDOWN intent it fires on low battery or over-temperature to an explicit component whose owning uid holds android.permission.SHUTDOWN. Before, it launched the implicit intent from the system process, so any activity with a matching intent-filter could resolve.",
     "component": "platform/frameworks/base, services/core BatteryService (system_server)",
     "weakness": "Implicit intent sent from a privileged (system) context without verifying the resolved target (missing permission check on the intent receiver; CWE-927-style intent exposure)",
     "root_cause": "In services/core/java/com/android/server/BatteryService.java, shutdownIfNoPowerLocked() and shutdownIfOverTempLocked() each posted a Runnable. That Runnable built an implicit Intent(Intent.ACTION_REQUEST_SHUTDOWN) with EXTRA_KEY_CONFIRM=false, EXTRA_REASON (SHUTDOWN_LOW_BATTERY or SHUTDOWN_BATTERY_THERMAL_STATE) and FLAG_ACTIVITY_NEW_TASK, then called mContext.startActivityAsUser(intent, UserHandle.CURRENT) with no component set. Because the intent was implicit and started by system_server, any activity declaring an intent-filter for ACTION_REQUEST_SHUTDOWN could be resolved and launched, with no check that the target held android.permission.SHUTDOWN.",
     "fix_analysis": [
      "BatteryService.java:shutdownIfNoPowerLocked() and shutdownIfOverTempLocked(): the Intent is now built inline, with the same action, extras and flags as before. The posted Runnable is replaced by a lambda that calls the new private method startShutdownActivity(intent). The isSystemReady() check moves into that method.",
      "BatteryService.java:startShutdownActivity(Intent): returns early if !mActivityManagerInternal.isSystemReady(). It gets PackageManagerInternal via LocalServices.getService and returns early (no activity started) if it is null.",
      "startShutdownActivity: calls pmi.queryIntentActivities(intent, null, 0, Process.myUid(), mActivityManagerInternal.getCurrentUserId()) and walks the ResolveInfo list. Entries with null activityInfo or applicationInfo are skipped. Each remaining entry must pass mContext.checkPermission(android.Manifest.permission.SHUTDOWN, 0, ri.activityInfo.applicationInfo.uid) == PERMISSION_GRANTED; otherwise a Slog.w naming the component is logged and the entry is skipped.",
      "The first ResolveInfo whose uid holds SHUTDOWN is pinned with intent.setComponent(ri.activityInfo.getComponentName()) and the loop breaks. The intent is then started with mContext.startActivityAsUser(intent, UserHandle.CURRENT) as before, now as an explicit intent."
     ],
     "completeness": [
      "Both BatteryService trigger paths (low battery via shouldShutdownLocked(), over-temperature via mShutdownBatteryTemperature) go through the single new startShutdownActivity() helper, so the permission check applies to both.",
      "Fallback gap visible in the code: if resolveInfos is null, or no resolved activity's uid holds SHUTDOWN, intent.setComponent() is never called. The method still falls through to mContext.startActivityAsUser(intent, UserHandle.CURRENT) with the implicit intent. The fix only guarantees an explicit, permission-checked target when at least one SHUTDOWN-holding handler resolves for the current user.",
      "The permission check uses the owning uid of the resolved activity (pid argument 0). It does not check any other property of the component.",
      "The query uses flags 0 and the current user id from ActivityManagerInternal. The later start uses UserHandle.CURRENT.",
      "If PackageManagerInternal is unavailable, the shutdown activity is not started at all (silent early return). This no-op path did not exist before."
     ],
     "behavior_changes": [
      "The shutdown request intent now has an explicit component (the first resolved handler whose uid holds SHUTDOWN) when such a handler exists.",
      "New Slog.w log line 'Shutdown activity <component> does not have permission android.permission.SHUTDOWN' for each skipped handler.",
      "If LocalServices has no PackageManagerInternal, the activity launch is skipped entirely.",
      "The Intent object is now created on the calling thread (inside the *Locked methods) rather than inside the posted Runnable. The isSystemReady() check still runs at execution time on mHandler."
     ],
     "preconditions": [
      "BatteryService must decide to shut down: either shouldShutdownLocked() is true (critically low battery, not powered) or mHealthInfo.batteryTemperatureTenthsCelsius > mShutdownBatteryTemperature. The commit's test line uses 'adb shell dumpsys battery set temp 1001' to force the latter.",
      "mActivityManagerInternal.isSystemReady() must be true when the posted work runs.",
      "For the pre-fix behavior to reach a non-system app, an activity in the current user must declare an intent-filter for Intent.ACTION_REQUEST_SHUTDOWN and be resolvable for the implicit intent."
     ],
     "per_major": {
      "14": "same fix: frameworks/base e22e656b5e88 in android-security-14.0.0_r22 (cherry-pick of the internal change, Change-Id I6c00b47d424d81712bd9634c31de4b7d2e9cbe31). Patched region fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "same fix: frameworks/base 0d30c78c8953 in android-security-15.0.0_r10. Hunk content is identical; only the surrounding import lines differ (CopyOnWriteArraySet). 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: frameworks/base b1c60eeadb2c in android-security-16.0.0_r1. Hunk content is identical; the import context differs (Looper, Objects, CopyOnWriteArraySet). 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-file change with no dependency on other commits. It uses PackageManagerInternal.queryIntentActivities, ActivityManagerInternal.getCurrentUserId() and LocalServices, which are internal system_server APIs, so a tree where those signatures differ needs the calls adapted (general platform knowledge). Android 13 is listed as affected, but the material includes no Android 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the Android 13 branch received this change (the bulletin lists 13 as affected; the material has no 13 commit).",
      "Whether any other system component sends ACTION_REQUEST_SHUTDOWN as an implicit intent; this material only covers BatteryService.",
      "The concrete consequence of system_server launching a non-system activity in this flow (the bulletin only states EoP, High)."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48559": {
     "cve": "CVE-2025-48559",
     "summary": "AppOpsService.verifyAndGetBypass no longer always accepts attribution tags for the root UID: a tag is now valid only when the proxy package is null or a system package. finishProxyOperation now passes the proxy identity into verification, so unverified tags from non-system proxies are not treated as valid on the finish path.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop/AppOpsService.java)",
     "weakness": "Missing validation / unbounded resource consumption: the root UID bypassed attribution tag verification, reachable through proxy app ops (DoS per bulletin)",
     "root_cause": "In AppOpsService.verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, suppressErrorLogs), the uid == Process.ROOT_UID branch returned PackageVerificationResult(null, isAttributionTagValid = true) whatever the proxy was. The special proxied apps branch (RestrictionBypass.UNRESTRICTED) already limited the tag bypass to proxies that are null or system packages; root had no such limit. The commit message says a malicious proxy app could exploit this, and the new code comment describes the risk as abusive apps clogging the appops system with unlimited attribution tags via proxy calls. A second defect: the proxy finish path called finishOperationUnchecked with only the proxied uid/package, and that function called the 3-argument verifyAndGetBypass without proxy information. As a result, for the special proxied apps the attribution tag was reported valid even when the proxy was a non-system app.",
     "fix_analysis": [
      "AppOpsService.java:verifyAndGetBypass (6-arg overload): the Process.ROOT_UID branch now returns isAttributionTagValid = isPackageNullOrSystem(proxyPackageName, proxyUid) instead of a constant true. The bypass (null) is unchanged, so root calls without a proxy (proxyPackageName null) behave as before.",
      "AppOpsService.java:isPackageNullOrSystem (new helper): returns true if packageName is null, true if the appId is in (0, Process.FIRST_APPLICATION_UID), otherwise mPackageManagerInternal.isSystemPackage(packageName).",
      "AppOpsService.java:verifyAndGetBypass (special proxied apps branch, RestrictionBypass.UNRESTRICTED): the inline proxyIsSystemAppOrNull computation is replaced by the same isPackageNullOrSystem helper, so both branches apply one rule.",
      "AppOpsService.java:finishOperationUnchecked: a new overload takes (clientId, code, proxyUid, proxyPackageName, proxiedUid, proxiedPackageName, attributionTag) and calls verifyAndGetBypass(proxiedUid, proxiedPackageName, attributionTag, proxyUid, proxyPackageName). The old 5-arg overload delegates to it with proxyUid -1 and proxyPackageName null. Lookups, logging and the isolated-process check now use proxiedUid/proxiedPackageName.",
      "AppOpsService.java:finishProxyOperation: now calls the new overload with proxyUid and resolvedProxyPackageName as well as the proxied identity, so the finish path checks the tag with proxy-aware logic."
     ],
     "completeness": [
      "Covers the ROOT_UID branch of verifyAndGetBypass for any caller that supplies proxy information, and makes finishProxyOperation supply it. The special-proxied-apps branch now uses the same helper.",
      "Non-proxy paths that call the 3-arg verifyAndGetBypass (e.g. setMode, shown in context) and the 5-arg finishOperationUnchecked pass no proxy, so root and the special UIDs still get isAttributionTagValid = true there. The fix does not restrict these paths.",
      "The helper trusts any proxy appId between 1 and FIRST_APPLICATION_UID-1 without asking the package manager. Proxies with appId 0, a negative appId, or appId >= FIRST_APPLICATION_UID are checked via isSystemPackage. Whether isSystemPackage covers all intended cases (e.g. updated system apps) is not shown in the material.",
      "noteProxyOperation and startProxyOperation are not modified by this commit. The material does not show their code, so whether they already passed the proxy identity to verifyAndGetBypass is not shown.",
      "The SDK sandbox branch and the regular app branch of verifyAndGetBypass are unchanged."
     ],
     "behavior_changes": [
      "Attribution tags supplied for proxied uid 0 (root) through a non-system, non-null proxy package are now treated as invalid and set to null on paths that pass the proxy identity.",
      "finishProxyOperation now checks attribution tag validity with the proxy identity, so for root and the special proxied apps the tag may be set to null on finish where it was previously kept. Error logs on that path now report the proxied uid/package explicitly.",
      "In the special proxied apps branch, a proxy with appId 0 and a non-null package name is now checked via isSystemPackage. Before, it was trusted without a check.",
      "No new exception or return code. Failures still result in the tag being set to null or in the existing Slog.e paths."
     ],
     "preconditions": [
      "A caller able to invoke the proxy app-ops entry points (finishProxyOperation and, per the commit message, other proxy paths) that names a proxied uid of Process.ROOT_UID (or one of the special proxied UIDs, for the finish-path bug) while the proxy package is a non-system app.",
      "mPackageManagerInternal.isSystemPackage must return false for the proxy package for the new restriction to apply.",
      "The exact binder-level access requirements for the proxy op entry points are not shown in the material."
     ],
     "per_major": {
      "14": "Same fix: RESTRICT AUTOMERGE cherry-pick 1c6779245598 in android-security-14.0.0_r22 (frameworks/base). Uses the 5-arg finishOperationUnchecked without virtualDeviceId and inline isolated-process logging.",
      "15": "Adapted fix: 7b88db4928f3 in android-security-15.0.0_r10. Same logic, plus the extra virtualDeviceId parameter on finishOperationUnchecked, the logVerifyAndGetBypassFailure helper, and the op.mDeviceAttributedOps/getPersistentId lookup.",
      "16": "Adapted fix: f745b6763bff in android-security-16.0.0_r1. Same as 15, except the attributed op lookup uses getPersistentDeviceIdForOp(virtualDeviceId, code).",
      "17": "Not affected per the bulletin (Android 17 is not listed). At android-security-17.0.0_r1, 5/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit per branch. When porting, match the local finishOperationUnchecked signature (14 has no virtualDeviceId; 15/16 do) and the local attributed-op lookup (op.mAttributions on 14, op.mDeviceAttributedOps on 15/16). The verifyAndGetBypass and isPackageNullOrSystem hunks are identical across 14/15/16.",
      "Check that the 6-arg verifyAndGetBypass (with proxyUid/proxyPackageName/suppressErrorLogs) exists in the target. The fix relies on it and on the existing special-proxied-apps branch it refactors."
     ],
     "confidence": "high",
     "unknowns": [
      "The code of noteProxyOperation/startProxyOperation and of the 3-arg verifyAndGetBypass wrapper is not in the material, so how the proxy identity flows on those paths is not shown.",
      "Which binder permission gates the proxy op entry points, and so the exact caller needed to reach the ROOT_UID branch with a non-system proxy.",
      "The concrete memory or behavior impact checked by AppOpsMemoryUsageTest is not shown.",
      "Why the patched region shows only partial line matches (4/8 or 5/8) at the latest security tags."
     ],
     "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-48562": {
     "cve": "CVE-2025-48562",
     "summary": "PrintSpooler's save-to-PDF path now opens the destination content URI in write-truncate mode (\"wt\") instead of the default mode, so that overwriting an existing file gets a truncated output stream.",
     "component": "platform/frameworks/base, packages/PrintSpooler (RemotePrintDocument, PDF save/write-out)",
     "weakness": "Missing truncation when overwriting an output file (information disclosure per bulletin type ID)",
     "root_cause": "RemotePrintDocument.writeContent(ContentResolver, Uri) copied the rendered document from the spooler's file into the user-selected destination using contentResolver.openOutputStream(uri), the one-argument form, which does not explicitly request truncation. The commit message says the fix requests a truncated output stream in case the user selects to overwrite an existing file. The inferred consequence is that content of the previous file could remain past the end of the newly written PDF; the material does not show this directly.",
     "fix_analysis": [
      "packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java:writeContent: contentResolver.openOutputStream(uri) is changed to contentResolver.openOutputStream(uri, \"wt\"), requesting a truncating write stream before the 8192-byte copy loop writes the document bytes.",
      "No other logic changes: the acquireFile/FileInputStream source, the copy loop, the IOException logging and the finally block (closeQuietly, releaseFile) are unchanged."
     ],
     "completeness": [
      "The fix covers the output open in RemotePrintDocument.writeContent, the only openOutputStream call shown in the material.",
      "The fix does not verify the resulting file length; actual truncation depends on the destination provider honoring \"wt\".",
      "IOExceptions are still only logged (Log.e \"Error writing document content.\") and swallowed in writeContent."
     ],
     "behavior_changes": [
      "When saving to PDF, the destination is opened with mode \"wt\" (truncate) instead of the default mode, so overwriting an existing file is intended to produce only the new document bytes."
     ],
     "preconditions": [
      "User saves a print job to PDF via PrintSpooler and selects an existing destination file (per the commit message).",
      "Inferred, not shown: the existing file would need to be larger than the new PDF for leftover data to remain.",
      "writeContent is invoked with a ContentResolver and the user-chosen Uri; its callers are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base a52780f0ce9e in android-security-14.0.0_r22, one-line change in RemotePrintDocument.writeContent; the code context at android-security-14.0.0_r29 shows the \"wt\" line.",
      "15": "same fix: platform/frameworks/base 252efec89b01 in android-security-15.0.0_r10, identical diff (index bfc00bb8b94d..985482904727); at android-security-15.0.0_r17, 4/5 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base 177eda822164 in android-security-16.0.0_r1, identical one-line hunk on a different file base (a9d00e9a77eb..287842992bef); at android-security-16.0.0_r8, 4/5 lines of the patched region are present.",
      "17": "not listed by the bulletin (not affected per the bulletin); at android-security-17.0.0_r1, 4/5 lines of the patched region are present. Whether the fixed line is there is not shown."
     },
     "backport_notes": [
      "Single self-contained one-line change in RemotePrintDocument.writeContent with no other commits in the material. ContentResolver.openOutputStream(Uri, String mode) is a long-standing public API (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The callers of writeContent and the destination providers used are not shown.",
      "How providers that do not support \"wt\" behave is not shown.",
      "Whether Android 13 (listed as affected) received the same patch is not shown.",
      "Why the per-major facts report 4/5 lines of the patched region present in every branch is not explained; the fixed line is shown only at 14.0.0_r29.",
      "Whether the fixed line is present at 15.0.0_r17, 16.0.0_r8 and 17.0.0_r1 is not directly shown."
     ],
     "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-48563": {
     "cve": "CVE-2025-48563",
     "summary": "Adds an onNullBinding() override to RemoteFillService that calls unbind(), so system_server no longer stays bound, with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, to an autofill service whose onBind() returned null.",
     "component": "platform/frameworks/base, autofill system service (services/autofill, RemoteFillService)",
     "weakness": "Missing handling of the null-binding callback in a privileged service connection (a retained binding that carries the background-activity-start allowance; EoP per bulletin)",
     "root_cause": "RemoteFillService (a ServiceConnector.Impl<IAutoFillService>) binds the autofill service component with Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, plus BIND_ALLOW_INSTANT when allowed, as its constructor shows. It implemented onServiceConnectionStatusChanged but did not override ServiceConnection.onNullBinding(). The commit message says that an autofill service returning null from onBind() therefore stayed bound by system_server and could launch activities from the background.",
     "fix_analysis": [
      "services/autofill/java/com/android/server/autofill/RemoteFillService.java: adds `public void onNullBinding(@NonNull ComponentName name)`, which overrides the ServiceConnection callback. It logs \"onNullBinding\" when sVerbose is set and then calls unbind(), which drops the binding when the remote service returns a null binder.",
      "No other logic changes. The bind flags passed to super() in the constructor (BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, optional BIND_ALLOW_INSTANT) stay the same."
     ],
     "completeness": [
      "Covers the null-binding case for the autofill fill-service connection in RemoteFillService.",
      "Only RemoteFillService.java is changed. The material does not show other ServiceConnector.Impl subclasses that bind with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, so it is not visible whether they already handle onNullBinding.",
      "The fix depends on the platform delivering onNullBinding for a null onBind() result. It adds no other validation of the returned binder."
     ],
     "behavior_changes": [
      "An autofill service that returns null from onBind() is now unbound by system_server when onNullBinding fires, instead of staying bound.",
      "A new verbose log line \"onNullBinding\" is written under TAG when sVerbose is enabled.",
      "Per the commit message, such a service can no longer launch activities from the background through this binding."
     ],
     "preconditions": [
      "RemoteFillService is created for an autofill service ComponentName and user (the material does not show how that component is chosen).",
      "The autofill service's onBind() returns null for the AutofillService.SERVICE_INTERFACE intent, so onNullBinding is delivered.",
      "system_server binds the service through RemoteFillService (the exact trigger is not shown in the material)."
     ],
     "per_major": {
      "14": "same fix: commit 1f704bff05f0 in android-security-14.0.0_r22, an 8-line addition of onNullBinding() in RemoteFillService.java; 2/2 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit a6a570a6f497 in android-security-15.0.0_r10, identical hunk at a different offset (line 116 vs 94); 2/2 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit dee86220f445 in android-security-16.0.0_r1, identical hunk (line 117); 2/2 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (2/2 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single, self-contained commit. It goes between onServiceConnectionStatusChanged and dispatchCancellationSignal; the hunk context is the same on 14, 15 and 16, with only line offsets differing. It needs RemoteFillService to extend ServiceConnector.Impl (for unbind()) and needs sVerbose, TAG and Slog to be in scope."
     ],
     "confidence": "high",
     "unknowns": [
      "The bulletin lists Android 13 as affected, but the material includes no Android 13 fix commit or tag.",
      "The ServiceConnector.Impl base class is not shown, so the exact behavior before the fix in the null-binding case (for example, whether the idle auto-disconnect timeout applied) is not visible.",
      "What triggers the bind of the autofill service is not shown.",
      "It is not shown whether other ServiceConnector-based connections that bind with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS lack an onNullBinding override."
     ],
     "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."
     }
    }
   }
  },
  "15": {
   "counts": {
    "announced": 50,
    "certain": 50,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2023-24023",
     "osv_id": "ASB-A-255601934",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the bluffs_mitigation aconfig flag so that four protections in system/stack/btm/btm_sec.cc are always active instead of being gated behind a flag that could be off: the BR/EDR encryption key size downgrade check, the cached session key size update, the Secure Connections support caching and the Secure Connections downgrade disconnect.",
      "confidence": "high",
      "backport_notes": [
       "This commit only removes a flag. It requires the earlier flagged mitigation (helpers btm_sec_is_session_key_size_downgrade, btm_sec_update_session_key_size, btm_sec_store_device_sc_support, btm_sec_is_device_sc_downgrade and the sec_rec accessors) to already exist. On a tree without them, follow the Android 14 approach (b5155f0591f9) of adding the checks directly without a flag.",
       "Function signatures differ across branches. Per index search at android-15.0.0_r36, btm_sec_encryption_change_evt and btm_sec_encrypt_change take an extra key_size parameter, while the diff and the r17 context use the 3-parameter form. Adapt call sites accordingly."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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-15.0.0_r5",
      "date": "2025-02-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r5",
      "date": "2025-02-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "f904fd5ad7ef4222bb2f310c2486e4443f2a8837",
       "tag": "android-security-15.0.0_r5",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref f904fd5ad7ef",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 210,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-0076",
     "osv_id": "ASB-A-366403307",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SystemUI's Quick Access Wallet tile and wallet screen now call Icon.loadDrawable() on wallet card images and card icons only when the Icon type is TYPE_BITMAP or TYPE_ADAPTIVE_BITMAP. Every other Icon type (URI, URI_ADAPTIVE_BITMAP, RESOURCE, DATA) gives a null drawable.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The production change is two small hunks (the QuickAccessWalletTile switch and the QAWalletCardViewInfo constructor) plus a test-only accessor in WalletView.",
       "The new QuickAccessWalletTileTest assumes a particular QuickAccessWalletTile constructor signature (QsEventLogger, SecureSettings, QuickAccessWalletController). On customized trees, adjust the test, not the fix."
      ],
      "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-15.0.0_r6",
      "date": "2025-03-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r6",
      "date": "2025-03-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d6ad7f34eaf5f0452b93a650265ee432288c0978",
       "tag": "android-security-15.0.0_r6",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 182,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-0089",
     "osv_id": "ASB-A-378505461",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds home-intent normalization to ActivityStartInterceptor. An ACTION_MAIN intent with CATEGORY_HOME/CATEGORY_SECONDARY_HOME in a non-standard form (extra categories, data/type, or an explicit default-home component) is rewritten into a standard home intent, re-resolved and launched with FLAG_ACTIVITY_NEW_TASK instead of being started as a standard-type activity. A companion commit makes the AMS mStrictModeCallbacks map self-locked.",
      "confidence": "medium",
      "backport_notes": [
       "For trees without interceptHomeIfNeeded, follow the Android 14 variant: add interceptHomeIfNeeded and normalizeHomeIntent, make ActivityRecord.isHomeIntent static, add TAG/Slog, and pass request.componentSpecified through intercept() and the ActivityStarter call. Keep the overload without componentSpecified for existing tests.",
       "The mStrictModeCallbacks lock change is a separate commit, linked only by bug id. Apply it if registerStrictModeCallback and triggerUnsafeIntentStrictMode use the AMS lock on the target branch."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r10",
      "date": "2025-09-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ed39b7c3895c8c63a1ccdbcc9783a2d3ca15127f",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "f27918b39cffb404ed429829f93b20344310da34",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 119,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26454",
     "osv_id": "ASB-A-299928772",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ManagedProvisioning's DisclaimersParserImpl now validates each EXTRA_PROVISIONING_DISCLAIMER_CONTENT URI before copying the URI's content into its own disclaimer file cache, and skips disclaimers that fail validation. The scheme must be content: or android.resource:, and for content: URIs the calling pid/uid must hold read permission via checkUriPermission.",
      "confidence": "medium",
      "backport_notes": [
       "Apply both commits in order (Change-Id I4c5ab64c... first, then Ie19576c1...). The second is a one-hunk edit inside validateUriSchemeAndPermission, which the first commit introduces. The second commit's message indicates that, without it, android.resource: URIs are also subjected to the read-permission check.",
       "The change is confined to DisclaimersParserImpl.java (new imports: Intent, PackageManager, Binder, Objects)."
      ],
      "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_r9",
      "date": "2025-06-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/apps/ManagedProvisioning",
       "sha": "d33d045407c5bd0000442667d9ef5c9fc3f590e5",
       "tag": "android-security-15.0.0_r9",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/ManagedProvisioning",
       "sha": "03cadb65c0b6a91a480041aa9129e9dbf995279b",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 91,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/apps/ManagedProvisioning"
     ]
    },
    {
     "cve": "CVE-2025-26464",
     "osv_id": "ASB-A-392614489",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the whole body of the hidden binder method AppSearchManagerService.executeAppFunction (AppSearch module, Android 15) with an unconditional UnsupportedOperationException. This disables the never-launched, flag-guarded app-function execution path in which, according to the commit, a security issue was reported.",
      "confidence": "medium",
      "backport_notes": [
       "Single-file, self-contained change: replace the body of the executeAppFunction binder override in AppSearchManagerService.java with the throw. executeAppFunctionUnchecked stays in the file.",
       "Android 15 forks should confirm the method still exists in their AppSearchManagerService. AppFunctionManagerServiceImpl in frameworks/base (present in Android 15 per index search) is a separate service and this commit does not cover it."
      ],
      "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_r9",
      "date": "2025-06-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r9",
      "date": "2025-06-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/AppSearch",
       "sha": "e272ad00529243f766c6ebd4e976549bd4fff4fb",
       "tag": "android-security-15.0.0_r9",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 91,
     "projects": [
      "platform/packages/modules/AppSearch"
     ]
    },
    {
     "cve": "CVE-2025-32321",
     "osv_id": "ASB-A-378902342",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountTypePreferenceLoader.isSafeIntent in Settings now pins the validated intent to the resolved authenticator-owned component with intent.setComponent(). The activity that gets launched is then the one whose uid was checked, not whatever an implicit intent resolves to later.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in isSafeIntent with no dependencies shown. The Android 14 and 15/16 hunk contents are identical apart from a one-line offset, so it should apply to any branch that has the `return resolvedAppInfo.uid == authenticatorAppInf.uid;` form. Settings is a platform app, not a mainline module, so the fix ships with the platform build (general platform knowledge)."
      ],
      "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-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/apps/Settings",
       "sha": "bb6e84fd04fcc3594750645982f8c667b0cd8c5e",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32323",
     "osv_id": "ASB-A-397216537",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DocumentsUI now passes the calling app's label through TextUtils.makeSafeForPresentation (500 char cap, trim, single line) in Shared.getCallingAppName and wraps the app name in quotation marks in the open_tree_dialog_message string. This limits how an application label can affect the ACTION_OPEN_DOCUMENT_TREE confirmation dialog text.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. Update any vendor callers of Shared.getCallingAppName that expect a String (it now returns CharSequence). Removing DocumentsAdapter.Environment.getCallingAppName is cleanup and must be done together with all of its implementations.",
       "The new test is written in Kotlin with Mockito. If the fork's unit test target lacks Kotlin support, the test can be left out without affecting the fix. TextUtils.makeSafeForPresentation and the SAFE_STRING_FLAG_* constants are public API since API 29 (general platform knowledge)."
      ],
      "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_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/apps/DocumentsUI",
       "sha": "f0336ada165fdda955eaf6245b501779f36f0bd3",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2025-32324",
     "osv_id": "ASB-A-406763872",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Moves the runStartActivity(pw) call of the `am start-in-vsync` shell command out of the Choreographer frame callback (posted via the system_server UI handler) back onto the shell command's own thread, so the activity is started with the shell caller's pid/uid.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, self-contained change. The only prerequisite is that the branch contains the start-in-vsync case with ProgressWaiter, mUiHandler.runWithScissors and Choreographer.postFrameCallback. If that case is absent (likely on Android 14), there is nothing to backport."
      ],
      "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_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/frameworks/base",
       "sha": "0fb2788dac393086b7e53fbe05414368ae395d9b",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32325",
     "osv_id": "ASB-A-402319736",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens Parcel::appendFrom in libbinder: it now refuses to append over existing objects in the destination, copies and acquires only the source objects that lie fully inside the requested [offset, offset+len) range, clears the mObjectsSorted flag in more cases, and keeps the RPC mObjectPositions vector sorted.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. Apply the Parcel.cpp hunks. Where acquire_object lacks the tagFds parameter, or the grow check in appendFrom uses mDataSize instead of mDataPos, keep the branch's existing form and add only the new lines, as the 14 and 16 cherry-picks did.",
       "Make sure validateReadData exists on the target branch and that the test file has a PERMISSION_DENIED using-declaration."
      ],
      "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_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/frameworks/native",
       "sha": "96f0dd71bc07051e4faae44abf7121a3f7b5b2aa",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-32326",
     "osv_id": "ASB-A-365739560",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the app-supplied custom restrictions Intent is now launched as a copy pinned to the component and package that passed the resolveActivity vetting, instead of re-launching a copy of the original (possibly implicit) Intent.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained Java change in one file with no new dependencies; it relies on the pre-existing assertSafeToStartCustomActivity method (EventLog 223578534 / resolveActivity check) being present, which the 14, 15 and 16 diffs all show. At android-security-15.0.0_r17 the method body differs from the diff (new Intent(intent) copy appears right after the EventLog call), so apply by intent rather than by exact context when porting to customized trees."
      ],
      "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_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/apps/Settings",
       "sha": "e8a17485771e54124abc08e8fb6c987bf83726a2",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32327",
     "osv_id": "ASB-A-389681152",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PickerDbFacade no longer puts caller-supplied unknown projection column names into the SQL projection. Unknown columns are left out of the query and added back as null-valued columns through a MatrixCursor after the query runs.",
      "confidence": "medium",
      "backport_notes": [
       "Each branch has one self-contained commit. Between 14 and 15, the source changes differ in the method name (queryMediaIdForAppsInternal vs queryMediaIdForAppsLocked) and in the pickerSegmentType parameter. The tests differ too: 14 adds the assertWithMessage import. The fix needs the import android.database.DatabaseUtils. It sits next to the existing static import of com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar.",
       "MediaProvider is a mainline module (general platform knowledge). Check the installed module version when you assess coverage."
      ],
      "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_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": "3f48b77e7cb9c15a76d42cfe4b2c771611625f9c",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-32330",
     "osv_id": "ASB-A-389127608",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the LE audio sharing (broadcast) password generator in SettingsLib. The old generator took a 12-hex-character password from a UUID string. The new one builds a 16-character password with SecureRandom from an 89-character alphabet.",
      "confidence": "high",
      "backport_notes": [
       "A single self-contained change: one private method, two constants and one import. If java.util.UUID is unused elsewhere in a fork's copy of the file, the import can be dropped as in the Android 14 commit. Otherwise keep it as in Android 15.",
       "Check that fork-specific UI or metadata consumers do not assume a 12-character hex broadcast code. The new password is 16 characters and can include punctuation."
      ],
      "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_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/frameworks/base",
       "sha": "5b10581d2a91ddb256a1e37efcbcdb015091f5a1",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32331",
     "osv_id": "ASB-A-404252173",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix makes IKeyguardService.showDismissibleKeyguard() in SystemUI's KeyguardService return early whenever ActivityManager reports a lock task mode other than NONE (app pinning). As a result, folding a device with \"Swipe up to continue\" set no longer shows a dismissible keyguard over a pinned app.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained. Place the getLockTaskModeState() early return directly after checkPermission() in showDismissibleKeyguard(), and inject ActivityManager into KeyguardService. SystemUI's Dagger framework services module normally provides ActivityManager (general platform knowledge). Any code that constructs KeyguardService directly must be updated for the new constructor parameter."
      ],
      "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_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/frameworks/base",
       "sha": "7f5cc94e82fca9b758c46c97d6be9cc38ef07208",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32345",
     "osv_id": "ASB-A-409318132",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings, ContentProtectionTogglePreferenceController.updateState now disables the Content Protection main switch for every non-admin user (!UserManager.isAdminUser()) instead of only for guest users (UserManager.isGuestUser()).",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching two files. The production change is a one-line condition swap in updateState. The updated test uses ShadowUserManager.setIsAdminUser(...)."
      ],
      "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_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/apps/Settings",
       "sha": "6c9a5944dd0458b90263da2e4a4ba618c69779dd",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32347",
     "osv_id": "ASB-A-388528350",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricEnrollIntroduction now strips the MultiBiometricEnrollHelper extras EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT from its intent in onStart() whenever the calling package is not Settings itself, so PendingIntent extras supplied by external packages are dropped during biometric enrollment.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It relies on MultiBiometricEnrollHelper exposing the three EXTRA_* constants. If a downstream tree has subclasses that call removeEnrollNextBiometricIfSkipEnroll or handleBiometricResultSkipOrFinished, the change from protected to private will break the build.",
       "Every latest security tag shows 7 of 8 lines of the patched region. Check directly that the onStart() override and the three removeExtra calls are present instead of relying on the line count."
      ],
      "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_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/apps/Settings",
       "sha": "25cfacbe5ac2423b8fe1375e0593ef69e98b8d09",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32349",
     "osv_id": "ASB-A-408215749",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowManager stops honoring app-supplied window animations for untrusted overlays: untrusted TYPE_TOAST windows are forced to R.style.Animation_Toast in DisplayPolicy.adjustWindowParamsLw, and any non-system overlay that the system force-hides exits with a fixed short alpha fade in WindowStateAnimator instead of its own exit animation.",
      "confidence": "medium",
      "backport_notes": [
       "Carry both commits; they touch separate files. The DisplayPolicy hunk needs win.mSession.mCanAddInternalSystemWindow and R.style.Animation_Toast. The WindowStateAnimator hunk needs the mForceHideNonSystemOverlayWindow field in WindowState plus the new getter, the AlphaAnimation import and config_shortAnimTime.",
       "The bulletin lists Android 13 as affected, but the material has no 13 commit. Check that the new else-if lands before the per-transit switch in WindowStateAnimator's animation-selection code."
      ],
      "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_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/frameworks/base",
       "sha": "394acf2aa1dade06c9cb2b98d92d6e585de31012",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "e4a93e6ffdaf0e51c2effd26a222a4e0b66ea5cb",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32350",
     "osv_id": "ASB-A-404256832",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes android:exported=\"true\" from SystemUI's ControlsActivity manifest entry and adds SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS to the controls settings dialog window created by ControlsSettingsDialogManager, so other apps cannot launch the activity and non-system overlay windows cannot be drawn over the dialog.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit with no dependencies: delete the one manifest attribute, then add the import and the d.window?.addSystemFlags(...) line after the AlertDialog builder in ControlsSettingsDialogManager.kt. On Android 14 branches the R import is com.android.systemui.R rather than com.android.systemui.res.R, and the test file imports differ, as reflected in b08dcc947008.",
       "If a fork added an intent-filter or a permission to ControlsActivity, re-check the manifest: an element with an intent-filter needs an explicit android:exported value on recent targetSdk levels (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": [
      "change_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/frameworks/base",
       "sha": "47424521c49b638334ed676223e7191178074da9",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref d6b86833ee06",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48522",
     "osv_id": "ASB-A-418773283",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a 1024-character upper limit on the display name accepted by AssociationRequest.Builder.setDisplayName in the Companion Device Manager API. A longer name now throws IllegalArgumentException.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained (one constant and one if-block in Builder.setDisplayName). It matches the 14 and 15 trees exactly (same file index) and needs only an offset adjustment on 16-style trees, which have extra fields near the constant. The material includes no fork comparison.",
       "The new IllegalArgumentException is a public API behavior change. The commit message references 'Test: cts', but no test change is in the material."
      ],
      "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_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/frameworks/base",
       "sha": "bdad29b85a6ca7c55a697e4e66356b744ef6cdb9",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48523",
     "osv_id": "ASB-A-388032224",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the single-account shortcut in Contacts' vCard SelectAccountActivity so the account selection dialog is always shown, instead of silently returning the only writable account and allowing an automatic vCard import.",
      "confidence": "high",
      "backport_notes": [
       "Single commit that only deletes one else-if block in SelectAccountActivity.onCreate. The same hunk (index 8ead5fab3..797fc8670) was applied unchanged on 14, 15 and 16, so it should apply cleanly to any tree whose onCreate still has the `accountList.size() == 1` shortcut. Optionally update the stale three-case comment above it."
      ],
      "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": [
      "change_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/apps/Contacts",
       "sha": "7c8dbcffbc9382fcdb788919106c3b0525db83ab",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref c314edb9bde8",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Contacts"
     ]
    },
    {
     "cve": "CVE-2025-48524",
     "osv_id": "ASB-A-399885815",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WifiPermissionsUtil.isSystem() now returns false up front when the calling uid is an SDK sandbox uid (on T and later), so SDK sandbox processes are no longer classified as system apps by the Wifi service.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that uses SdkLevel.isAtLeastT() and Process.isSdkSandboxUid. Android 13 is listed as affected by the bulletin, but the material includes no 13 commit; the same 3-line insertion into isSystem() should apply if the method body matches."
      ],
      "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-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/modules/Wifi",
       "sha": "298745e0cb23cbef631aff1977b284155384bbf0",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2025-48526",
     "osv_id": "ASB-A-407764858",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ChooserActivity now gives tabs for profiles other than the launched-as profile a sanitized copy of the payload intents, with package and component cleared on each intent and on its selector, instead of the caller's original explicit intents.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. For trees with the Kotlin ChooserRequest model, use the 15/16 shape: crossProfilePayloadIntents plus isCrossProfile in createMultiProfilePagerAdapter, updating both call sites. For the older ChooserActivity derived from ResolverActivity, with mIntents and PROFILE_PERSONAL/PROFILE_WORK, use the 14 shape with the null-tolerant helper. The Profile.kt hunks are formatting only and can be dropped if they conflict."
      ],
      "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_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/modules/IntentResolver",
       "sha": "923a5673ac9d4b366097a8912a04e40e85111ed4",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48527",
     "osv_id": "ASB-A-378088320",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' Notification History screen now shows the generic \"notification_hidden_text\" string as the summary (and sets no title) for history, snoozed and dismissed notifications belonging to a profile that has a secure lock, is currently locked, and has private notifications disallowed on the lock screen, instead of showing the real title and text.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit in packages/apps/Settings. It relies on com.android.internal.widget.LockPatternUtils, KeyguardManager.isDeviceLocked(int) and com.android.internal.R.string.notification_hidden_text, which all three branch variants use unchanged. The main adaptation is the surrounding context (imports, GONE/VISIBLE) and updating any other places that construct the two adapters."
      ],
      "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_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/apps/Settings",
       "sha": "d8b1de7a18fc6a469c2d191ff9a0771f5d104fe4",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48528",
     "osv_id": "ASB-A-384727394",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reverts the \"Fix biometric prompt appearing above shade\" change in SystemUI's AuthContainerView. The BiometricPrompt window moves back from TYPE_APPLICATION_OVERLAY to TYPE_KEYGUARD_DIALOG, and the panel interaction detector is enabled unconditionally again.",
      "confidence": "medium",
      "backport_notes": [
       "Check which window type AuthContainerView uses for the prompt window, and whether mPanelInteractionDetector.enable() sits inside if (!constraintBp()). If the tree matches the pre-fix 15 state, the 15 commit is a 3-line revert in a single file.",
       "The 16 forward fix depends on the newer UdfpsControllerOverlay constructor shape and adds PromptUdfpsTouchOverlayViewModel plus test changes. On 15-based trees, the window-type change in the revert is the core part."
      ],
      "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_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/frameworks/base",
       "sha": "ca71b9a63c40ef3fa741c76a2835146283fbb69a",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48529",
     "osv_id": "ASB-A-325030433",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Stops Telephony from storing, reading back, or migrating a caller-supplied voicemail ringtone URI through its default SharedPreferences. TelephonyManager#setVoicemailRingtoneUri becomes a no-op, and getVoicemailRingtoneUri only reports the sound of the voicemail NotificationChannel.",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching two files in frameworks/opt/telephony, with identical hunks on 14/15/16. The setRingtoneUri and getRingtoneUri signatures are unchanged, so the PhoneInterfaceManager caller (per index search) still compiles.",
       "After backporting, check that nothing else in a customized tree still calls the deleted getRingTonePreference."
      ],
      "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_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/frameworks/opt/telephony",
       "sha": "e5cdca27526f5c2c358880538c7a15d8d5d5dd6d",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/opt/telephony"
     ]
    },
    {
     "cve": "CVE-2025-48531",
     "osv_id": "ASB-A-389681530",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' CredentialStorage now identifies the launching app via ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) instead of Activity.getCallingPackage() when deciding whether the caller is the trusted certinstaller package.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained Java change in CredentialStorage.java with no dependencies beyond the hidden ActivityManager.getService().getLaunchedFromPackage(IBinder) API, which Settings can access as a platform-signed system app (general platform knowledge). The diff is the same across 14/15/16 apart from hunk offsets; Android 13 is listed as affected by the bulletin but no 13 commit is in the material."
      ],
      "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_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/apps/Settings",
       "sha": "cc1b1b5e493affcb1ef9c3543b10c89141f245c4",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48534",
     "osv_id": "ASB-A-381419370",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "CellBroadcastHandler.getDefaultCBRPackageName now queries broadcast receivers with PackageManager.MATCH_SYSTEM_ONLY instead of flags 0, so only system packages can be selected as the default CellBroadcastReceiver package.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit: the source change is a one-argument flag change plus a visibility change and annotation. The only differences seen between branches are in the test file (import placement)."
      ],
      "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_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/modules/CellBroadcastService",
       "sha": "584cec4f17eab96ac44bce4e1bce8d6a2c59cd75",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/CellBroadcastService"
     ]
    },
    {
     "cve": "CVE-2025-48535",
     "osv_id": "ASB-A-353680402",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is now launched as a copy. The copy is the object that was resolved, and its component and package are set to the ActivityInfo that passed the vetting check. Before, Settings relaunched the app-supplied Intent after only checking it.",
      "confidence": "high",
      "backport_notes": [
       "Apply in this order: first Iff0d820c1261c29eb6703bf89194339cba700688 (assertSafeToStartCustomActivity returns the copy with component and package set, and the caller uses it), then I860e9e606de6b8d3c99fa52a63b72ba7a99ce179 (moves new Intent(intent) before resolveActivity). The second diff's context depends on the first. Both commits touch only src/com/android/settings/users/AppRestrictionsFragment.java.",
       "Consider also picking the follow-up that adds removeFlags(FLAG_GRANT_*_URI_PERMISSION), setData(null) and setClipData(null), seen at 15.0.0_r17. It is not in the provided diffs."
      ],
      "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-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/apps/Settings",
       "sha": "1e4423730f8776bd09df7614474643ae735d2176",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "e8a17485771e54124abc08e8fb6c987bf83726a2",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48537",
     "osv_id": "ASB-A-391894257",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ZenModeConditions now tracks CPS subscriptions by (condition Uri, ComponentName) pairs instead of by Uri alone, only lets system-owned (or manual) zen rules be bound to the built-in system ConditionProviderServices, and ignores non-system rules whose component lives in the \"android\" package.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. On branches where evaluateRule lacks the isManual parameter (as on Android 14), add it and pass true for config.manualRule and false for automatic rules as in 49078c4bfdce; ConditionProviders.java hunks are test-support only (visibility of ConditionRecord plus getRecord) and their surrounding context differs by branch.",
       "ZenModeHelperTest.testRulesWithSameUri is updated alongside to register a package CPS (per its comment, otherwise ZenModeConditions clears rule.condition)."
      ],
      "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_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/frameworks/base",
       "sha": "63aab59ce13856799a7c24a70b35625d32ae5357",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48538",
     "osv_id": "BUL-CVE-2025-48538",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Extends the existing \"cannot hide package android\" guard in PackageManagerService so that the SystemUI package (resolved via PackageManagerInternal.getSystemUiServiceComponent()) can also not be hidden, preventing a device-unusable state.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained 5-line change; it relies on PackageManagerInternal exposing getSystemUiServiceComponent(), and since the diffs add no imports, LocalServices and PackageManagerInternal must already be resolvable in PackageManagerService.java on 14, 15 and 16. On a fork, locate the existing `\"android\".equals(packageName)` guard with the \"Cannot hide package\" log and extend it the same way."
      ],
      "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_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/frameworks/base",
       "sha": "bd7578b738a09734a2d23656e5569643ad37fffe",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48539",
     "osv_id": "ASB-A-406785684",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In AclArbiter::SendPacketToPeer (system/stack/arbiter/acl_arbiter.cc), the failure-path warning log now reads the length from the rust::Vec argument (buffer.size()) instead of from the BT_HDR p_buf, which L2CA_SendFixedChnlData may already have freed.",
      "confidence": "high",
      "backport_notes": [
       "A single one-line change with no dependencies. For trees with the L2CA_SendFixedChnlData(...) != L2CAP_DW_SUCCESS form, use the Android 15 diff. For trees with the stack::l2cap::get_interface() / tL2CAP_DW_RESULT::SUCCESS form, use the Android 16 diff. In both cases, replace p_buf->len with buffer.size() in the warn call."
      ],
      "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-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/modules/Bluetooth",
       "sha": "c881220f499edcbdff9a7efdf00beeaeba084245",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-48540",
     "osv_id": "ASB-A-416734088",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Three commits in libs/binder/RpcState.cpp make RPC Binder explicitly terminate the session (session->shutdownAndWait(false)) before returning an error on unrecoverable command errors: CommandData allocation failure in waitForReply and processTransact, a bad object-table span in a transaction, and RPC_COMMAND_TRANSACT received when the command type is restricted. Previously these paths returned an error and left the session alive.",
      "confidence": "medium",
      "backport_notes": [
       "On 15 the blob hashes give this order: ENOMEM (ba4ea3598e6d), then SPAN (570e2d6e29ee), then wrong transact type (7fb4755c9d93). Each commit is a small insertion in libs/binder/RpcState.cpp. Hunk offsets differ between branches (for example @@ -946 on 14 vs @@ -984 on 15)."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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_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/frameworks/native",
       "sha": "570e2d6e29ee10879150f868913c285a45a936b1",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "7fb4755c9d93bf75de13f2bc458fbbb547a79dd6",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "ba4ea3598e6dcea4b7b2202f4cec11eb1d85c2a7",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48541",
     "osv_id": "ASB-A-411418366",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "FaceSettings now reads the hw_auth_token, sensor id, challenge and EXTRA_USER_ID intent extras only when the hosting SettingsActivity was launched by the Settings package itself. For any other caller, or an unknown one, it ignores those extras and uses the current user.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, self-contained change. It needs SettingsActivity.getInitialCallingPackage() to already exist in the target branch: all three branch commits use it without adding it. The only difference between branches is the FeatureFactory context line (getFactory(getContext()) on 14, getFeatureFactory() on 15 and 16), and it does not need to change."
      ],
      "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_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/apps/Settings",
       "sha": "aba2077a6c03f6d7b1a315c275e186ea0f2c1b6b",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48542",
     "osv_id": "ASB-A-273501008",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds per-account storage accounting to AccountManagerService. Auth-token and user-data writes are refused once an account's share of a 30,000,000 (STORAGE_LIMIT_PER_USER) per-user budget is used up. Password length is capped at 1,000,000 chars when an account is added and when a password is set.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches only AccountManagerService.java. It relies on the existing userDataCache, authTokenCache and accountCache fields and on the cacheLock/dbLock locks. Forks that restructured UserAccounts or the auth-token save transaction must keep the shouldBlockDatabaseWrite checks under cacheLock and before insertAuthToken. The bulletin lists Android 13 as affected, but the material contains no Android 13 commit."
      ],
      "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_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/frameworks/base",
       "sha": "7ed50e5b5d568bc8be5d3603991aa9add67a7f44",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48543",
     "osv_id": "ASB-A-421834866",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an IsInstantiable() check to the ART JNI entry points AllocObject, NewObjectV and NewObjectA, so that allocating an instance of an abstract class or interface throws java.lang.InstantiationException instead of creating an object. It also reduces the CheckJNI-only check to an array-class check.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit, and the jni_internal.cc and check_jni.cc hunks should be taken together. With only the jni_internal.cc hunks, CheckJNI would still abort on abstract classes before the new exception path is reached. With only the check_jni.cc hunks, CheckJNI would lose the abstract-class check and nothing would replace it at runtime."
      ],
      "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_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/art",
       "sha": "444fc40dfb04d2ec5f74c443ed3a4dd45d3131f2",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2025-48545",
     "osv_id": "ASB-A-397438392",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountManagerService.isSystemUid() now returns false immediately for SDK sandbox UIDs instead of resolving the UID's packages and treating the caller as system if any resolved package carries FLAG_SYSTEM.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, self-contained 3-line change that depends on Process.isSdkSandboxUid, which the 14, 15 and 16 fixes use. Apply it at the top of isSystemUid, before the Binder.clearCallingIdentity() call. The hunk is identical across 14, 15 and 16 apart from line-number offsets."
      ],
      "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_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/frameworks/base",
       "sha": "66ac17909252c80b0edf7f4ae282bce4579410ad",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48546",
     "osv_id": "ASB-A-388029380",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SafeActivityOptions now rejects, with a SecurityException, activity launches whose ActivityOptions carry setLaunchWindowingMode(WINDOWING_MODE_PINNED), closing a path by which a caller could request a launch directly into the pinned (picture-in-picture) windowing mode.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit: one import plus an 11-line block inserted right before the 'final int activityType = options.getLaunchActivityType();' check in SafeActivityOptions.java. It applied to 14, 15 and 16 with only line-offset differences; the test file changes are not needed for the fix itself."
      ],
      "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_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/frameworks/base",
       "sha": "8b660e88700541ca70a01c7b25a52587ec65411f",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48547",
     "osv_id": "ASB-A-419105158",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PermissionController now calls PermissionManager.stopOneTimePermissionSession(packageName) on revoke only when no permission of the package remains granted as one-time, instead of stopping the package-wide session whenever a single one-time permission (group) is revoked.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. Take both files together, since the Java and Kotlin paths each call stopOneTimePermissionSession on their own. On Android 14, the Kotlin caller passes group.packageName directly to stopOneTimePermissionSession (no device context); the Kotlin change there is the condition line, the PackageInfo import and the helper rewrite. PermissionController ships in the Permission mainline module, so the fix can also arrive via a module update (general platform knowledge)."
      ],
      "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_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/modules/Permission",
       "sha": "5dca0ccb26f2b99d706a1d3e9402f851e849c913",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2025-48548",
     "osv_id": "ASB-A-293603271",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes the audioserver recording silenced state and the AppOps started-op state consistent: startRecording now returns the exact permission result, a recording client is un-silenced only when the op is actually GRANTED, soft-denied starts use a rejected noteOp instead of leaving a paused started op, virtual sources use only their own op, and SystemUI suppresses the mic indicator only when all recordings of a uid are silenced.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the PermissionManagerService preflight change (20e363e22258) together with the frameworks/av change. The new checkRecordingInternal calls checkPermissionForPreflightFromDatasource before starting the op, and the commit message says audioserver relies on preflight not failing its checkOp. The av change also changes the startRecording signature in ServiceUtilities.h, so downstream callers must be updated. Per index search, nothing outside av was found among the top results, but the search may be incomplete.",
       "For Android 14 the material shows a different base-side commit (bbd8db54b3c9, which finishes started proxy ops when a later chain link fails). Backports to 13/14-based trees should check which PermissionManagerService changes match the local chain-walking code."
      ],
      "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_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/frameworks/av",
       "sha": "37e7f808fad105da187b021fb762a66d37c9212a",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "00344da68fce6ec4f7a1bf36f0ea3797805f00ce",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "20e363e2225843ff3cc7d6bea05ae2f4db83b408",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "acbd37d21c2feffb6d64e669b956d59a6062b751",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "projects": [
      "platform/frameworks/av",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48549",
     "osv_id": "ASB-A-325912429",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes audioserver's OpRecordAudioMonitor check and watch the recording app op (and always OP_RECORD_AUDIO) for every link of the AttributionSource chain instead of only the top entry. Two companion changes ship with it: SystemUI now hides the mic indicator only when all of a UID's recordings are silenced, and PermissionManagerService's preflight check skips the first chain link the same way data delivery does.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the frameworks/av AudioRecordClient.cpp change together with the PermissionManagerService preflight change. The latter's commit message says audioserver relies on it, because audioserver fails checkOp. AttrSourceItr uses a defaulted operator==, which requires C++20 (general platform knowledge).",
       "On Android 14 the material shows only the SystemUI commit; confirm whether the av and PermissionManagerService counterparts exist on that 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_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/frameworks/av",
       "sha": "8c09eb1034cb3b02a66f6c241c0b9c9981998d6f",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "00344da68fce6ec4f7a1bf36f0ea3797805f00ce",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "20e363e2225843ff3cc7d6bea05ae2f4db83b408",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "acbd37d21c2feffb6d64e669b956d59a6062b751",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "projects": [
      "platform/frameworks/av",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48550",
     "osv_id": "ASB-A-401256328",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds package-name validation (FrameworkParsingPackageUtils.validateName with requireSeparator=false, requireFilename=true) to both PkgUser constructors in SlicePermissionManager, so an invalid package string is rejected with IllegalArgumentException.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The only new dependency is android.content.pm.parsing.FrameworkParsingPackageUtils.validateName(String, boolean, boolean). The bulletin lists Android 13 as affected, but the material includes no Android 13 commit."
      ],
      "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_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/frameworks/base",
       "sha": "354820f6ec38e8c50140bb5247779d3a3423b4c4",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48551",
     "osv_id": "ASB-A-407991863",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Sharesheet \"Edit\" image action in IntentResolver's ChooserActivity now launches the image editor as the user that launched the sharesheet (mUserInteractor.getLaunchedAs()) instead of always as the personal profile user (mProfiles.getPersonalHandle()).",
      "confidence": "high",
      "backport_notes": [
       "On Android 14 style branches (ChooserActivity extends ResolverActivity), Google used UserHandle.of(UserHandle.myUserId()); on 15+ it used mUserInteractor.getLaunchedAs(). If use_preferred_image_editor exists on the target branch, also check for the companion flag-on change (ag/34038177)."
      ],
      "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_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/modules/IntentResolver",
       "sha": "13c30b464d042f3e00899ffcf1c02b76bc35f769",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48552",
     "osv_id": "ASB-A-365975561",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Wraps the three Settings.Global writes in DevicePolicyManagerService.saveGlobalProxyLocked in a try/catch(Exception), so a SettingsProvider failure while applying a device admin's global proxy no longer propagates and aborts the calling operation (such as removing an ActiveAdmin, per the commit message).",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in saveGlobalProxyLocked. If you port it to a tree that still uses Settings.Global.*, either keep that form or confirm that Global is imported, because the 14/15 hunk renames the constants to Global.* in the same change.",
       "If a fork adds further settings writes in this method, consider whether they belong inside the same try block."
      ],
      "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_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/frameworks/base",
       "sha": "bb6d2f17243ddd4313f826b9ac6119fb40962ee7",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48553",
     "osv_id": "BUL-CVE-2025-48553",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DevicePolicyManagerService.handlePackagesChanged now re-parses the device admin receiver's meta-data (new DeviceAdminInfo) on package change and removes the active admin if the package, receiver or meta-data is missing or malformed; DeviceAdminInfo's constructor additionally converts OutOfMemoryError during meta-data parsing into XmlPullParserException so it is treated as malformed.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit, two files, applied identically on 14/15/16. The new isActiveAdminPackageValid uses unqualified GET_META_DATA, MATCH_DIRECT_BOOT_AWARE, MATCH_DIRECT_BOOT_UNAWARE and ActiveAdmin.getUserHandle(); on older or customized DPMS trees confirm those imports and that method exist, otherwise qualify the constants. Both the DeviceAdminInfo OOM catch and the DPMS change are needed for the OOM case to be handled by catch(Exception)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "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-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/frameworks/base",
       "sha": "660c7075dc00d23a47f8b2018d62c66b8e27c450",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48554",
     "osv_id": "ASB-A-414603411",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DevicePolicyManagerService now re-validates an active admin's device_admin metadata whenever its package changes, by constructing a DeviceAdminInfo from the receiver's ActivityInfo fetched with GET_META_DATA, and removes the admin if the metadata is malformed. DeviceAdminInfo's metadata parser also converts OutOfMemoryError into an XmlPullParserException so it is handled like any other parse failure.",
      "confidence": "medium",
      "backport_notes": [
       "Apply both hunks together. Without the OutOfMemoryError to XmlPullParserException conversion in DeviceAdminInfo, an OOM escapes the catch (Exception e) in isActiveAdminPackageValid.",
       "The helper uses GET_META_DATA, MATCH_DIRECT_BOOT_AWARE and MATCH_DIRECT_BOOT_UNAWARE unqualified, and the diff adds no imports. A customized branch needs the matching static imports in DevicePolicyManagerService, or must qualify the constants."
      ],
      "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_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/frameworks/base",
       "sha": "660c7075dc00d23a47f8b2018d62c66b8e27c450",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48556",
     "osv_id": "BUL-CVE-2025-48556",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "NotificationChannel.setId() and setConversationId() now pass their string arguments through the existing getTrimmedString() helper so mId, mParentId and mConversationId are capped at MAX_TEXT_LENGTH instead of being stored unbounded.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the only dependency is the private getTrimmedString(String) helper and MAX_TEXT_LENGTH constant in NotificationChannel.java, both already present in the 15 code context. The three one-line substitutions are identical on 15 and 16 apart from line offsets.",
       "The new tests use Truth assertThat and Guava Strings.repeat; the existing testLongAlertFields already uses Strings.repeat, and the 15/16 commits add no imports."
      ],
      "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_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/frameworks/base",
       "sha": "fc5d9b6ef5305263d37404fc3d4afe583a15c62b",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48558",
     "osv_id": "ASB-A-380885270",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BatteryService now resolves the implicit ACTION_REQUEST_SHUTDOWN intent itself and pins it to an activity whose owning uid holds android.permission.SHUTDOWN before calling startActivityAsUser, so a non-system app should not be chosen as the shutdown target when a permitted activity resolves.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching one file; the hunk is identical on 14, 15 and 16 with only line offsets differing, so it should apply with fuzz to any branch whose BatteryService still has the two anonymous Runnable blocks calling mContext.startActivityAsUser(intent, UserHandle.CURRENT).",
       "Requires PackageManagerInternal.queryIntentActivities(Intent, String, int, int, int) and ActivityManagerInternal.getCurrentUserId() in the target branch; verify their signatures on older branches."
      ],
      "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_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/frameworks/base",
       "sha": "0d30c78c8953adfc969a8dba8a58a8ea3571908c",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48559",
     "osv_id": "ASB-A-416491779",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppOpsService no longer treats attribution tags of operations proxied for the root UID as valid unless the proxy is null or a system package (via a new isPackageNullOrSystem helper shared with the special-UID branch), and finishProxyOperation now passes the proxy identity into verifyAndGetBypass.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. Apply the ROOT_UID branch change, the new isPackageNullOrSystem helper, and the passing of proxyUid/proxyPackageName from finishProxyOperation through the new finishOperationUnchecked overload together. Without the last piece, finishProxyOperation keeps using the verification path that ignores the proxy. Match the finishOperationUnchecked parameters to the branch: virtualDeviceId / mDeviceAttributedOps (15+) or op.mAttributions (14).",
       "At the latest security tags only 4/8 (15) to 5/8 (14, 16) of the patched region lines match. Check directly that isPackageNullOrSystem exists and that the ROOT_UID branch uses it."
      ],
      "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_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/frameworks/base",
       "sha": "7b88db4928f390cb7656dcc4a14fac2d645301a9",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48560",
     "osv_id": "ASB-A-419110583",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Moves the ACCESSIBILITY_MOTION_EVENT_OBSERVING permission check in AbstractAccessibilityServiceConnection out of the property-setting code that ran under a cleared Binder identity, and into the service-info update method before Binder.clearCallingIdentity(). The check is now evaluated against the calling accessibility service instead of system_server, and requested observed motion event sources are reset to 0 when the caller lacks the permission.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit fix. The diff is identical on 15 and 16 apart from line offsets. The fix drops the Flags.motionEventObserving() gate entirely. Keep the new check before Binder.clearCallingIdentity() in the service-info update method."
      ],
      "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_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/frameworks/base",
       "sha": "580118ec5b41075c21fb0d9d6edcf5c89ebbc799",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48562",
     "osv_id": "ASB-A-423815728",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PrintSpooler now opens the destination URI with mode \"wt\" (write + truncate) in RemotePrintDocument.writeContent when saving a print job to PDF, so overwriting an existing file requests an emptied output stream instead of possibly leaving old content of the previous file in the output.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained one-line change. The hunk context (acquireFile(null), FileInputStream, 8192-byte buffer loop) is identical on 14, 15 and 16, so it should apply cleanly wherever RemotePrintDocument.writeContent still calls the one-argument openOutputStream."
      ],
      "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-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/frameworks/base",
       "sha": "252efec89b01a89b4d394c500e9dae2b6c08dbae",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48563",
     "osv_id": "ASB-A-401545800",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an onNullBinding() override to RemoteFillService that calls unbind(), so an autofill service returning null from onBind() is no longer kept bound by system_server with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained 8-line commit, applied the same way on 14, 15 and 16. Android 13 is listed as affected by the bulletin, but the material includes no Android 13 commit."
      ],
      "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_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/frameworks/base",
       "sha": "a6a570a6f4972c1dfea13c5fe3558805c1658991",
       "tag": "android-security-15.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    }
   ],
   "tags": [
    "android-security-15.0.0_r10"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "383681202"
     ],
     "commits": [
      {
       "project": "platform/build",
       "sha": "d60b9c64ef0b18d300a81cf5d9918781f45a30f3",
       "subject": "Set game_default_frame_rate_override as 60 for AOSP",
       "tag": "android-security-15.0.0_r10",
       "merged": "2025-04-15",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r10"
     ],
     "tag": "android-security-15.0.0_r10"
    },
    {
     "bug_ids": [
      "295549202"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "943cab06b39ebaedec8019b0807da38250415430",
       "subject": "Revert \"Update CtsShim to use sharedUserId\"",
       "tag": "android-security-15.0.0_r10",
       "merged": "2025-07-11",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r10"
     ],
     "tag": "android-security-15.0.0_r10"
    },
    {
     "bug_ids": [
      "399120953"
     ],
     "commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "20465375a1d0cb71cdb891235a9f8a3fba31dbf6",
       "subject": "Don't blur too many layers",
       "tag": "android-security-15.0.0_r10",
       "merged": "2025-07-16",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r10"
     ],
     "tag": "android-security-15.0.0_r10"
    },
    {
     "bug_ids": [
      "315320090",
      "418085016"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "7b61d03c1ed0fa21ad89a49571ffc6ad4ea510ad",
       "subject": "Ensure Gallery always shows as system fixed",
       "tag": "android-security-15.0.0_r10",
       "merged": "2025-07-11",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r10"
     ],
     "tag": "android-security-15.0.0_r10"
    }
   ],
   "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-2023-24023": {
     "cve": "CVE-2023-24023",
     "summary": "Removes the bluffs_mitigation aconfig flag so that four protections in system/stack/btm/btm_sec.cc are always active instead of being gated behind a flag that could be off: the BR/EDR encryption key size downgrade check, the cached session key size update, the Secure Connections support caching and the Secure Connections downgrade disconnect.",
     "component": "platform/packages/modules/Bluetooth, system/stack/btm (BTM security manager, btm_sec.cc)",
     "weakness": "Missing validation of remote security parameters (the stack accepted encryption key size and Secure Connections mode downgrades); the mitigation was disabled behind a feature flag",
     "root_cause": "In system/stack/btm/btm_sec.cc the protections against a remote peer lowering link security were wrapped in com::android::bluetooth::flags::bluffs_mitigation(). With the flag off: (1) read_encryption_key_size_complete_after_encryption_change did not compare the negotiated encryption key size against the cached value for the handle and did not update that cache. (2) btm_sec_encryption_change_evt took an alternate path that skipped the READ_ENCRYPTION_KEY_SIZE request entirely when init_flags set_min_encryption_is_enabled() was set and the controller supports SET_MIN_ENCRYPTION_KEY_SIZE. (3) btm_sec_link_key_notification did not record whether a persistently bonded BR/EDR or dual-mode device supports Secure Connections. (4) btm_sec_set_peer_sec_caps did not disconnect a peer whose reported capabilities indicate a downgrade from Secure Connections mode. The stack therefore accepted links with weaker security than previously recorded.",
     "fix_analysis": [
      "flags/security.aconfig: deletes the bluffs_mitigation flag definition (bug 314331379).",
      "system/stack/btm/btm_sec.cc:read_encryption_key_size_complete_after_encryption_change: always calls btm_sec_is_session_key_size_downgrade(handle, key_size). On a downgrade it logs an error, calls acl_disconnect_from_handle(handle, HCI_ERR_HOST_REJECT_SECURITY, ...) and returns. Otherwise it calls btm_sec_update_session_key_size(handle, key_size) before btm_acl_encrypt_change/btm_sec_encrypt_change. This runs after the existing bad-key-size and btm_sec_get_min_enc_key_size() checks (context lines 3500-3509).",
      "system/stack/btm/btm_sec.cc:btm_sec_encryption_change_evt: removes the flag-off branch. A single condition now picks the path: if status != HCI_SUCCESS, encr_enable == 0, BTM_IsBleConnection(handle), or the controller lacks READ_ENCRYPTION_KEY_SIZE, it calls btm_acl_encrypt_change/btm_sec_encrypt_change directly; otherwise it issues btsnd_hcic_read_encryption_key_size bound to read_encryption_key_size_complete_after_encryption_change. The old flag-off bypass for set_min_encryption_is_enabled() plus SET_MIN_ENCRYPTION_KEY_SIZE support is gone, so the key size is always read when the controller supports it.",
      "system/stack/btm/btm_sec.cc:btm_sec_link_key_notification: drops the flag term, so btm_sec_store_device_sc_support(p_dev_rec->get_br_edr_hci_handle(), p_dev_rec->SupportsSecureConnections()) is always called when p_dev_rec->sec_rec.is_bond_type_persistent() is true and the device is BR/EDR or dual mode.",
      "system/stack/btm/btm_sec.cc:btm_sec_set_peer_sec_caps: always checks btm_sec_is_device_sc_downgrade(hci_handle, sc_supported). On a downgrade it calls acl_set_disconnect_reason(HCI_ERR_HOST_REJECT_SECURITY) and btm_sec_send_hci_disconnect(p_dev_rec, HCI_ERR_AUTH_FAILURE, hci_handle, ...), then returns before remote_feature_received is set."
     ],
     "completeness": [
      "Covers the four flag-gated sites in btm_sec.cc. The mitigation helpers (btm_sec_is_session_key_size_downgrade, btm_sec_update_session_key_size, btm_sec_store_device_sc_support, btm_sec_is_device_sc_downgrade) are not changed and must already be present.",
      "The key size downgrade check only runs when the controller supports READ_ENCRYPTION_KEY_SIZE, status is HCI_SUCCESS, encr_enable is nonzero and the link is not BLE. When READ_ENCRYPTION_KEY_SIZE is unsupported, the code still calls btm_acl_encrypt_change/btm_sec_encrypt_change without any key size comparison.",
      "Secure Connections support is only cached for persistent bonds on BR/EDR or dual-mode devices, so the SC downgrade check in btm_sec_set_peer_sec_caps only has recorded data for such devices.",
      "Per index search (android-15.0.0_r36), btm_sec_encryption_change_evt is also reached from parse_encryption_change_v2/btu_hcif_encryption_change_evt_v2, in a later signature that takes a key_size argument. That version of the function is not part of this diff and its handling is not shown."
     ],
     "behavior_changes": [
      "A BR/EDR link whose negotiated encryption key size is lower than the cached session key size for that handle is now disconnected with HCI_ERR_HOST_REJECT_SECURITY instead of being accepted.",
      "A peer that reports no Secure Connections support when SC support was previously recorded is disconnected (HCI_ERR_AUTH_FAILURE sent, disconnect reason HCI_ERR_HOST_REJECT_SECURITY), and its security capabilities are not recorded.",
      "READ_ENCRYPTION_KEY_SIZE is now always issued after a successful encryption enable on non-BLE links when the controller supports it. The previous bypass tied to set_min_encryption_is_enabled() and SET_MIN_ENCRYPTION_KEY_SIZE is removed.",
      "The session key size is cached on every successful encryption change that goes through the key size read. SC support is cached on link key notification for persistent BR/EDR or dual-mode bonds.",
      "The bluffs_mitigation flag no longer exists, so the behavior cannot be turned off through aconfig."
     ],
     "preconditions": [
      "A remote BR/EDR (non-BLE) peer, and an encryption change event with status HCI_SUCCESS and encryption enabled. Per index search, the event arrives via btu_hcif_encryption_change_evt or parse_encryption_change.",
      "For the key size path: the local controller supports READ_ENCRYPTION_KEY_SIZE, and a session key size has already been cached for the handle.",
      "For the SC downgrade path: the device was bonded persistently as BR/EDR or dual mode, so its SC support was cached via btm_sec_link_key_notification (reached from btu_hcif_link_key_notification_evt or parse_link_key_notification, per index search). The peer later reports capabilities through btm_sec_set_peer_sec_caps.",
      "Before the fix: the bluffs_mitigation flag was off."
     ],
     "per_major": {
      "14": "Adapted fix: a separate commit, b5155f0591f9 (android-security-14.0.0_r12, linked as a related bug), adds the SC support caching in btm_sec_link_key_notification and the SC downgrade disconnect in btm_sec_set_peer_sec_caps without any aconfig flag. The material does not show whether the session key size downgrade check was applied on 14. 6/6 lines of its patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: commit f904fd5ad7ef (android-security-15.0.0_r5) removes the bluffs_mitigation flag and makes all four sites unconditional. At android-security-15.0.0_r17 the code context shows the unconditional key size downgrade check, the cache update and the single-path btm_sec_encryption_change_evt (4/8 lines of the patched region matched).",
      "16": "Not listed by the bulletin (not affected). 2/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not listed by the bulletin (not affected). 2/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This commit only removes a flag. It requires the earlier flagged mitigation (helpers btm_sec_is_session_key_size_downgrade, btm_sec_update_session_key_size, btm_sec_store_device_sc_support, btm_sec_is_device_sc_downgrade and the sec_rec accessors) to already exist. On a tree without them, follow the Android 14 approach (b5155f0591f9) of adding the checks directly without a flag.",
      "Function signatures differ across branches. Per index search at android-15.0.0_r36, btm_sec_encryption_change_evt and btm_sec_encrypt_change take an extra key_size parameter, while the diff and the r17 context use the 3-parameter form. Adapt call sites accordingly."
     ],
     "confidence": "high",
     "unknowns": [
      "How the helper functions (btm_sec_is_session_key_size_downgrade, btm_sec_update_session_key_size, btm_sec_store_device_sc_support, btm_sec_is_device_sc_downgrade) are implemented, and whether the cached values persist across connections, is not shown.",
      "Whether the Android 14 branch also received the encryption key size downgrade part of the mitigation is not shown. Only the SC downgrade commit is provided.",
      "The original flagged commit that introduced the mitigation (bug 314331379) is not provided.",
      "The material does not explain why only 4/8 lines (Android 15) and 2/8 lines (Android 16 and 17) of the patched region match."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-0076": {
     "cve": "CVE-2025-0076",
     "summary": "SystemUI's Quick Access Wallet tile and wallet screen now call Icon.loadDrawable() on wallet card images and card icons only when the Icon type is TYPE_BITMAP or TYPE_ADAPTIVE_BITMAP. Every other Icon type (URI, URI_ADAPTIVE_BITMAP, RESOURCE, DATA) gives a null drawable.",
     "component": "platform/frameworks/base, SystemUI Quick Access Wallet (packages/SystemUI qs/tiles and wallet/ui)",
     "weakness": "Missing restriction on Icon source type: Icons supplied by the wallet app were loaded with SystemUI's Context for non-bitmap types. The bulletin type is ID (information disclosure).",
     "root_cause": "WalletCard objects returned from the wallet service carry Icon objects for the card image and the card icon. In QuickAccessWalletTile.WalletCardRetriever.onWalletCardsRetrieved, the card image was loaded with loadDrawable(mContext) for TYPE_RESOURCE, TYPE_BITMAP, TYPE_ADAPTIVE_BITMAP and TYPE_DATA; only TYPE_URI and TYPE_URI_ADAPTIVE_BITMAP were set to null. In WalletScreenController.QAWalletCardViewInfo, the card image was loaded for every type except TYPE_URI, so TYPE_URI_ADAPTIVE_BITMAP, TYPE_RESOURCE and TYPE_DATA were loaded. The card icon from getCardIcon() was loaded for any type, including URI types. Non-bitmap Icons, such as a content:// URI, were therefore resolved with SystemUI's Context.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java : WalletCardRetriever.onWalletCardsRetrieved: the switch on mSelectedCard.getCardImage().getType() becomes an allow-list. Only TYPE_BITMAP and TYPE_ADAPTIVE_BITMAP call loadDrawable(mContext). TYPE_URI, TYPE_URI_ADAPTIVE_BITMAP, TYPE_RESOURCE and TYPE_DATA now set mCardViewDrawable = null. The default branch is unchanged.",
      "packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java : QAWalletCardViewInfo constructor: the card image is loaded only if cardImageIcon.getType() is Icon.TYPE_BITMAP or Icon.TYPE_ADAPTIVE_BITMAP; otherwise mCardDrawable = null. The card icon from getCardIcon() is loaded only if it is non-null and one of the same two types; otherwise mIconDrawable = null. Before the fix it was loaded for any type.",
      "packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java: adds a @VisibleForTesting getIcon() accessor for mIcon. This is test support only, with no behavior change.",
      "Tests: adds a new multivalentTests QuickAccessWalletTileTest, which includes testQueryCards_invalidDrawable_noSideViewDrawable using a content:// Icon. Adds WalletScreenControllerTest.queryCards_hasCards_showCarousel_invalidIconSource_noIcon, which asserts that the WalletView icon drawable is null for a content URI Icon."
     ],
     "completeness": [
      "Covers the three loadDrawable call sites in the diff: the tile card image, the wallet screen card image and the wallet screen card icon. All three now use the same bitmap-only allow-list.",
      "TYPE_DATA and TYPE_RESOURCE are rejected along with the URI types. The fix does not validate URI authority or individual types; it rejects everything that is not a bitmap.",
      "This diff does not touch other Icon or Drawable inputs from the wallet service. GetWalletCardsError carries an Icon (the new test calls new GetWalletCardsError(CARD_IMAGE, errorMessage)), and QuickAccessWalletClient.getTileIcon() returns a Drawable. How these are produced or loaded is not shown in the material.",
      "The separate packages/apps/QuickAccessWallet plugin (WalletPanelViewController, seen in index test results) also consumes WalletCard objects but is outside this commit. Its handling is not shown."
     ],
     "behavior_changes": [
      "In the QS Wallet tile, wallet cards whose card image Icon is TYPE_RESOURCE, TYPE_DATA, TYPE_URI or TYPE_URI_ADAPTIVE_BITMAP no longer show a side view drawable (sideViewCustomDrawable is null).",
      "In the wallet screen, a card image of any non-bitmap type gives a null card drawable, and a card icon of any non-bitmap type gives a null icon drawable. The new test asserts that the WalletView icon is empty.",
      "The changed branches add no new errors or logs; unsupported types fall back to null drawables without any message."
     ],
     "preconditions": [
      "A wallet service must return WalletCards through QuickAccessWalletController.queryWalletCards.",
      "Tile path: the QS Wallet tile is listening (handleSetListening(true) leads to queryWalletCards and then WalletCardRetriever.onWalletCardsRetrieved), the card is a payment card (getPaymentCards), and it is at the selected index.",
      "Wallet screen path: a QAWalletCardViewInfo is constructed for a card (the code comment says the constructor runs on a background executor), including the optional getCardIcon().",
      "The wallet app supplies an Icon of a non-bitmap type for the card image or the card icon."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin, but the material contains no Android 13 commit; status unknown.",
      "14": "Same fix: platform/frameworks/base cac7c51cd276 in android-security-14.0.0_r18. The diff matches the 15 commit except for hunk line offsets. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Fix commit d6ad7f34eaf5 in android-security-15.0.0_r6 (cherry-pick of internal eb5a9b8a / 398d07d3, Change-Id I0033f6ba). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin (not listed). 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 present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. The production change is two small hunks (the QuickAccessWalletTile switch and the QAWalletCardViewInfo constructor) plus a test-only accessor in WalletView.",
      "The new QuickAccessWalletTileTest assumes a particular QuickAccessWalletTile constructor signature (QsEventLogger, SecureSettings, QuickAccessWalletController). On customized trees, adjust the test, not the fix."
     ],
     "confidence": "high",
     "unknowns": [
      "What content the wallet app could cause SystemUI to read or display (the bulletin only states type ID, severity High).",
      "Whether an Android 13 patch exists and what it looks like; the material contains none.",
      "How GetWalletCardsError icons and QuickAccessWalletClient.getTileIcon() drawables are loaded, and whether the packages/apps/QuickAccessWallet plugin has a similar loadDrawable path.",
      "Which code constructs QAWalletCardViewInfo and how the wallet service app is selected."
     ],
     "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-0089": {
     "cve": "CVE-2025-0089",
     "summary": "Adds home-intent normalization to ActivityStartInterceptor. An ACTION_MAIN intent with CATEGORY_HOME/CATEGORY_SECONDARY_HOME in a non-standard form (extra categories, data/type, or an explicit default-home component) is rewritten into a standard home intent, re-resolved and launched with FLAG_ACTIVITY_NEW_TASK instead of being started as a standard-type activity. A companion commit makes the AMS mStrictModeCallbacks map self-locked.",
     "component": "platform/frameworks/base, WindowManager activity start (services/core/java/com/android/server/wm/ActivityStartInterceptor.java, ActivityStarter.java); companion change in ActivityManagerService (am)",
     "weakness": "Improper input validation / inconsistent intent classification: per the commit message, a home intent in an incorrect format could start a home activity as a standard-type activity",
     "root_cause": "Before the fix, interceptHomeIfNeeded (on 15) returned early for any intent that failed ActivityRecord.isHomeIntent. ActivityStartInterceptor.intercept also did not know whether the caller had set the component explicitly (there was no componentSpecified parameter). An ACTION_MAIN + CATEGORY_HOME intent in a non-standard form (for example with an extra category such as LAUNCHER, which fails isHomeIntent per the new test) was therefore not normalized. Per the commit message, this allowed 'home activities being started as a standard type activity due to incorrect home intent format'. The existing interceptor comment notes that activities cannot change type ('a standard activity cannot become a home activity').",
     "fix_analysis": [
      "ActivityStarter.java (mInterceptor.intercept call site, hunk at line 1201): passes request.componentSpecified into mInterceptor.intercept.",
      "ActivityStartInterceptor.java:intercept: new boolean componentSpecified parameter, stored in mComponentSpecified. An overload without it (which passes false) is kept for tests. The comment at the interceptHomeIfNeeded call site is extended to cover 'the home intent is not in the correct format'.",
      "ActivityStartInterceptor.java:interceptHomeIfNeeded: the existing null guards on mPresumableLaunchDisplayArea and mRootWindowContainer are kept. Any intent with ACTION_MAIN and a HOME or SECONDARY_HOME category is now a candidate (the early return on !ActivityRecord.isHomeIntent is gone). If mComponentSpecified, it resolves the default home through mService.getHomeIntent() and mRootWindowContainer.resolveHomeActivity(mUserId, homeIntent), and returns false if the explicit component is not the default home. If !isHomeIntent || mComponentSpecified, it calls normalizeHomeIntent() and marks the start as intercepted. The secondary-home replacement moves into replaceToSecondaryHomeIntentIfNeeded() and is OR-ed into the result. When intercepted: calling pid/uid become the real calling pid/uid, mResolvedType is set to null, and mRInfo/mAInfo are re-resolved. This block moved from the secondary-home path into the common path.",
      "ActivityStartInterceptor.java:normalizeHomeIntent (new): if there is more than one category, removes all of them and adds back only CATEGORY_SECONDARY_HOME (if it was present) or CATEGORY_HOME. If type or data is set, calls setType(null). If mComponentSpecified, calls setComponent(null). Always adds FLAG_ACTIVITY_NEW_TASK and logs with Slog.",
      "ActivityStartInterceptorTest.java: the new testInterceptIncorrectHomeIntent checks that an intent with HOME + LAUNCHER categories is intercepted and then passes ActivityRecord.isHomeIntent.",
      "Companion commit f27918b39cff, ActivityManagerService.java: registerStrictModeCallback drops the synchronized modifier. It, its linkToDeath callback and triggerUnsafeIntentStrictMode now lock on mStrictModeCallbacks (@GuardedBy(\"mStrictModeCallbacks\")) instead of the AMS lock. The commit message says the AMS lock cannot be used because 'it's potentially called from many places with other locks held'. Per index search, triggerUnsafeIntentStrictMode is reached from SaferIntentUtils.reportUnsafeIntentEvent. The commit is linked to this CVE only by the shared bug 378505461."
     ],
     "completeness": [
      "Covers ACTION_MAIN home/secondary-home intents reaching ActivityStartInterceptor.intercept from ActivityStarter that have extra categories, data/type, or an explicit component equal to the default home activity.",
      "On Android 15, interceptHomeIfNeeded still returns false when mPresumableLaunchDisplayArea or mRootWindowContainer is null. The Android 14 variant only checks mRootWindowContainer.",
      "If an explicit component is set and it is not the default home component, the function returns false and does not normalize the intent.",
      "normalizeHomeIntent does not touch extras, other flags, the selector or clip data.",
      "The aInfo returned by resolveHomeActivity is dereferenced without a null check in the diff.",
      "The new test only covers the extra-category case.",
      "The commit message lists Flag: com.android.window.flags.normalize_home_intent, but the diff shows no runtime flag check."
     ],
     "behavior_changes": [
      "Non-standard home intents are rewritten in place: extra categories removed, data/type cleared, an explicit default-home component cleared, and FLAG_ACTIVITY_NEW_TASK added. Slog warning and debug messages are logged.",
      "Such starts are treated as intercepted: calling pid/uid become the real caller's, resolvedType is set to null, rInfo/aInfo are re-resolved, and (per the ActivityStarter context) intentGrants is set to null.",
      "The secondary-home replacement now re-resolves in the common intercepted path.",
      "registerStrictModeCallback no longer takes the AMS lock; access to mStrictModeCallbacks is serialized on the SparseArray itself."
     ],
     "preconditions": [
      "An activity start that goes through ActivityStartInterceptor.intercept from ActivityStarter.",
      "The intent action is ACTION_MAIN with CATEGORY_HOME or CATEGORY_SECONDARY_HOME, and the intent either fails ActivityRecord.isHomeIntent or explicitly names the default home component.",
      "On Android 15: computeSuggestedLaunchDisplayArea returned a non-null TaskDisplayArea and mRootWindowContainer is non-null."
     ],
     "per_major": {
      "14": "adapted fix: ee0d85dd3f50 (RESTRICT AUTOMERGE) in android-security-14.0.0_r20. Adds interceptHomeIfNeeded fresh, without the secondary-home replacement or the mPresumableLaunchDisplayArea guard. Makes ActivityRecord.isHomeIntent static, adds a TAG constant and the Slog import, and passes componentSpecified through intercept and ActivityStarter. 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: ed39b7c3895c in android-security-15.0.0_r8, plus companion f27918b39cff in android-security-15.0.0_r10 (resolved there per the per-major facts). 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin); 7/8 lines of the patched region are 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": [
      "For trees without interceptHomeIfNeeded, follow the Android 14 variant: add interceptHomeIfNeeded and normalizeHomeIntent, make ActivityRecord.isHomeIntent static, add TAG/Slog, and pass request.componentSpecified through intercept() and the ActivityStarter call. Keep the overload without componentSpecified for existing tests.",
      "The mStrictModeCallbacks lock change is a separate commit, linked only by bug id. Apply it if registerStrictModeCallback and triggerUnsafeIntentStrictMode use the AMS lock on the target branch."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete security consequence of a home activity being launched as a standard-type activity, beyond the commit message and the EoP/High rating.",
      "How the mStrictModeCallbacks locking commit relates to the home-intent issue.",
      "Whether com.android.window.flags.normalize_home_intent gates the behavior at runtime on any branch.",
      "Whether Android 13 (listed as affected) received a fix; no Android 13 commit is in the material.",
      "The bulletin affected list includes '16-next' while the per-major facts mark Android 16 as not listed.",
      "The full set of conditions checked by ActivityRecord.isHomeIntent (the material shows them only partially)."
     ],
     "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-26454": {
     "cve": "CVE-2025-26454",
     "summary": "ManagedProvisioning's DisclaimersParserImpl now validates each EXTRA_PROVISIONING_DISCLAIMER_CONTENT URI before copying the URI's content into its own disclaimer file cache, and skips disclaimers that fail validation. The scheme must be content: or android.resource:, and for content: URIs the calling pid/uid must hold read permission via checkUriPermission.",
     "component": "platform/packages/apps/ManagedProvisioning, disclaimers parser (src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java)",
     "weakness": "Missing permission/authorization check on caller-supplied URI (confused deputy)",
     "root_cause": "DisclaimersParserImpl.parse took each disclaimer content Uri from the provisioning disclaimers extras and passed it to saveDisclaimerContentIntoFile, which saves the URI content into a file in ManagedProvisioning's disclaimer cache. Before the fix there was no check of the URI scheme and no check that the caller itself had read access to that URI. The commit message describes this as a confused deputy in termsActivity for the terms_disclaimer Uri.",
     "fix_analysis": [
      "Commit d33d045407c5 (android-security-15.0.0_r9), DisclaimersParserImpl.java: adds the constants SCHEME_ANDROID_RESOURCE (\"android.resource\") and SCHEME_CONTENT (\"content\") and a new private method validateUriSchemeAndPermission(Uri). The method throws a SecurityException for any scheme other than those two. It then calls mContext.checkUriPermission(uri, Binder.getCallingPid(), Binder.getCallingUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION) and throws a SecurityException if the result is not PackageManager.PERMISSION_GRANTED.",
      "Same commit, DisclaimersParserImpl.parse: after the null-URI check and before saveDisclaimerContentIntoFile(uri, i), the call to validateUriSchemeAndPermission(uri) is wrapped in try/catch(SecurityException). On failure it logs via ProvisionLogger.loge and continues, so that disclaimer is dropped and the rest are still processed.",
      "Commit 03cadb65c0b6 (android-security-15.0.0_r10), DisclaimersParserImpl.validateUriSchemeAndPermission: wraps the checkUriPermission block in `if (Objects.equals(scheme, SCHEME_CONTENT))`, so the read-permission check applies only to content: URIs. android.resource: URIs pass on the scheme check alone. The commit message cites DevicePolicyManager documentation saying FLAG_GRANT_READ_URI_PERMISSION is only required for content: URIs."
     ],
     "completeness": [
      "Covers the only URI consumer visible in the material: the per-disclaimer path in DisclaimersParserImpl.parse that feeds saveDisclaimerContentIntoFile. Both the scheme allowlist and the content: permission check sit in front of that call.",
      "The permission check uses Binder.getCallingPid()/Binder.getCallingUid(). Outside an incoming Binder transaction these return the current process's own pid/uid (general platform knowledge). The material does not show the call site of parse, so whether the check attributes to the right caller on every entry path cannot be confirmed from the code shown.",
      "After the second commit, android.resource: URIs are accepted without any permission check. For them the fix relies on the scheme allowlist alone.",
      "Only DisclaimersParserImpl is changed. No other ManagedProvisioning code that reads caller-supplied URIs appears in the material, so nothing can be said about sibling paths."
     ],
     "behavior_changes": [
      "Disclaimer content URIs with a scheme other than content: or android.resource: are now rejected. The SecurityException is caught internally, logged with ProvisionLogger.loge, and the disclaimer is skipped.",
      "content: disclaimer URIs for which the calling pid/uid lacks read permission are now skipped instead of being copied into the disclaimer cache.",
      "Invalid disclaimers are dropped one at a time, and parse returns null only if no disclaimer remains.",
      "New debug log line \"validateUriSchemeAndPermission: <uri>\" for each disclaimer URI."
     ],
     "preconditions": [
      "A provisioning flow handled by ManagedProvisioning reaches DisclaimersParserImpl.parse with caller-controlled disclaimer extras containing EXTRA_PROVISIONING_DISCLAIMER_CONTENT URIs. The commit message names termsActivity and the terms_disclaimer Uri."
     ],
     "per_major": {
      "14": "same fix: first commit c341743982f0 (identical diff to d33d045407c5) resolved at android-security-14.0.0_r21. At android-security-14.0.0_r29, 8/8 lines of the patched region are present. The material does not show an Android 14 commit for the second (content:-only) change, or which commit's lines the count measures.",
      "15": "two commits: d33d045407c5 (android-security-15.0.0_r9) adds the validation, and 03cadb65c0b6 (android-security-15.0.0_r10) restricts the permission check to content: URIs. 8/8 patched lines are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). The patched code (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched code (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply both commits in order (Change-Id I4c5ab64c... first, then Ie19576c1...). The second is a one-hunk edit inside validateUriSchemeAndPermission, which the first commit introduces. The second commit's message indicates that, without it, android.resource: URIs are also subjected to the read-permission check.",
      "The change is confined to DisclaimersParserImpl.java (new imports: Intent, PackageManager, Binder, Objects)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Where DisclaimersParserImpl.parse is invoked from (activity or binder path), and therefore whether Binder.getCallingPid()/getCallingUid() reflect the originating app at the point of the check.",
      "Whether the second (content:-only) commit landed on Android 14: no commit is shown, and the line-presence count does not identify which commit's lines it measures.",
      "Whether Android 13 received the same two commits: the bulletin lists 13 as affected, but no commit for it is in the material.",
      "The implementation of saveDisclaimerContentIntoFile, and so exactly how the URI content is read."
     ],
     "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-26464": {
     "cve": "CVE-2025-26464",
     "summary": "Replaces the whole body of the hidden binder method AppSearchManagerService.executeAppFunction (AppSearch module, Android 15) with an unconditional UnsupportedOperationException. This disables the never-launched, flag-guarded app-function execution path in which, according to the commit, a security issue was reported.",
     "component": "platform/packages/modules/AppSearch, AppSearchManagerService (system_server AppSearch service, app-function execution path)",
     "weakness": "unknown (the diff removes the entire code path instead of correcting a specific check; the commit message only says \"a security issue reported in this code path\" and that the hidden API could be invoked despite the feature flag)",
     "root_cause": "AppSearchManagerService exposed a binder method executeAppFunction(ExecuteAppFunctionAidlRequest, IAppSearchResultCallback). It verified the caller (verifyIncomingCallWithCallback, checkCallDenied, isUserOrganizationManaged, verifyExecuteAppFunctionCaller) and then dispatched executeAppFunctionUnchecked, which builds an AppFunctionService intent for the target package and resolves the service. The API was supposed to stay unreachable behind a feature flag that never launched. According to the commit, an attacker could still invoke the hidden API. The commit states that a security issue was reported in this path (EoP per the bulletin). The material does not identify the exact defect in the removed logic.",
     "fix_analysis": [
      "service/java/com/android/server/appsearch/AppSearchManagerService.java:executeAppFunction (binder stub): the commit deletes the 76-line body and replaces it with a single `throw new UnsupportedOperationException(\"Moved to AppFunctionManager.\")`. The deleted body held the null checks, caller verification, the CallStats logging callback, the organization-managed check, target package validation, verifyExecuteAppFunctionCaller, the executeLambdaForUserAsync -> executeAppFunctionUnchecked dispatch and rate-limit logging.",
      "The method now throws before it touches the request, so this binder entry point can no longer reach executeAppFunctionUnchecked. As a result, the service no longer resolves an AppFunctionService on a caller's behalf through this path.",
      "The new comment gives the reasons: the flag never launched, a hidden-API path to the method existed, and since Android 16 the API lives in AppFunctionManager."
     ],
     "completeness": [
      "Disables the executeAppFunction binder entry point in AppSearchManagerService unconditionally, whatever the flag state, caller identity or user.",
      "executeAppFunctionUnchecked is still in the file at android-security-15.0.0_r17 (lines 2305-2320); the fix does not delete it. The diff shows no other caller of it, but the material does not prove that none exists.",
      "The fix removes the path instead of correcting it. Other code with similar logic, such as frameworks/base AppFunctionManagerServiceImpl.executeAppFunctionInternal (per index search), is not touched by this commit. The material does not show whether that code shares the defect.",
      "The commit changes only AppSearchManagerService.java; client-side AppSearch classes are not modified by it."
     ],
     "behavior_changes": [
      "Any call to executeAppFunction now throws UnsupportedOperationException in the service. The IAppSearchResultCallback is never invoked. Before the fix, failures were reported through the callback as AppSearchResult codes (RESULT_SECURITY_ERROR, RESULT_INVALID_ARGUMENT, RESULT_RATE_LIMITED).",
      "The material does not show whether the calling process receives the exception; that depends on whether the AIDL method is oneway.",
      "This method no longer logs a CallStats entry with CALL_TYPE_EXECUTE_APP_FUNCTION, and caller verification and rate limiting for this call type no longer run.",
      "No AppFunctionService is resolved through this path any more."
     ],
     "preconditions": [
      "Android 15 device whose AppSearch module still has the pre-fix executeAppFunction body.",
      "A caller able to invoke the hidden executeAppFunction binder method (the commit states this was possible despite the unlaunched feature flag).",
      "Before the fix, the caller would also have had to get past verifyIncomingCallWithCallback, checkCallDenied, the organization-managed-user check and verifyExecuteAppFunctionCaller. The material does not show what in those checks was insufficient."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. 5/8 lines of the patched region are present at android-security-14.0.0_r29. The material does not show whether the executeAppFunction method exists in the Android 14 source.",
      "15": "Same fix: commit e272ad005292 in android-security-15.0.0_r9 (cherry-pick of b1d2fcc8b39b, Change-Id Ia5bf6a6133aac5f83c5ec062ccd25662bd29a8b0). All 8/8 lines of the throwing stub are present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin. 6/8 lines of the patched region are present at android-security-16.0.0_r8. The commit says the code was already removed in main and that the API moved to AppFunctionManager in Android 16. The material does not show which lines match on this branch.",
      "17": "Not affected per the bulletin. 6/8 lines of the patched region are present at android-security-17.0.0_r1. The material does not show which lines match."
     },
     "backport_notes": [
      "Single-file, self-contained change: replace the body of the executeAppFunction binder override in AppSearchManagerService.java with the throw. executeAppFunctionUnchecked stays in the file.",
      "Android 15 forks should confirm the method still exists in their AppSearchManagerService. AppFunctionManagerServiceImpl in frameworks/base (present in Android 15 per index search) is a separate service and this commit does not cover it."
     ],
     "confidence": "medium",
     "unknowns": [
      "The specific defect in the removed verification/dispatch logic that made up the EoP; the commit only says a security issue was reported in the code path.",
      "Whether executeAppFunctionUnchecked or verifyExecuteAppFunctionCaller still have callers, or still exist, after the fix.",
      "Whether the IAppSearchManager.executeAppFunction AIDL method is oneway, which decides whether callers see the exception.",
      "Whether the client-side framework AppSearch classes were changed in a separate commit for Android 15.",
      "Whether the separate AppFunctionManagerServiceImpl path in frameworks/base (per index search) has any of the same logic or defect.",
      "Which lines of the patched region match on Android 14, 16 and 17."
     ],
     "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-32321": {
     "cve": "CVE-2025-32321",
     "summary": "AccountTypePreferenceLoader.isSafeIntent in Settings now pins the validated intent to the resolved authenticator-owned component with intent.setComponent(). The activity that gets launched is then the one whose uid was checked, not whatever an implicit intent resolves to later.",
     "component": "platform/packages/apps/Settings, accounts (AccountTypePreferenceLoader)",
     "weakness": "Insufficient intent validation: the activity resolved during the safety check can differ from the activity launched from the implicit intent (intent redirection from a privileged app)",
     "root_cause": "isSafeIntent(PackageManager, Intent, String accountType) resolved the authenticator-supplied intent with pm.resolveActivityAsUser(intent, 0, userId). It returned true if the resolved activity's uid equalled the authenticator package's uid. The intent itself was not changed, so if it was implicit (no component), the check only covered one possible resolution. Settings could then start the original intent, which might resolve to an activity that was never validated. The commit message frames this as preventing arbitrary intents from being launched from Settings.",
     "fix_analysis": [
      "src/com/android/settings/accounts/AccountTypePreferenceLoader.java:isSafeIntent: the single line `return resolvedAppInfo.uid == authenticatorAppInf.uid;` is replaced by an if/else. When the uids match, `intent.setComponent(resolvedActivityInfo.getComponentName())` is called on the passed-in Intent before returning true. This makes the intent explicit to the exact activity that passed the uid check. Otherwise the method returns false, as before.",
      "The content-scheme rejection, the resolveInfo == null rejection and the NameNotFoundException handling are unchanged. The fix only adds the component pinning in the success path."
     ],
     "completeness": [
      "Covers the only intent-launch gate in this class that the material shows. Every intent that passes isSafeIntent is now explicit and bound to the resolved component.",
      "The fix relies on the caller launching the same Intent object that was mutated. The caller is not in the material, so the code shown cannot confirm this.",
      "Resolution still uses pm.resolveActivityAsUser(intent, 0, ...) with flags 0. The fix does not change which activity is chosen, only that the chosen one is the one launched.",
      "No other call sites or similar authenticator-intent launch paths appear in the material. The diff touches a single method."
     ],
     "behavior_changes": [
      "Intents from account authenticator preferences that pass the uid check become explicit intents (component set to the resolved activity) before they are returned as safe. Alternative resolution for such intents no longer applies.",
      "No new error paths or log messages. The false and exception branches behave the same as before."
     ],
     "preconditions": [
      "An installed account authenticator whose account type preferences supply an Intent that Settings validates through AccountTypePreferenceLoader.isSafeIntent.",
      "The intent must not use the content scheme. It must resolve (via resolveActivityAsUser) to an activity whose application uid matches the authenticator package's uid at check time.",
      "A user interacting with that account type's settings in the Settings app, so the preference intent is validated and launched. The exact caller is not shown in the material."
     ],
     "per_major": {
      "14": "Same fix: platform/packages/apps/Settings fd19bf11ef08 in android-security-14.0.0_r22. The hunk content is identical; the hunk offset differs by one line. At android-security-14.0.0_r29, 4 of the 5 lines of the patched region are present.",
      "15": "Same fix: platform/packages/apps/Settings bb6e84fd04fc in android-security-15.0.0_r10, a cherry-pick of 6a896b6b26d4. At android-security-15.0.0_r17, 4 of the 5 lines of the patched region are present (the code context shows the setComponent branch).",
      "16": "Same fix: platform/packages/apps/Settings e985e294f15d in android-security-16.0.0_r1, identical diff. At android-security-16.0.0_r8, 4 of the 5 lines of the patched region are present.",
      "17": "Not listed by the bulletin (not affected per the bulletin). At android-security-17.0.0_r1, 4 of the 5 lines of the patched region are present."
     },
     "backport_notes": [
      "Single self-contained hunk in isSafeIntent with no dependencies shown. The Android 14 and 15/16 hunk contents are identical apart from a one-line offset, so it should apply to any branch that has the `return resolvedAppInfo.uid == authenticatorAppInf.uid;` form. Settings is a platform app, not a mainline module, so the fix ships with the platform build (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The caller of isSafeIntent and how the mutated Intent is then started are not in the material.",
      "The commit and bulletin do not describe how the launched activity could differ from the resolved one.",
      "Whether a regression test comes with the change is not shown.",
      "Which line of the patched region the per-major checks did not find (4 of 5 lines present on every branch) 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-32323": {
     "cve": "CVE-2025-32323",
     "summary": "DocumentsUI now passes the calling app's label through TextUtils.makeSafeForPresentation (500 char cap, trim, single line) in Shared.getCallingAppName and wraps the app name in quotation marks in the open_tree_dialog_message string. This limits how an application label can affect the ACTION_OPEN_DOCUMENT_TREE confirmation dialog text.",
     "component": "platform/packages/apps/DocumentsUI, picker / open document tree confirmation (Shared.getCallingAppName, ConfirmFragment dialog strings)",
     "weakness": "Missing sanitization of app-controlled display text (application label) shown in a folder access confirmation dialog (improper neutralization of untrusted text / UI spoofing)",
     "root_cause": "Shared.getCallingAppName(Activity) in src/com/android/documentsui/base/Shared.java returned pm.getApplicationLabel(ai).toString() without any change. The label comes from the calling package, so it could be very long and could contain newlines (the new test covers both cases). Per the commit message, the app name is presented in ConfirmFragment, and the open_tree_dialog_message string inserted it inline with no delimiters. The commit says the label could therefore read as a continuation of the dialog's own text.",
     "fix_analysis": [
      "src/com/android/documentsui/base/Shared.java:getCallingAppName: the return type changes from String to CharSequence. A non-empty label is now returned as TextUtils.makeSafeForPresentation(result.toString(), 500, 0, SAFE_STRING_FLAG_TRIM | SAFE_STRING_FLAG_SINGLE_LINE), so it is capped at 500 characters, trimmed and made single-line. The new test expects \"label\\nlabel\" to become \"label label\" and a 600-character label to be cut to 500. An empty label still returns R.string.anonymous_application. Static imports for the two SAFE_STRING_FLAG_* constants are added.",
      "res/values/strings.xml:open_tree_dialog_message: the %1$s appName placeholder is now wrapped in double quotes, so the label is visibly set apart from the dialog's own sentence.",
      "src/com/android/documentsui/dirlist/DocumentsAdapter.java:Environment, src/com/android/documentsui/dirlist/DirectoryFragment.java:AdapterEnvironment.getCallingAppName, tests/common/.../TestEnvironment.java:getCallingAppName: the unused Environment.getCallingAppName() interface method and its two implementations are removed. Per the commit message this is cleanup to avoid false positives when looking for callers of Shared.getCallingAppName.",
      "tests/unit/com/android/documentsui/picker/ApplicationNameTest.kt: a new parameterized unit test covers a normal label, newline stripping, truncation to 500 characters, and an empty label falling back to the anonymous name."
     ],
     "completeness": [
      "All sanitization is done in Shared.getCallingAppName, so every consumer of that helper receives the capped, single-line label. The commit names ConfirmFragment; the material does not list other callers.",
      "Only open_tree_dialog_message gains quotation marks. open_tree_dialog_title still embeds the appName placeholder without delimiters. Its CHAR_LIMIT=80 annotation is a translation hint, not enforcement (general platform knowledge).",
      "The cap is 500 characters with ellipsizeDip=0, and the test shows a long label being cut, not ellipsized. A single-line label of up to 500 characters still reaches the dialog.",
      "The second placeholder (%2$s, directory) is not changed by this fix."
     ],
     "behavior_changes": [
      "Shared.getCallingAppName now returns CharSequence instead of String. Any downstream caller that assigns the result to String no longer compiles.",
      "App labels longer than 500 characters are truncated, and labels are trimmed and flattened to a single line before display.",
      "The default (English) open_tree_dialog_message text now shows the app name in double quotes."
     ],
     "preconditions": [
      "A calling app with a long or multi-line label requests folder access through ACTION_OPEN_DOCUMENT_TREE, and the user reaches the DocumentsUI confirmation dialog (per the commit message and the test's doc comment).",
      "The calling package name must be non-empty and the label non-empty; otherwise the anonymous name is returned."
     ],
     "per_major": {
      "14": "same fix: 1259beab2b8f in android-security-14.0.0_r22. The changed lines match the 15 commit; only the strings.xml and DirectoryFragment line offsets differ. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "same fix: f0336ada165f in android-security-15.0.0_r10 (cherry-picked from internal c8ef2db3 / 4fe6c0b5). At android-security-15.0.0_r17 the Shared.java imports and the quoted string are present, and the Environment method is absent (7/8 lines of the patched region present).",
      "16": "same fix: 35a75b245508 in android-security-16.0.0_r1. The changed lines are identical; only the surrounding context in Shared.java (RESTRICT_STORAGE_ACCESS_FRAMEWORK import) and the line numbers differ. 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, 4/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single self-contained commit. Update any vendor callers of Shared.getCallingAppName that expect a String (it now returns CharSequence). Removing DocumentsAdapter.Environment.getCallingAppName is cleanup and must be done together with all of its implementations.",
      "The new test is written in Kotlin with Mockito. If the fork's unit test target lacks Kotlin support, the test can be left out without affecting the fix. TextUtils.makeSafeForPresentation and the SAFE_STRING_FLAG_* constants are public API since API 29 (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The full list of callers of Shared.getCallingAppName and how ConfirmFragment fills open_tree_dialog_title/message (that file is not shown).",
      "Why the bulletin rates this as High EoP; the material shows only the UI-text sanitization, not the concrete impact.",
      "Whether translated versions of open_tree_dialog_message are updated.",
      "The state of the equivalent code on Android 17 (4/8 lines of the patched region present)."
     ],
     "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-32324": {
     "cve": "CVE-2025-32324",
     "summary": "Moves the runStartActivity(pw) call of the `am start-in-vsync` shell command out of the Choreographer frame callback (posted via the system_server UI handler) back onto the shell command's own thread, so the activity is started with the shell caller's pid/uid.",
     "component": "platform/frameworks/base, ActivityManagerService shell command (services/core/java/com/android/server/am/ActivityManagerShellCommand.java)",
     "weakness": "Operation executed under the wrong caller identity: a shell-initiated activity start was performed from a system_server internal thread, so the calling pid/uid seen by the start path was not the shell's.",
     "root_cause": "In ActivityManagerShellCommand.onCommand, the \"start-in-vsync\" case used mInternal.mUiHandler.runWithScissors to post a lambda that registered a Choreographer frame callback, and runStartActivity(pw) was called inside that frame callback. The callback runs on the UI handler thread rather than the thread handling the shell command, so the activity start ran with an incorrect pid/uid (commit title: \"Fix starting the activity with incorrect pid/uid\"). The result was passed back through a startResult[] array and the ProgressWaiter.",
     "fix_analysis": [
      "services/core/java/com/android/server/am/ActivityManagerShellCommand.java:onCommand, case \"start-in-vsync\": the frame callback body is reduced to waiter.onFinished(0, null). The runStartActivity(pw) call, the startResult[] array and the try/catch that printed \"Error: unable to start activity\" are removed from the callback.",
      "After waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS) returns, onCommand now calls and returns runStartActivity(pw) directly on the shell command thread, as the plain \"start\"/\"start-activity\" case does.",
      "The vsync synchronisation remains only as a wait: the command blocks until the next frame callback fires (or the timeout elapses) and then starts the activity."
     ],
     "completeness": [
      "The fix covers the only place in the diff where runStartActivity was executed off the calling thread (the start-in-vsync frame callback).",
      "Other onCommand cases shown in the context (start, start-activity, startservice, broadcast, etc.) already call their run* methods directly on the command thread and are not changed.",
      "The frame callback and the runWithScissors posting to mUiHandler remain. The fix changes only what runs inside the callback."
     ],
     "behavior_changes": [
      "start-in-vsync now starts the activity on the shell command thread after the vsync wait, so the start is attributed to the shell caller's pid/uid.",
      "The in-callback try/catch is gone. Exceptions from runStartActivity are now handled the same way as for the plain \"start\" command. Previously such an exception was printed as \"Error: unable to start activity, ...\", waiter.onFinished was not called, and the command waited out the timeout and returned -1.",
      "If the frame callback does not fire before the timeout, runStartActivity now still runs after the wait ends. Previously the command returned -1, while the posted callback could still start the activity later.",
      "The activity start now happens after the frame callback rather than within it."
     ],
     "preconditions": [
      "Ability to invoke the `am start-in-vsync` ActivityManager shell command.",
      "The start-in-vsync case must exist in ActivityManagerShellCommand.onCommand. Per-major facts (1/8 patched-region lines at android-security-14.0.0_r29) suggest it is likely absent on Android 14."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. Only 1/8 lines of the patched region are present at android-security-14.0.0_r29, which suggests the start-in-vsync code is likely absent there.",
      "15": "Same fix: commit 0fb2788dac39 in android-security-15.0.0_r10 (cherry-picked from fca9c788b336 / b04474764693). The fixed code is shown at android-security-15.0.0_r17 (5/8 patched-region lines present).",
      "16": "Same fix: commit 55361a549fc2 in android-security-16.0.0_r1 with an identical diff (different line offset, @@ -250). 5/8 patched-region lines present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (Android 17 is not listed). 5/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-file, self-contained change. The only prerequisite is that the branch contains the start-in-vsync case with ProgressWaiter, mUiHandler.runWithScissors and Choreographer.postFrameCallback. If that case is absent (likely on Android 14), there is nothing to backport."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which identity checks in the activity start path were affected by the wrong pid/uid, nor the exact impact behind the High EoP rating.",
      "The material does not show the commit that introduced start-in-vsync or in which release it first appeared.",
      "The material does not directly confirm that the start-in-vsync code is absent on Android 14. Only the 1/8 line match is given."
     ],
     "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-32325": {
     "cve": "CVE-2025-32325",
     "summary": "Hardens Parcel::appendFrom in libbinder: it now refuses to append over existing objects in the destination, copies and acquires only the source objects that lie fully inside the requested [offset, offset+len) range, clears the mObjectsSorted flag in more cases, and keeps the RPC mObjectPositions vector sorted.",
     "component": "platform/frameworks/native, libs/binder (Parcel)",
     "weakness": "Missing validation that keeps the Parcel object table consistent with the data buffer (object metadata desync)",
     "root_cause": "Parcel::appendFrom in libs/binder/Parcel.cpp had three consistency gaps. (1) It never checked whether the destination range starting at mDataPos with length len overlapped objects already recorded in the destination. Plain bytes from the source could therefore be written over an object while the object table still pointed at that position. (2) To select source objects, it recorded firstIndex/lastIndex of the in-range objects, set numObjects = lastIndex - firstIndex + 1, and then appended and acquire_object()'d every index between them. If the source mObjects table was not sorted, objects between those indices that lay outside the range were also copied and acquired, at positions computed as objects[i] - offset + startPos. (3) kernelFields->mObjectsSorted was not cleared after appendFrom added objects, or when the object-write path at restart_write recorded an object at a position other than the data end. The flag could therefore stay set when the order was no longer sorted. For RPC parcels, appendFrom and writeFileDescriptor added to rpcFields->mObjectPositions with push_back, so the vector could become unsorted.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::appendFrom: before growing data, calls validateReadData(mDataPos + len) and returns its status on failure. This rejects appends whose destination range covers an existing object; the new test expects PERMISSION_DENIED.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (kernel path): replaces the firstIndex/lastIndex counting with a direct count of objects that satisfy pos >= offset && pos + sizeof(flat_binder_object) <= offset + len. The append/acquire loop now walks all source objects and skips the ones that fail the same range test, so only in-range objects are copied and acquire_object()'d.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (kernel path): always sets kernelFields->mObjectsSorted = false after appending objects.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (RPC path): inserts newDataPos into rpcFields->mObjectPositions at std::upper_bound instead of using push_back, which keeps the vector sorted.",
      "libs/binder/Parcel.cpp:Parcel::writeFileDescriptor (RPC path): uses the same sorted insert for dataPos in rpcFields->mObjectPositions.",
      "libs/binder/Parcel.cpp: object-write path at the restart_write label: after recording a new object, applies kernelFields->mObjectsSorted &= (mDataPos == mDataSize), so the flag is cleared when the object is not written at the data end.",
      "libs/binder/tests/binderParcelUnitTest.cpp: adds TEST(Parcel, AppendOverObject). It asserts that appendFrom at position 8 in a parcel holding an FD object returns PERMISSION_DENIED."
     ],
     "completeness": [
      "The destination overlap check runs before the kernel/RPC branch, so it applies to both paths. The material does not show its exact semantics for RPC fields.",
      "Source object selection is fixed only in the kernel (mObjects) path. The RPC path already filtered each object with offset <= objPos && objPos < offset + len, and it is unchanged apart from the sorted insert.",
      "mObjectsSorted is now also cleared in appendFrom and in the non-tail object-write case. setDataPosition already cleared it (context at android-security-15.0.0_r17). The material does not show whether other write paths that could overwrite object bytes at a rewound position already validate."
     ],
     "behavior_changes": [
      "Parcel::appendFrom now fails (PERMISSION_DENIED per the new unit test, returned from validateReadData) when the destination range overlaps an existing object. Before, the append went ahead.",
      "With an unsorted source object table, appendFrom now copies only in-range objects, so objectsCount() can differ from the old first/last index behaviour.",
      "After any appendFrom that copies objects, and after an object is written at a non-tail position, mObjectsSorted is false.",
      "RPC parcels: mObjectPositions is kept sorted by inserting at std::upper_bound instead of using push_back."
     ],
     "preconditions": [
      "A caller uses Parcel::appendFrom on a destination whose mDataPos has been moved back over previously written objects, or with a source parcel whose object table is not sorted (for example, objects written after setDataPosition rewinds).",
      "appendFrom can be reached from the NDK through AParcel_appendFrom in frameworks/native/libs/binder/ndk/parcel.cpp (per index search). The mObjects path applies when the binder kernel driver is enabled at build time; the mObjectPositions path applies to RPC parcels."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/native 7be10040534a in android-security-14.0.0_r22. The Parcel.cpp logic is the same at older line offsets, and acquire_object at restart_write has no tagFds argument. The AppendOverObject test is placed after DebugReadAllFds. 6/8 patched-region lines present at android-security-14.0.0_r29.",
      "15": "Fix commit 96f0dd71bc07 in android-security-15.0.0_r10 (the analysed diff). 6/8 patched-region lines present at android-security-15.0.0_r17.",
      "16": "Same fix: 46e72b490edd in android-security-16.0.0_r1. The visible textual differences are acquire_object(..., true /*tagFds*/) and a grow condition that reads (mDataPos + len) > mDataCapacity. 7/8 patched-region lines present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). 6/8 patched-region lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. Apply the Parcel.cpp hunks. Where acquire_object lacks the tagFds parameter, or the grow check in appendFrom uses mDataSize instead of mDataPos, keep the branch's existing form and add only the new lines, as the 14 and 16 cherry-picks did.",
      "Make sure validateReadData exists on the target branch and that the test file has a PERMISSION_DENIED using-declaration."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of validateReadData is not in the material. The exact condition that returns PERMISSION_DENIED, how it treats RPC parcels, and how it uses mObjectsSorted are inferred only from the new test.",
      "The material does not show which production callers (beyond AParcel_appendFrom) call appendFrom at a rewound position or with unsorted source parcels.",
      "The material does not show which of the 8 patched-region lines are reported missing in the latest security tags.",
      "The material does not show the concrete downstream impact of the object table/data desync."
     ],
     "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-32326": {
     "cve": "CVE-2025-32326",
     "summary": "In Settings' AppRestrictionsFragment, the app-supplied custom restrictions Intent is now launched as a copy pinned to the component and package that passed the resolveActivity vetting, instead of re-launching a copy of the original (possibly implicit) Intent.",
     "component": "platform/packages/apps/Settings, users / app restrictions (AppRestrictionsFragment.RestrictionsResultReceiver)",
     "weakness": "Validation applied to a different object than the one used: the intent was vetted by resolving it once, but the original intent (not the resolved component) was then started, so the launch could resolve to a component other than the vetted one (check/use mismatch on an untrusted Intent).",
     "root_cause": "RestrictionsResultReceiver.onReceive reads an Intent from the broadcast result extras (Intent.EXTRA_RESTRICTIONS_INTENT), i.e. data returned for the app whose restrictions are being configured. assertSafeToStartCustomActivity(Intent) resolved it with mPackageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) and threw SecurityException (\"Application \" + packageName + \" is not allowed to start activity ...\") when its check failed, but returned void. The caller then called startActivityForResult(new Intent(restrictionsIntent), ...) with a copy of the original Intent. Because the original Intent is not necessarily explicit, the component actually started could differ from the one checked via resolveActivity, so the check did not constrain what Settings launched.",
     "fix_analysis": [
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.assertSafeToStartCustomActivity now returns an Intent. After the existing resolveActivity and checks succeed, it creates new Intent(intent), calls setComponent(activityInfo.getComponentName()) and setPackage(activityInfo.packageName) so the copy is bound to exactly the component that was vetted, and returns it.",
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.onReceive stores the return value in a local vettedIntent and passes vettedIntent to AppRestrictionsFragment.this.startActivityForResult instead of new Intent(restrictionsIntent). The existing catch of ActivityNotFoundException | SecurityException (log, EventLog 200688991, return) is unchanged."
     ],
     "completeness": [
      "Covers the single launch site in RestrictionsResultReceiver.onReceive: the started Intent is now explicit and pinned to the vetted ComponentName and package; other extras, action, data and flags of the app-supplied Intent are still copied through via new Intent(intent).",
      "The vetting criteria themselves (resolveActivity with MATCH_DEFAULT_ONLY and the subsequent check) are not changed by this commit; only the consistency between what is checked and what is started is fixed.",
      "No other callers of assertSafeToStartCustomActivity appear in the diff; it is a private method of the inner receiver class."
     ],
     "behavior_changes": [
      "The custom restrictions activity is always started with an explicit component (the one returned by resolveActivity) and an explicit package, so resolution at launch time cannot pick a different component.",
      "If the intent fails vetting the behavior is unchanged: error log, EventLog tag 0x534e4554/200688991, and no activity is started."
     ],
     "preconditions": [
      "The app restrictions UI in Settings is used and the target app's restrictions receiver returns a result Bundle containing Intent.EXTRA_RESTRICTIONS_INTENT; the launch happens only when invokeIfCustom is true and the fragment isResumed().",
      "The app-supplied Intent must resolve via resolveActivity(MATCH_DEFAULT_ONLY) and pass the existing check in assertSafeToStartCustomActivity (details elided in the diff)."
     ],
     "per_major": {
      "14": "same fix: commit 33a1cbf7bd0e in android-security-14.0.0_r22, identical hunks at slightly different line offsets (643/655/674); 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit e8a17485771e in android-security-15.0.0_r10 (cherry-pick of d3e34060803c); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit e12156e2d805 in android-security-16.0.0_r1, identical diff to 15; 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 present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained Java change in one file with no new dependencies; it relies on the pre-existing assertSafeToStartCustomActivity method (EventLog 223578534 / resolveActivity check) being present, which the 14, 15 and 16 diffs all show. At android-security-15.0.0_r17 the method body differs from the diff (new Intent(intent) copy appears right after the EventLog call), so apply by intent rather than by exact context when porting to customized trees."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact check logic between resolveActivity and the SecurityException throw is not shown in the diff context (lines are elided).",
      "The material does not show what change produced the reorganized method body visible at android-security-15.0.0_r17 (line 668) versus the diff.",
      "Bug 353680402 / 365739560 details and the concrete resolution mismatch are not in the material."
     ],
     "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-32327": {
     "cve": "CVE-2025-32327",
     "summary": "PickerDbFacade no longer puts caller-supplied unknown projection column names into the SQL projection. Unknown columns are left out of the query and added back as null-valued columns through a MatrixCursor after the query runs.",
     "component": "platform/packages/providers/MediaProvider, Photo Picker database facade (PickerDbFacade)",
     "weakness": "Caller-supplied column name used to build a SQL projection element. This likely allowed SQL injection through the projection, but the body of getProjectionSimple is not shown.",
     "root_cause": "In PickerDbFacade.getMediaStoreProjectionLocked, every requested column was mapped to a SQL projection string. Known PickerMediaColumns were mapped to internal expressions. In the default branch, the code called getProjectionSimple(\"NULL\", columns[i]), which built a projection element from the raw caller-supplied column name. queryMediaIdForAppsLocked then passed that projection to SQLiteQueryBuilder.query, so the caller-controlled name appears to have reached the SQL statement unvalidated. The test now uses the invalid column name \"test invalid column\" (with spaces) where it used to use \"testInvalidColumn\". That change is consistent with the name no longer being part of the SQL, but the old failure mode is not shown.",
     "fix_analysis": [
      "PickerDbFacade.java:getMediaStoreProjectionLocked: the projection is now built as an ArrayList. The default (unknown column) branch no longer adds anything to the projection; it only logs a warning. The method returns projection.toArray(new String[0]), so the SQL projection can be shorter than the requested columns array.",
      "PickerDbFacade.java:queryMediaIdForAppsLocked (called queryMediaIdForAppsInternal on Android 14): after qb.query runs, the method compares cursor.getColumnCount() to columns.length. If they are equal, or columns is null or empty, it returns the raw cursor. Otherwise it builds a MatrixCursor over the requested column names and copies each row through DatabaseUtils.cursorRowToContentValues and rowBuilder.add(column, contentValues.get(column)), so unknown columns get null. It then closes the original cursor and returns the MatrixCursor.",
      "PickerDbFacadeTest.java: the invalid column is renamed to \"test invalid column\". New assertions check that the cursor has exactly 2 columns and that the invalid column reads as 0 with getLong and null with getString."
     ],
     "completeness": [
      "The fix covers the queryMediaIdForApps path (queryMediaIdForAppsLocked and getMediaStoreProjectionLocked). This is the only place in the diff where caller column names were built into SQL.",
      "After the fix, unknown caller column names are no longer added to the projection. Known columns map to internal expressions; the bodies of getProjectionSimple, getProjectionDataLocked and getDisplayNameSql are not shown.",
      "The columns == null check in queryMediaIdForAppsLocked has no practical effect. getMediaStoreProjectionLocked reads columns.length before that check runs, so a null projection would fail there first. This behavior predates the fix and is unchanged.",
      "The diff does not touch other query entry points in PickerDbFacade. The material does not show whether they accept caller-supplied projections."
     ],
     "behavior_changes": [
      "Unknown projection column names are no longer part of the executed SQL. They are only logged (Log.w \"Unexpected Picker column\").",
      "If at least one unknown column is requested, the returned Cursor is a MatrixCursor. The unknown columns are present and null-valued: per the test, getLong returns 0 and getString returns null.",
      "If all requested columns are known, the raw query cursor is returned as before."
     ],
     "preconditions": [
      "A caller reaches PickerDbFacade.queryMediaIdForApps with a projection that contains a column name outside the handled PickerMediaColumns set. The test calls it with the PickerUriResolver segments PICKER_SEGMENT and PICKER_GET_CONTENT_SEGMENT. The app-facing entry point is not shown.",
      "The bulletin rates this High severity EoP. The material does not show what privilege is gained."
     ],
     "per_major": {
      "14": "adapted fix: commit 186cff75881c in android-security-14.0.0_r22. Same logic, applied to queryMediaIdForAppsInternal and to a getMediaStoreProjectionLocked(String[] columns) signature without pickerSegmentType. The test also adds the assertWithMessage import. 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 3f48b77e7cb9 in android-security-15.0.0_r10 (RESTRICT AUTOMERGE, cherry-picked from internal changes). 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). 7/8 lines of the patched region are 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": [
      "Each branch has one self-contained commit. Between 14 and 15, the source changes differ in the method name (queryMediaIdForAppsInternal vs queryMediaIdForAppsLocked) and in the pickerSegmentType parameter. The tests differ too: 14 adds the assertWithMessage import. The fix needs the import android.database.DatabaseUtils. It sits next to the existing static import of com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar.",
      "MediaProvider is a mainline module (general platform knowledge). Check the installed module version when you assess coverage."
     ],
     "confidence": "medium",
     "unknowns": [
      "The body of getProjectionSimple is not in the material. The conclusion that it put the column name into SQL is inferred from its call with \"NULL\" and columns[i] and from the test change to a name containing spaces.",
      "The material does not show the concrete security impact, that is, what EoP is reached.",
      "If every requested column is unknown, the SQL projection becomes empty. The diff does not show how SQLiteQueryBuilder.query handles an empty projection, or how the column-count comparison behaves in that case.",
      "The material does not identify the 1/8 missing line in the patched-region check at each latest security tag.",
      "The material does not show the app-facing entry points that reach queryMediaIdForApps."
     ],
     "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-32330": {
     "cve": "CVE-2025-32330",
     "summary": "Replaces the LE audio sharing (broadcast) password generator in SettingsLib. The old generator took a 12-hex-character password from a UUID string. The new one builds a 16-character password with SecureRandom from an 89-character alphabet.",
     "component": "platform/frameworks/base, packages/SettingsLib Bluetooth (LocalBluetoothLeBroadcast, LE Audio broadcast / audio sharing)",
     "weakness": "Insufficiently random / low-entropy generated password (weak credential generation)",
     "root_cause": "LocalBluetoothLeBroadcast.generateRandomPassword() built the audio sharing broadcast password from UUID.randomUUID().toString() by joining substring(0, 8) and substring(9, 13). The result was 12 hexadecimal characters: a 16-symbol alphabet with at most 48 bits of entropy.",
     "fix_analysis": [
      "packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java: adds the constants VALID_PASSWORD_CHARACTERS (upper and lower case letters, digits and punctuation, 89 characters) and PASSWORD_LENGTH = 16, and imports java.security.SecureRandom.",
      "LocalBluetoothLeBroadcast.generateRandomPassword(): now creates a new SecureRandom and loops PASSWORD_LENGTH times. Each pass picks random.nextInt(VALID_PASSWORD_CHARACTERS.length()) and appends that character to a StringBuilder. It returns the 16-character result. The UUID-substring derivation is removed.",
      "Net effect: the alphabet grows from 16 hex symbols to 89 characters and the length from 12 to 16. Entropy rises from at most 48 bits to about 103 bits."
     ],
     "completeness": [
      "The fix covers only the password generated by LocalBluetoothLeBroadcast.generateRandomPassword(). It does not change how the password is stored, displayed or transmitted (none of that is in the diff).",
      "The commit message says ag/31424453, filed against the same bug, is superseded by this change and need not be backported.",
      "Per index search, an identical UUID-substring password generator exists in packages/apps/Settings WifiHotspotRepository. This fix does not touch it. It belongs to a different feature (Wi-Fi hotspot) in a different project.",
      "The Android 15 commit leaves the java.util.UUID import in place (it is still present at android-security-15.0.0_r17). The Android 14 commit removes it."
     ],
     "behavior_changes": [
      "Generated audio sharing passwords change from 12 hex characters to 16 characters. These can include upper and lower case letters, digits and punctuation such as !@#$%^&*()-_=+[]{}|;:,.<>?/.",
      "Any UI or peer that expects a 12-character or hex-only broadcast code will see a different format. The material does not show any such consumers.",
      "A new SecureRandom instance is created on each call."
     ],
     "preconditions": [
      "The device supports LE Audio broadcast / audio sharing, and SettingsLib LocalBluetoothLeBroadcast generates a broadcast password. The callers are not shown in the material.",
      "The weakness concerns the strength of the generated secret. The material does not show any specific attacker interaction."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base a9d98a5646f1 in android-security-14.0.0_r22. Same generator rewrite, and it also makes generateRandomPassword() static and removes the java.util.UUID import. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 5b10581d2a91 in android-security-15.0.0_r10 (backport of ag/31824318). The java.util.UUID import is kept. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). The patched code (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched code (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "A single self-contained change: one private method, two constants and one import. If java.util.UUID is unused elsewhere in a fork's copy of the file, the import can be dropped as in the Android 14 commit. Otherwise keep it as in Android 15.",
      "Check that fork-specific UI or metadata consumers do not assume a 12-character hex broadcast code. The new password is 16 characters and can include punctuation."
     ],
     "confidence": "high",
     "unknowns": [
      "Where generateRandomPassword() is called within LocalBluetoothLeBroadcast, and how the password is used afterwards (broadcast code setting, display, storage). This includes whether it is only used when no user-supplied code exists.",
      "Whether java.util.UUID is still used elsewhere in the Android 15 file.",
      "The content of the superseded change ag/31424453 and of the original ag/31824318.",
      "The disclosure scenario: the bulletin only rates the issue High / ID."
     ],
     "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-32331": {
     "cve": "CVE-2025-32331",
     "summary": "The fix makes IKeyguardService.showDismissibleKeyguard() in SystemUI's KeyguardService return early whenever ActivityManager reports a lock task mode other than NONE (app pinning). As a result, folding a device with \"Swipe up to continue\" set no longer shows a dismissible keyguard over a pinned app.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardService / fold grace period)",
     "weakness": "Missing state check: the dismissible keyguard is shown without considering lock task (app pinning) mode",
     "root_cause": "On foldables, when the \"Continue using apps on fold\" setting is \"Swipe up to continue\", folding the device shows a dismissible keyguard (per the commit message). The handler for this, KeyguardService's showDismissibleKeyguard(), forwarded after trace() and checkPermission() to mKeyguardViewMediator.showDismissibleKeyguard() and then ran a block gated on SceneContainerFlag.isEnabled() && mFoldGracePeriodProvider.get().isEnabled(), all without checking lock task state. Per index search, KeyguardViewMediator.showDismissibleKeyguard() checks only fold grace period enablement and device provisioning. The dismissible keyguard was therefore shown even with a pinned app. The fix keeps the app pinned and in the same security state as when the device was unfolded (per the commit message). The bulletin rates this as a High EoP.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java, constructor: adds an injected ActivityManager parameter and stores it in a new final field, mActivityManager.",
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java, showDismissibleKeyguard(): after trace() and checkPermission(), returns early if mActivityManager.getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE. On Android 15 this skips both mKeyguardViewMediator.showDismissibleKeyguard() and the SceneContainerFlag/fold grace period block. On Android 16 it skips the mFoldGracePeriodProvider/mKeyguardInteractor.showDismissibleKeyguard() path. Result: when the device is folded with an app pinned, the screen stays on with the pinned app displayed (per the commit message)."
     ],
     "completeness": [
      "The gate is applied only at the showDismissibleKeyguard() entry point in KeyguardService. Per index search, KeyguardViewMediator.showDismissibleKeyguard() still has no lock task check of its own, so the protection depends on requests going through KeyguardService. The material shows no other caller.",
      "The check treats every lock task state other than LOCK_TASK_MODE_NONE the same way. This would include both pinned and locked task modes (general platform knowledge).",
      "No regression test is added: the commit message says it was tested manually, and the diff touches only KeyguardService.java."
     ],
     "behavior_changes": [
      "showDismissibleKeyguard() now returns without logging or reporting an error when lock task mode is active. When folded, the device stays on and shows the pinned app.",
      "Each showDismissibleKeyguard() request now calls ActivityManager.getLockTaskModeState().",
      "The KeyguardService constructor gains a new ActivityManager parameter. This affects Dagger injection and any code that constructs the service directly."
     ],
     "preconditions": [
      "Foldable device with \"Continue using apps on fold\" set to \"Swipe up to continue\" (fold grace period enabled).",
      "Device provisioned: per index search, KeyguardViewMediator.showDismissibleKeyguard() ignores the request otherwise on 15.",
      "An app is pinned, i.e. lock task mode is active (getLockTaskModeState() != LOCK_TASK_MODE_NONE).",
      "The device is folded. The request must pass KeyguardService.checkPermission()."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin). At android-security-14.0.0_r29, 5/8 lines of the patched region are present.",
      "15": "Same fix: commit 7f5cc94e82fc in android-security-15.0.0_r10 (cherry-pick of 14f874decfdd). It adds ActivityManager injection and the lock task early return before mKeyguardViewMediator.showDismissibleKeyguard(). At android-security-15.0.0_r17, 7/8 lines of the region are present.",
      "16": "Adapted fix: commit 705d322dc8b6 in android-security-16.0.0_r1. Same ActivityManager injection and early return, placed before the mFoldGracePeriodProvider/mKeyguardInteractor.showDismissibleKeyguard() body, which differs from 15. 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, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The change is self-contained. Place the getLockTaskModeState() early return directly after checkPermission() in showDismissibleKeyguard(), and inject ActivityManager into KeyguardService. SystemUI's Dagger framework services module normally provides ActivityManager (general platform knowledge). Any code that constructs KeyguardService directly must be updated for the new constructor parameter."
     ],
     "confidence": "high",
     "unknowns": [
      "Which system component invokes IKeyguardService.showDismissibleKeyguard() on fold.",
      "What checkPermission() enforces.",
      "How a dismissible keyguard over a pinned app leads to the EoP the bulletin describes. The commit message only says the fix keeps the app pinned and in the same security state.",
      "Whether a dismissible keyguard that is already showing when lock task mode starts is handled elsewhere.",
      "Whether Android 14 contains the fold grace period / showDismissibleKeyguard code at all, and what form the code takes on Android 17."
     ],
     "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-32345": {
     "cve": "CVE-2025-32345",
     "summary": "In Settings, ContentProtectionTogglePreferenceController.updateState now disables the Content Protection main switch for every non-admin user (!UserManager.isAdminUser()) instead of only for guest users (UserManager.isGuestUser()).",
     "component": "platform/packages/apps/Settings, security settings (Content Protection toggle, src/com/android/settings/security/)",
     "weakness": "Insufficient user-type / authorization check on a settings UI control (only guest users were excluded, other non-admin users were not)",
     "root_cause": "ContentProtectionTogglePreferenceController.updateState() decides whether the SettingsMainSwitchPreference for Content Protection can be used. When no enforced admin applies, the original code only called mSwitchBar.setEnabled(false) if userManager.isGuestUser() was true. Any other non-admin user (for example a secondary user) therefore kept an enabled switch and could change the Content Protection preference. The test file reads and writes this preference as a Settings.Global value (KEY_CONTENT_PROTECTION_PREFERENCE), which suggests it is a device-wide setting. The commit title states the toggle should not be enabled for non-admin users. The bulletin rates this as a High severity EoP.",
     "fix_analysis": [
      "src/com/android/settings/security/ContentProtectionTogglePreferenceController.java:updateState: the condition `userManager != null && userManager.isGuestUser() && mSwitchBar != null` is changed to `userManager != null && !userManager.isAdminUser() && mSwitchBar != null`. mSwitchBar.setEnabled(false) is now applied to guests and to all other non-admin users. The enforced-admin branch before it (setDisabledByAdmin + return) is unchanged.",
      "tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java: setUp switches from ShadowUserManager.setGuestUser(false) to setIsAdminUser(true). The guest test is renamed to updateState_flagEnabled_noEnforcedAdmin_nonAdminUser_switchBarDisabled and uses setIsAdminUser(false). The positive test is renamed to ..._adminUser_switchBarEnabled, explicitly sets setIsAdminUser(true) and verifies that setEnabled(false) is never called."
     ],
     "completeness": [
      "Covers the UI check in updateState for every non-admin user as reported by UserManager.isAdminUser(), replacing the narrower guest-only check.",
      "The fix is UI-level only: it disables the switch widget. onCheckedChanged() as shown has no admin check, and setChecked() and the availability logic are not in the material. The protection therefore relies on the switch being disabled, not on rejecting the write itself.",
      "The switch is only disabled when mSwitchBar != null, i.e. when displayPreference found a SettingsMainSwitchPreference for the key. This is unchanged from before.",
      "The material does not show whether the underlying Settings.Global write is also enforced elsewhere (for example in the framework)."
     ],
     "behavior_changes": [
      "For non-admin, non-guest users, the Content Protection main switch in Settings is now shown disabled when the enforced-admin branch does not apply. Before the fix, these users could use it.",
      "No change for admin users, guest users, or the enforced-admin (setDisabledByAdmin) path."
     ],
     "preconditions": [
      "A device on an affected version (bulletin: 15, 16, 16-next) where Settings displays ContentProtectionTogglePreferenceController with a SettingsMainSwitchPreference.",
      "The current user is a non-admin user that is not a guest (UserManager.isAdminUser() false, isGuestUser() false).",
      "The enforced-admin branch does not apply (otherwise setDisabledByAdmin is called and updateState returns first)."
     ],
     "per_major": {
      "14": "not affected: the bulletin does not list Android 14. ContentProtectionTogglePreferenceController.java and its test are absent at android-security-14.0.0_r29.",
      "15": "same fix: commit 6c9a5944dd04 in android-security-15.0.0_r10 (cherry-pick of ef801e1a8ec3, Change-Id I46609c795923d427a5b7fa10bc2e8b071fad72d6). At android-security-15.0.0_r17 the !isAdminUser() check is in place (6/8 lines of the patched region present).",
      "16": "same fix: commit dc41d27f1bd9 in android-security-16.0.0_r1 with an identical diff. At android-security-16.0.0_r8, 6/8 lines of the patched region are present.",
      "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": [
      "Single commit touching two files. The production change is a one-line condition swap in updateState. The updated test uses ShadowUserManager.setIsAdminUser(...)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the controller's setChecked()/isChecked()/getAvailabilityStatus() implementations, so it is unknown whether the Settings.Global write is enforced anywhere outside the UI.",
      "The material does not show whether non-admin users could reach the toggle other than through the Settings UI (for example via slices, since getSliceHighlightMenuRes is present).",
      "The material does not show whether the ShadowUserManager on every target branch provides setIsAdminUser(...)."
     ],
     "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-32347": {
     "cve": "CVE-2025-32347",
     "summary": "BiometricEnrollIntroduction now strips the MultiBiometricEnrollHelper extras EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT from its intent in onStart() whenever the calling package is not Settings itself, so PendingIntent extras supplied by external packages are dropped during biometric enrollment.",
     "component": "platform/packages/apps/Settings, biometrics enrollment (BiometricEnrollIntroduction, base of FingerprintEnrollIntroduction and FaceEnrollIntroduction)",
     "weakness": "Missing validation of caller-supplied intent extras: PendingIntent chaining extras from a calling package other than Settings were not checked against the caller",
     "root_cause": "BiometricEnrollIntroduction kept the multi-biometric chaining extras EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT (PendingIntents, as the tests show by filling them with mock(PendingIntent.class)) and EXTRA_SKIP_PENDING_ENROLL on whatever intent launched the activity, without checking who launched it. The existing helpers (removeEnrollNextBiometric, removeEnrollNextBiometricIfSkipEnroll, handleBiometricResultSkipOrFinished) handle these extras with no caller check. Since the fix keeps them only when the caller is Settings, any package that launched the enrollment introduction activity could supply its own PendingIntents to this flow. Where those PendingIntents are then used is not shown in the material.",
     "fix_analysis": [
      "src/com/android/settings/biometrics/BiometricEnrollIntroduction.java:onStart (new override): after super.onStart(), if getPackageName() does not equal getCallingPackage(), it loops over List.of(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE, EXTRA_ENROLL_AFTER_FINGERPRINT) and calls getIntent().removeExtra(key) for each key.",
      "src/com/android/settings/biometrics/BiometricEnrollIntroduction.java: removeEnrollNextBiometricIfSkipEnroll(Intent) and handleBiometricResultSkipOrFinished(int, Intent) change from protected to private.",
      "tests/robotests FaceEnrollIntroductionTest.java and FingerprintEnrollIntroductionTest.java: new drops_pendingIntents tests put the three extras on the launch intent (mock PendingIntents and a boolean), call mController.start(), and assert that hasExtra() is false for all three."
     ],
     "completeness": [
      "Covers both the fingerprint and face introduction activities, because the check is in the shared abstract base class BiometricEnrollIntroduction.",
      "The trust decision is based on package name: the extras are kept only when getCallingPackage() equals Settings' own package name. If getCallingPackage() returns null, getPackageName().equals(null) is false, so the extras are dropped. This is the safe direction.",
      "Only the three listed MultiBiometricEnrollHelper keys are removed. Other extras on the intent are left untouched.",
      "The stripping happens in onStart(), which runs after onCreate(). The material does not show whether any subclass reads these extras during onCreate().",
      "Other activities that might use the same MultiBiometricEnrollHelper extras outside BiometricEnrollIntroduction are not shown in the material and are not changed by this commit."
     ],
     "behavior_changes": [
      "When the biometric enrollment introduction is launched by any package other than Settings, EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT are silently removed from the activity intent in onStart(). No error is shown.",
      "Only launches where the calling package is Settings keep these chaining extras.",
      "Subclasses can no longer access removeEnrollNextBiometricIfSkipEnroll and handleBiometricResultSkipOrFinished, which are now private."
     ],
     "preconditions": [
      "The FingerprintEnrollIntroduction or FaceEnrollIntroduction activity (subclasses of BiometricEnrollIntroduction) is started by a package other than Settings with the MultiBiometricEnrollHelper extras set.",
      "Where the PendingIntents are actually sent is not shown in the material."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin, but the material gives no fix commit or tag facts for it. Status unknown.",
      "14": "Same fix: commit 214b82f7097b in android-security-14.0.0_r22, with the same onStart() logic and the same visibility changes at slightly different line offsets. The A14 diff provided shows only the src file. At android-security-14.0.0_r29, 7 of 8 lines of the patched region are present.",
      "15": "Same fix: commit 25cfacbe5ac2 in android-security-15.0.0_r10 (a cherry-pick of the internal change, Change-Id I61281dcf95e53100a96d6a218f3f00fd1b4ea3f9), covering the src file and both robolectric tests. At android-security-15.0.0_r17, 7 of 8 lines of the patched region are present.",
      "16": "Same fix: commit d50de07930ee in android-security-16.0.0_r1, matching the A15 change including tests. At android-security-16.0.0_r8, 7 of 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 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single self-contained commit. It relies on MultiBiometricEnrollHelper exposing the three EXTRA_* constants. If a downstream tree has subclasses that call removeEnrollNextBiometricIfSkipEnroll or handleBiometricResultSkipOrFinished, the change from protected to private will break the build.",
      "Every latest security tag shows 7 of 8 lines of the patched region. Check directly that the onStart() override and the three removeExtra calls are present instead of relying on the line count."
     ],
     "confidence": "high",
     "unknowns": [
      "Where in the enrollment flow the EXTRA_ENROLL_AFTER_FACE / EXTRA_ENROLL_AFTER_FINGERPRINT PendingIntents are sent (MultiBiometricEnrollHelper and the code that uses these extras are not in the material).",
      "Whether any subclass reads these extras in onCreate(), before the new onStart() stripping runs.",
      "Whether Android 13 received a matching fix (listed as affected, no commit provided).",
      "Which of the 8 patched lines is 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": 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-32349": {
     "cve": "CVE-2025-32349",
     "summary": "WindowManager stops honoring app-supplied window animations for untrusted overlays: untrusted TYPE_TOAST windows are forced to R.style.Animation_Toast in DisplayPolicy.adjustWindowParamsLw, and any non-system overlay that the system force-hides exits with a fixed short alpha fade in WindowStateAnimator instead of its own exit animation.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm: DisplayPolicy, WindowState, WindowStateAnimator)",
     "weakness": "Missing validation of untrusted input in a security-relevant path: an app-controlled WindowManager.LayoutParams.windowAnimations style decided how long a non-system overlay stayed on screen after the system forced it hidden (policy enforcement bypass via custom exit animation)",
     "root_cause": "When the system decides a non-system overlay must be hidden, WindowState.setForceHideNonSystemOverlayWindowIfNeeded(true) calls hide(true /* doAnimation */, true /* requestAnim */), so the window animates out before it disappears. This applies to windows whose session lacks mCanAddInternalSystemWindow and whose type is a system alert window type or TYPE_TOAST, per the gating in that method. Per the commit message, these windows could use their own specified exit animations. DisplayPolicy.adjustWindowParamsLw already clamped hideTimeoutMilliseconds and forced FLAG_NOT_TOUCHABLE for TYPE_TOAST, but it left attrs.windowAnimations unchanged. So a legacy or custom-view toast shown in the app's own process, and other non-system overlays, could set an animation of any duration. Per the commit messages, this let the untrusted window stay visible for a custom duration instead of the intended short one when the forced-hide policy was triggered.",
     "fix_analysis": [
      "Commit 394acf2aa1da, services/core/java/com/android/server/wm/DisplayPolicy.java:adjustWindowParamsLw, case TYPE_TOAST: after forcing FLAG_NOT_TOUCHABLE, the commit adds `if (!win.mSession.mCanAddInternalSystemWindow) attrs.windowAnimations = R.style.Animation_Toast;`. Toast windows from a session without the internal-system-window capability now always use the default toast animation (the commit message cites config_longAnimTime=500ms). Toasts rendered by SystemUI keep their style because SystemUI has the internal permission.",
      "Commit e4a93e6ffdaf, services/core/java/com/android/server/wm/WindowState.java: adds a package-private getter, isForceHiddenNonSystemOverlayWindow(), that returns the existing mForceHideNonSystemOverlayWindow flag. setForceHideNonSystemOverlayWindowIfNeeded sets that flag only for sessions lacking mCanAddInternalSystemWindow, and only for system alert window types or TYPE_TOAST. A TYPE_APPLICATION_OVERLAY window is exempt when isSystemApplicationOverlay() is true and its session has mCanCreateSystemApplicationOverlay.",
      "Commit e4a93e6ffdaf, services/core/java/com/android/server/wm/WindowStateAnimator.java, in the animation-selection path next to the AnimationUtils.loadAnimation(mContext, anim) call (the enclosing method is not shown in the material): inserts a new branch `else if (!isEntrance && mWin.isForceHiddenNonSystemOverlayWindow())`. That branch builds `new AlphaAnimation(1f, 0f)` with duration com.android.internal.R.integer.config_shortAnimTime. It sits before the `else { switch (transit) ... }` path, and the commit also adds the android.view.animation.AlphaAnimation import. When this branch is reached, a force-hidden non-system overlay animates out with a fixed short fade.",
      "The two commits work together: the DisplayPolicy change removes custom animations from untrusted toasts when parameters are adjusted, and the WindowStateAnimator change replaces custom exit animations for force-hidden overlay windows."
     ],
     "completeness": [
      "The DisplayPolicy change only covers attrs.type == TYPE_TOAST. System alert window types (e.g. TYPE_APPLICATION_OVERLAY) can still carry an app-chosen windowAnimations style. For those, the only mitigation is the WindowStateAnimator exit-fade substitution, which applies only while mForceHideNonSystemOverlayWindow is true.",
      "The WindowStateAnimator substitution is limited to !isEntrance, so entrance animations are unchanged.",
      "The new WindowStateAnimator branch is an else-if after a preceding branch whose condition is not shown in the material. When that branch is taken, the fade substitution does not apply. The material does not show whether that matters.",
      "Both commits base the trust decision on mSession.mCanAddInternalSystemWindow. Windows from sessions with that capability keep full control of their animations, which matches the stated intent.",
      "Other hide paths with similar type gating are not touched. One example is WindowState.setHiddenWhileSuspended (visible in the context, gated by mOwnerCanAddInternalSystemWindow and the same type checks). Whether those paths animate with the window's own style is not shown."
     ],
     "behavior_changes": [
      "TYPE_TOAST windows added by sessions without mCanAddInternalSystemWindow ignore the caller's WindowManager.LayoutParams.windowAnimations and always animate with R.style.Animation_Toast.",
      "Non-system overlay windows forced hidden via setForceHideNonSystemOverlayWindowIfNeeded now exit with a plain alpha fade (1 to 0) lasting config_shortAnimTime instead of their own exit animation. The commit says this avoids jump cuts and inconsistent durations.",
      "No new error codes or exceptions; the changes only affect which animation is used."
     ],
     "preconditions": [
      "A window owned by a session lacking mCanAddInternalSystemWindow, of type TYPE_TOAST or a system alert window type, with a custom windowAnimations style in its LayoutParams. Per the commit message, untrusted toasts come from legacy apps or deprecated custom-view toasts shown in the app's own process.",
      "For the WindowStateAnimator path: the system must have called WindowState.setForceHideNonSystemOverlayWindowIfNeeded(true) for that window, which calls hide(true, true). The commit's test launches Settings to trigger this.",
      "TYPE_APPLICATION_OVERLAY windows that are system application overlays, from sessions with mCanCreateSystemApplicationOverlay, are exempt from the forced-hide flag and so from the exit-fade substitution."
     ],
     "per_major": {
      "14": "Same fix as 15, partly shown: resolved at android-security-14.0.0_r22 via b8401310e97f. This is the same WindowState/WindowStateAnimator change (isForceHiddenNonSystemOverlayWindow getter plus the AlphaAnimation exit substitution) at different line offsets. The material does not show whether the DisplayPolicy TYPE_TOAST commit was also applied on 14. 8/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Both commits (394acf2aa1da for DisplayPolicy, e4a93e6ffdaf for WindowState/WindowStateAnimator) landed in android-security-15.0.0_r10. 4/4 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix as 15, partly shown: resolved at android-security-16.0.0_r1 via 3a76075721bb. This is the same DisplayPolicy.adjustWindowParamsLw TYPE_TOAST hunk at a different line offset. The material does not show whether the WindowStateAnimator commit was also applied on 16. 4/4 lines are present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (Android 17 is not listed). 4/4 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Carry both commits; they touch separate files. The DisplayPolicy hunk needs win.mSession.mCanAddInternalSystemWindow and R.style.Animation_Toast. The WindowStateAnimator hunk needs the mForceHideNonSystemOverlayWindow field in WindowState plus the new getter, the AlphaAnimation import and config_shortAnimTime.",
      "The bulletin lists Android 13 as affected, but the material has no 13 commit. Check that the new else-if lands before the per-transit switch in WindowStateAnimator's animation-selection code."
     ],
     "confidence": "medium",
     "unknowns": [
      "The enclosing method name and the condition of the preceding `if` in WindowStateAnimator are not shown in the material.",
      "Which system events call setForceHideNonSystemOverlayWindowIfNeeded(true) is not shown, beyond the commit test's mention of launching Settings.",
      "Whether both commits were applied on Android 14 and 16 (the material shows one commit per branch for those majors), and what was applied on Android 13.",
      "Whether the fix applies cleanly to Android 13.",
      "The exact security consequence is not described beyond the bulletin's EoP/High rating and the commit text."
     ],
     "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-32350": {
     "cve": "CVE-2025-32350",
     "summary": "Removes android:exported=\"true\" from SystemUI's ControlsActivity manifest entry and adds SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS to the controls settings dialog window created by ControlsSettingsDialogManager, so other apps cannot launch the activity and non-system overlay windows cannot be drawn over the dialog.",
     "component": "platform/frameworks/base, SystemUI (Device Controls: ControlsActivity, ControlsSettingsDialogManager)",
     "weakness": "Improperly exported Android component (activity exported without a permission) combined with a system dialog that did not hide non-system overlay windows (overlay/tapjacking exposure)",
     "root_cause": "In packages/SystemUI/AndroidManifest.xml the ControlsActivity entry carried android:exported=\"true\" (with launchMode singleInstance and visibleToInstantApps=\"true\"), and the shown hunk has no permission attribute, so other applications could start this SystemUI activity directly. In addition, the AlertDialog built in ControlsSettingsDialogManager.kt (ControlsSettingsDialogManagerImpl, exercised via maybeShowDialog in the test) prompts the user to enable the lockscreen controls settings (Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS). It was created without SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, so windows from non-system apps could be drawn on top of this system dialog. The bulletin rates the resulting issue as a High severity EoP.",
     "fix_analysis": [
      "packages/SystemUI/AndroidManifest.xml: the android:exported=\"true\" attribute is deleted from the ControlsActivity <activity> element (commit title: 'Unexport ControlsActivity'). The shown hunk has no intent-filter, so the activity is no longer exported and other apps can no longer start it.",
      "packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt (ControlsSettingsDialogManagerImpl, dialog construction before SystemUIDialog.registerDismissListener(d)): adds an import of android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS and the call d.window?.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) after the AlertDialog is built, so the window manager hides non-system overlay windows while this dialog is shown.",
      "packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt: the new test dialogHidesNonSystemOverlayWindows sets attempts to 0 and both settings to false, calls underTest.maybeShowDialog(context, completedRunnable), and asserts that dialog.window.attributes.privateFlags contains SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS."
     ],
     "completeness": [
      "Covers the ControlsActivity export (one manifest attribute removed) and the single dialog created in ControlsSettingsDialogManager.kt. The flag is applied unconditionally to every dialog instance created there.",
      "The flag is added to the dialog window only. The fix does not add it to ControlsActivity's own window or to any other controls UI, and the material does not show whether those surfaces need it.",
      "The manifest hunk does not show the android:name attribute, so the identity of the modified activity rests on the commit title ('Unexport ControlsActivity') and the surrounding attributes (singleInstance, visibleToInstantApps).",
      "android:visibleToInstantApps=\"true\" is left in place."
     ],
     "behavior_changes": [
      "Other packages, including instant apps, can no longer start ControlsActivity via explicit intent.",
      "While the controls settings dialog is showing, the window manager hides non-system application overlay windows because of SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS. They reappear when the dialog is dismissed (general platform knowledge).",
      "No new error codes or API changes. The new unit test asserts the private flag on the dialog window."
     ],
     "preconditions": [
      "Pre-fix: ControlsActivity was exported, so any installed app (and instant apps, given visibleToInstantApps) could start it directly.",
      "The dialog in ControlsSettingsDialogManager is only shown when at least one of LOCKSCREEN_SHOW_CONTROLS / LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS is false and the dialog has been shown fewer than MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG times (per the interface doc comment in the file).",
      "The overlay concern requires an app that can draw non-system overlay windows, for example one holding SYSTEM_ALERT_WINDOW (general platform knowledge)."
     ],
     "per_major": {
      "14": "Same fix with context differences: b08dcc947008 in android-security-14.0.0_r22 makes the same three changes. The differences are context only: the manifest hunk is at line 949, the import is com.android.systemui.R instead of com.android.systemui.res.R, and the test file adds an androidx AndroidJUnit4 import alongside the existing AndroidTestingRunner import. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix: 47424521c49b in android-security-15.0.0_r10, resolved via change_id (cherry-pick of 0042369d, Change-Id I63e98794). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "Same fix: d6b86833ee06 in android-security-16.0.0_r1. The diff matches Android 15 except for the manifest line offset (948). 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 (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit with no dependencies: delete the one manifest attribute, then add the import and the d.window?.addSystemFlags(...) line after the AlertDialog builder in ControlsSettingsDialogManager.kt. On Android 14 branches the R import is com.android.systemui.R rather than com.android.systemui.res.R, and the test file imports differ, as reflected in b08dcc947008.",
      "If a fork added an intent-filter or a permission to ControlsActivity, re-check the manifest: an element with an intent-filter needs an explicit android:exported value on recent targetSdk levels (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The manifest hunk omits the android:name line, so the activity name (ControlsActivity) comes from the commit title rather than the diff.",
      "The hunk header does not show the name of the function that builds the dialog (it reports 'internal constructor('). The test exercises it via maybeShowDialog.",
      "The material does not show how the exported ControlsActivity and the dialog relate in the reported issue (for example, whether launching the activity leads to the dialog). It also does not include the details of bug 404256832.",
      "The material does not show which components launch ControlsActivity internally, so it cannot confirm that legitimate launch paths are unaffected."
     ],
     "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-48522": {
     "cve": "CVE-2025-48522",
     "summary": "Adds a 1024-character upper limit on the display name accepted by AssociationRequest.Builder.setDisplayName in the Companion Device Manager API. A longer name now throws IllegalArgumentException.",
     "component": "platform/frameworks/base, Companion Device Manager (android.companion.AssociationRequest)",
     "weakness": "Missing input length validation (unbounded string accepted into a request shown in system UI)",
     "root_cause": "AssociationRequest.Builder.setDisplayName accepted any non-null CharSequence with no length limit. Per the class javadoc, the display name is \"the Display name of the device to be shown in the CDM confirmation UI\" and must be non-null for self-managed associations. An application could therefore put an arbitrarily long string into the request. The bulletin rates the issue as a High-severity EoP; the material does not show the exact downstream consequence of the oversize string.",
     "fix_analysis": [
      "core/java/android/companion/AssociationRequest.java: adds a private static constant DISPLAY_NAME_LENGTH_LIMIT = 1024 to the AssociationRequest class.",
      "core/java/android/companion/AssociationRequest.java:Builder.setDisplayName: after checkNotUsed() and mDisplayName = requireNonNull(displayName), the method compares displayName.length() with DISPLAY_NAME_LENGTH_LIMIT. If the name is longer, it throws IllegalArgumentException(\"Length of the display name must be at most 1024 characters\"). The check runs after the assignment, so the oversize value is already stored in mDisplayName when the exception is thrown.",
      "Single-commit fix. The same change was applied to 14, 15 and 16 with no functional difference."
     ],
     "completeness": [
      "Covers only the public Builder.setDisplayName entry point. The length check runs in the client-side Builder.",
      "The diff does not modify the private AssociationRequest constructor (which takes displayName directly) or the Parcelable deserialization path. The material does not show whether the system_server side independently checks the length of a received request, so a request built outside the Builder is not shown to be bounded.",
      "The assignment mDisplayName = requireNonNull(displayName) happens before the length check. If a caller catches the exception, the Builder still holds the oversize value. The diff does not show build() re-checking the length.",
      "No other fields of AssociationRequest (e.g. deviceProfile, device filters) get length limits in this change."
     ],
     "behavior_changes": [
      "AssociationRequest.Builder.setDisplayName now throws IllegalArgumentException for display names longer than 1024 characters. This is a public API behavior change; before the fix, any length was accepted.",
      "Apps that pass display names longer than 1024 characters now get an exception from setDisplayName."
     ],
     "preconditions": [
      "An application builds an AssociationRequest with AssociationRequest.Builder, calls setDisplayName with a CharSequence longer than 1024 characters, and submits the request to CompanionDeviceManager. Per the class javadoc, a display name is required for self-managed associations.",
      "The material does not show whether any special permission or device profile role is needed to reach the code that consumes the string."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base aa93bf9db684 in android-security-14.0.0_r22, identical diff (same file index 2e969f83f836..10d47559cf73); 4/4 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base bdad29b85a6c in android-security-15.0.0_r10, cherry-pick of b73847cdfeb6; 4/4 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base eb3cf182f929 in android-security-16.0.0_r1, same hunk content at different line offsets (the file has extra fields, such as the device icon); 4/4 lines present at android-security-16.0.0_r8.",
      "17": "not affected: not listed by the bulletin; 3/4 lines of the patched region present at android-security-17.0.0_r1. The exact code is not shown in the material."
     },
     "backport_notes": [
      "The change is self-contained (one constant and one if-block in Builder.setDisplayName). It matches the 14 and 15 trees exactly (same file index) and needs only an offset adjustment on 16-style trees, which have extra fields near the constant. The material includes no fork comparison.",
      "The new IllegalArgumentException is a public API behavior change. The commit message references 'Test: cts', but no test change is in the material."
     ],
     "confidence": "high",
     "unknowns": [
      "What the system side does with an oversize display name that leads to the High-severity EoP rating. The consuming code in the CDM service and the CDM confirmation UI is not included.",
      "Whether the system service checks the display name length independently of the Builder (the Parcel path is not shown).",
      "Whether build() can still be called after setDisplayName throws, leaving the oversize value in place.",
      "Exact form of the patched region on Android 17 (only a 3/4 line match is reported)."
     ],
     "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-48523": {
     "cve": "CVE-2025-48523",
     "summary": "Removes the single-account shortcut in Contacts' vCard SelectAccountActivity so the account selection dialog is always shown, instead of silently returning the only writable account and allowing an automatic vCard import.",
     "component": "platform/packages/apps/Contacts, vCard import (src/com/android/contacts/vcard/SelectAccountActivity.java)",
     "weakness": "Missing user confirmation / user-interaction bypass: an activity meant to let the user choose an import target returned a result automatically, with no UI, when exactly one account existed. This enabled auto-import of contacts.",
     "root_cause": "SelectAccountActivity.onCreate() got the writable accounts from AccountTypeManager.blockForWritableAccounts(). When accountList.size() == 1, it built a result Intent with the ACCOUNT_NAME, ACCOUNT_TYPE and DATA_SET of that single account, called setResult(RESULT_OK, intent) and finished immediately. No dialog was shown, so the import target was chosen without user interaction. The commit message (b/388032224, security team recommendation) describes the problem as auto-importing when there is a single account.",
     "fix_analysis": [
      "src/com/android/contacts/vcard/SelectAccountActivity.java:onCreate: the `else if (accountList.size() == 1) { ... setResult(RESULT_OK, intent); finish(); return; }` branch is deleted (9 lines removed, nothing added).",
      "For any accountList.size() >= 1, control now falls through to the existing path: log the account count, create the AccountSelectionUtil.AccountSelectedListener over accountList, and call showDialog(R.string.import_from_vcf_file). The result Intent is produced only in the listener's onClick, after the user picks an entry.",
      "The zero-account branch (log 'Account does not exist', finish()) is unchanged."
     ],
     "completeness": [
      "Covers the only automatic result path visible in SelectAccountActivity: with the branch gone, the visible RESULT_OK path requires a selection in the dialog.",
      "The comment block in onCreate (lines 57-60 in the r17 context) still says 'just one account -> use the account without asking the user' and 'no account -> use phone-local storage'. The comment is stale but has no functional effect. The zero-account path actually just finishes, and that is unchanged.",
      "The fix adds no check on how SelectAccountActivity is launched or by whom. It relies only on the dialog requiring user interaction. getWindow().setHideOverlayWindows(true) is already present in onCreate in the r17 context (not part of this diff).",
      "This commit does not touch other vCard import entry points or account-selection code outside this activity. The material does not show whether any other path auto-selects an account."
     ],
     "behavior_changes": [
      "Devices with exactly one writable contacts account now see the 'import from vcf file' account selection dialog (with a single entry) instead of getting an immediate result.",
      "If the dialog is cancelled without a selection, no RESULT_OK is set. The cancel wiring and the caller's handling are not shown in the material. Before the fix, this step returned a result immediately.",
      "In the single-account case, the existing Log.i line 'The number of available accounts: 1' is now reached."
     ],
     "preconditions": [
      "SelectAccountActivity (Contacts vCard import flow) is started and reaches onCreate.",
      "AccountTypeManager.blockForWritableAccounts() returns exactly one account (the vulnerable branch). With zero accounts the activity simply finished; with two or more the dialog was already shown."
     ],
     "per_major": {
      "14": "same fix: identical diff (6104890ea916, linked by Change-Id) in android-security-14.0.0_r22; patched region present at android-security-14.0.0_r29 (7/8 lines).",
      "15": "same fix: 7c8dbcffbc93 in android-security-15.0.0_r10, same diff; patched region present at android-security-15.0.0_r17 (7/8 lines).",
      "16": "same fix: c314edb9bde8 in android-security-16.0.0_r1, same diff; patched region present at android-security-16.0.0_r8 (7/8 lines).",
      "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 commit that only deletes one else-if block in SelectAccountActivity.onCreate. The same hunk (index 8ead5fab3..797fc8670) was applied unchanged on 14, 15 and 16, so it should apply cleanly to any tree whose onCreate still has the `accountList.size() == 1` shortcut. Optionally update the stale three-case comment above it."
     ],
     "confidence": "high",
     "unknowns": [
      "How SelectAccountActivity is launched in the vulnerable scenario (manifest export state, intent filters, calling component) is not in the material. The content of b/388032224 beyond the commit message is not available.",
      "Whether CancelListener is attached to the dialog, and how the caller handles a cancelled result, is not shown (the context is cut off inside onCreateDialog).",
      "Which 1 of the 8 lines of the patched region differs in the latest security tags 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-48524": {
     "cve": "CVE-2025-48524",
     "summary": "WifiPermissionsUtil.isSystem() now returns false up front when the calling uid is an SDK sandbox uid (on T and later), so SDK sandbox processes are no longer classified as system apps by the Wifi service.",
     "component": "platform/packages/modules/Wifi, Wifi service permission helpers (WifiPermissionsUtil)",
     "weakness": "Improper authorization: caller-privilege check derived from package ApplicationInfo flags without excluding SDK sandbox uids",
     "root_cause": "isSystem(String packageName, int uid) in service/java/com/android/server/wifi/util/WifiPermissionsUtil.java decided whether a binder caller is a system app only by resolving ApplicationInfo for the supplied packageName under the user derived from uid and testing APP_INFO_FLAGS_SYSTEM_APP. It did not consider that the uid may be an SDK sandbox uid (Process.isSdkSandboxUid). A sandbox caller could therefore be treated as a system app by whatever Wifi service paths rely on isSystem(), getting the privileged treatment reserved for system apps. The bulletin rates the impact as DoS, High.",
     "fix_analysis": [
      "WifiPermissionsUtil.java:isSystem: inside the try block, before the PackageManager lookup, add `if (SdkLevel.isAtLeastT() && Process.isSdkSandboxUid(uid)) return false;`. On T+ builds any SDK sandbox uid is rejected as non-system regardless of the packageName's ApplicationInfo flags. On pre-T builds the guard does not take effect (SDK sandbox uids were introduced in T, general platform knowledge).",
      "The check runs after Binder.clearCallingIdentity() and the finally block still restores identity; the NameNotFoundException fallback (return false) is unchanged."
     ],
     "completeness": [
      "Covers only WifiPermissionsUtil.isSystem(); every Wifi service path that relies on isSystem() for privileged treatment inherits the fix.",
      "The fix does not add a uid-to-packageName consistency check inside isSystem(); the code comment states that checkPackage is called to verify validity beforehand, and the material does not show those callers.",
      "Other privilege helpers in WifiPermissionsUtil (e.g. isDeviceOwner, isProfileOwnerOfOrganizationOwnedDevice, isOrganizationOwnedDeviceAdmin visible in the context) are untouched by this commit; the material does not show whether they have a comparable sandbox-uid issue.",
      "The guard is gated on SdkLevel.isAtLeastT(); on pre-T platform builds no change takes effect, which is consistent with SDK sandbox uids only existing from T onward (general platform knowledge)."
     ],
     "behavior_changes": [
      "Callers running as an SDK sandbox uid are now reported as non-system by isSystem(); any Wifi API that grants system-app treatment through this helper will deny it to sandbox processes.",
      "No new exception or error code; the method simply returns false earlier and skips the PackageManager lookup for sandbox uids."
     ],
     "preconditions": [
      "Device running Android T or later (SdkLevel.isAtLeastT() true) with the Wifi module.",
      "Caller executes in an SDK sandbox process (Process.isSdkSandboxUid(uid) true) and reaches a Wifi service entry point whose authorization depends on WifiPermissionsUtil.isSystem(). The specific entry points are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 980d442c5926 in android-security-14.0.0_r22, identical 3-line insertion at the same place in isSystem() (hunk offset at line 1138); 5/5 lines present at android-security-14.0.0_r29",
      "15": "same fix: commit 298745e0cb23 in android-security-15.0.0_r10, cherry-pick with Merged-In I1ee7a09b7628a67dcb3502b29d99761bafd913b5; 5/5 lines present at android-security-15.0.0_r17",
      "16": "same fix: commit c47809aec031 in android-security-16.0.0_r1, identical diff to the 15 commit; 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": [
      "Single self-contained commit that uses SdkLevel.isAtLeastT() and Process.isSdkSandboxUid. Android 13 is listed as affected by the bulletin, but the material includes no 13 commit; the same 3-line insertion into isSystem() should apply if the method body matches."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Wifi service APIs call isSystem(), and therefore which privileged operation an SDK sandbox caller could previously reach; the material does not list them (per index search, results returned only unrelated isSystem methods in other projects).",
      "How the misclassification leads to the DoS impact stated by the bulletin.",
      "The Android 13 fix commit is not included in the material."
     ],
     "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-48526": {
     "cve": "CVE-2025-48526",
     "summary": "ChooserActivity now gives tabs for profiles other than the launched-as profile a sanitized copy of the payload intents, with package and component cleared on each intent and on its selector, instead of the caller's original explicit intents.",
     "component": "platform/packages/modules/IntentResolver, ChooserActivity (share sheet) cross-profile tabs",
     "weakness": "Missing sanitization of caller-controlled explicit intent targeting on the cross-profile path (policy bypass / improper access control)",
     "root_cause": "In ChooserActivity.createMultiProfilePagerAdapter, the same list request.getPayloadIntents() was passed unchanged to createChooserGridAdapter for every Profile, including profiles different from profileHelper.getLaunchedAsProfile(). That list is targetIntent plus additionalTargets; per index search, readChooserRequest fills it from EXTRA_INTENT and readAlternateIntents(). Explicit targeting carried by those intents (Intent.getPackage, Intent.getComponent, and the same fields on Intent.getSelector) therefore reached the other profile's tab. The KDoc added to ChooserRequest says such explicit intents could otherwise be used to bypass the device's cross-profile sharing policy settings.",
     "fix_analysis": [
      "java/src/com/android/intentresolver/util/IntentUtils.kt (new): sanitizePayloadIntents(List<Intent>) copies each Intent with Intent(intent) and calls setPackage(null) and setComponent(null). If a selector exists, it is replaced with a copy whose package and component are also cleared. The original intents are not changed.",
      "java/src/com/android/intentresolver/data/model/ChooserRequest.kt: adds val crossProfilePayloadIntents by lazy { sanitizePayloadIntents(payloadIntents) }, so each ChooserRequest builds the sanitized list once from the existing payloadIntents (targetIntent + additionalTargets).",
      "java/src/com/android/intentresolver/ChooserActivity.java:createMultiProfilePagerAdapter: for each profile, computes isCrossProfile = !profile.equals(launchedAs). It passes request.getCrossProfilePayloadIntents() when cross-profile and request.getPayloadIntents() otherwise. Initial intents are still passed only for the launched-as profile (isCrossProfile ? null : initialIntentArray). The initialIntents parameter is removed and the method reads request.getInitialIntents() itself. Both call sites in ChooserActivity are updated: the onCreate path and a second site that is followed by setCurrentPage(currentPage).",
      "java/src/com/android/intentresolver/shared/model/Profile.kt: cosmetic only (unused import removed, trailing commas added); no behavioral change.",
      "tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt (new): checks that package and component are null on the sanitized intents and on their selectors."
     ],
     "completeness": [
      "Sanitization only happens when building ChooserGridAdapter instances for profiles that differ from the launched-as Profile. The launching profile's own tab, including its clone user (which belongs to the same Profile object), keeps the original explicit intents.",
      "Only package and component are cleared, on the intent and on its selector. Everything else is copied by the Intent copy constructor without change.",
      "Covers both adapter creation call sites in ChooserActivity. The fix touches ChooserActivity only; the material does not show whether any other IntentResolver surface passes caller intents across profiles.",
      "Initial intents were already withheld from cross-profile tabs before the fix, and they still are."
     ],
     "behavior_changes": [
      "In any tab whose profile is not the launched-as profile, payload intents that named a package or component are resolved without that explicit targeting. A caller can no longer point the other profile's tab at a specific package or component.",
      "Each ChooserRequest lazily creates a sanitized copy of the payload intents. The launching profile's tab still uses the original intents unchanged.",
      "Internal signature change: createMultiProfilePagerAdapter no longer takes a List<Intent> initialIntents parameter."
     ],
     "preconditions": [
      "The device has more than one profile visible to the chooser (work or private), so createMultiProfilePagerAdapter builds a tab for a Profile other than getLaunchedAsProfile().",
      "A caller starts the chooser with a target intent or alternate intents (read into ChooserRequest by readChooserRequest, per index search) that carry an explicit package or component on the intent or on its selector.",
      "Cross-profile sharing to the other profile is otherwise reachable through the chooser UI. The fix text names the cross-profile sharing policy settings as what could be bypassed."
     ],
     "per_major": {
      "14": "Adapted fix (4ffdecbbef8a in android-security-14.0.0_r22). This tree has no ChooserRequest model. ChooserActivity statically imports IntentUtils.sanitizePayloadIntents, computes crossProfileIntents = sanitizePayloadIntents(mIntents), and passes it to whichever of the personal and work ChooserGridAdapters is not selectedProfile (PROFILE_PERSONAL / PROFILE_WORK). This version of the Kotlin helper accepts null entries and keeps them. The diff shown includes no unit test. At android-security-14.0.0_r29: 8/8 lines of the patched region present.",
      "15": "Same fix (923a5673ac9d in android-security-15.0.0_r10, cherry-pick of d605b544). At android-security-15.0.0_r17: 6/8 lines of the patched region present. The context there shows the fix in place: ChooserRequest imports sanitizePayloadIntents, the call sites no longer pass getInitialIntents(), and IntentUtils.kt now also contains prepareCrossProfileIntents.",
      "16": "Same fix (247af9e3e487 in android-security-16.0.0_r1). The logic is identical; the profile loop iterates over Collection<ProfileRecord>, and ChooserRequest also carries callerAllowsTextToggle. At android-security-16.0.0_r8: 6/8 lines of the patched region present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit. For trees with the Kotlin ChooserRequest model, use the 15/16 shape: crossProfilePayloadIntents plus isCrossProfile in createMultiProfilePagerAdapter, updating both call sites. For the older ChooserActivity derived from ResolverActivity, with mIntents and PROFILE_PERSONAL/PROFILE_WORK, use the 14 shape with the null-tolerant helper. The Profile.kt hunks are formatting only and can be dropped if they conflict."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not say which cross-profile sharing policy explicit intents could bypass, or what the concrete outcome is, beyond the fix's KDoc statement.",
      "The material does not cover whether other IntentResolver surfaces (for example ResolverActivity rather than ChooserActivity) handle cross-profile payload intents in a similar unsanitized way.",
      "The material does not show whether the second updated call site is the recreatePagerAdapter path that onChooserRequestChanged uses for Shareousel updates.",
      "The material does not say why Android 17 is not listed even though its tree contains most of the patched region, or what accounts for the 6/8 partial matches on 15, 16 and 17."
     ],
     "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-48527": {
     "cve": "CVE-2025-48527",
     "summary": "Settings' Notification History screen now shows the generic \"notification_hidden_text\" string as the summary (and sets no title) for history, snoozed and dismissed notifications belonging to a profile that has a secure lock, is currently locked, and has private notifications disallowed on the lock screen, instead of showing the real title and text.",
     "component": "platform/packages/apps/Settings, notification history UI (src/com/android/settings/notification/history: NotificationHistoryActivity, NotificationHistoryAdapter, NotificationSbnAdapter)",
     "weakness": "Missing lock-state / privacy-setting check before displaying sensitive data (information disclosure across a locked profile boundary)",
     "root_cause": "NotificationHistoryActivity builds three RecyclerViews: per-package history (NotificationHistoryAdapter over HistoricalNotification) and snoozed/dismissed lists (NotificationSbnAdapter over StatusBarNotification). Both adapters' onBindViewHolder unconditionally called holder.setTitle(...) and holder.setSummary(...) with the real notification title and text. NotificationSbnAdapter already tracks the enabled profiles of the current user (mEnabledProfiles), but nothing consulted the per-user LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS setting or whether that profile was locked behind its own secure credential. As a result, a locked profile with its own lock (the commit was tested manually with a work profile with a different PIN) that was set to hide sensitive content on the lock screen had its notification content shown in the history page.",
     "fix_analysis": [
      "NotificationHistoryActivity.java: right after mPm/mUm are obtained and before the CountDownLatch/history loading (the hunk does not show the method name), the code iterates mUm.getProfiles(getUserId()). For each UserInfo whose Settings.Secure LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS (read with getIntForUser, default 0) is 0, it creates a LockPatternUtils and a KeyguardManager and, if lpu.isSecure(user.id) && km.isDeviceLocked(user.id), adds user.id to the new field ArrayList<Integer> mContentRestrictedUsers. In this Android 15 commit the list is also cleared (twice) before the loop and in onDestroy.",
      "NotificationHistoryActivity.java: the three adapter constructions now pass mContentRestrictedUsers (and, for NotificationHistoryAdapter, the activity Context): the per-package NotificationHistoryAdapter, and the two NotificationSbnAdapter instances for mSnoozedRv (isSnoozed=true) and mDismissedRv (isSnoozed=false).",
      "NotificationHistoryAdapter.java: the constructor gains Context and ArrayList<Integer> contentRestrictedUsers parameters, stored in mContext and mContentRestrictedUsers. onBindViewHolder: if mContentRestrictedUsers.contains(hn.getUserId()), only holder.setSummary(mContext.getString(com.android.internal.R.string.notification_hidden_text)) is called; otherwise the original setTitle(hn.getTitle())/setSummary(hn.getText()) run. Posted time and the click listener are unchanged.",
      "NotificationSbnAdapter.java: the constructor gains ArrayList<Integer> contentRestrictedUsers, stored in mContentRestrictedUsers. onBindViewHolder: if mContentRestrictedUsers.contains(sbn.getNormalizedUserId()), only holder.setSummary(notification_hidden_text) is called; otherwise the original setTitle(getTitleString(...))/setSummary(getTextString(...)) run. Icon, background, package label, posted time and user badge handling are unchanged."
     ],
     "completeness": [
      "Covers all three lists rendered by NotificationHistoryActivity (per-package history, snoozed, dismissed), using the same restricted-user list.",
      "The list is computed in the activity's loading path. The material does not show whether it is recomputed when a profile's lock state or the LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS setting changes while the activity is displayed.",
      "Only title and text are redacted. Package label, app icon, user badge, posted time and item count are still shown, and the item click behaviour is unchanged, for restricted users.",
      "In the redacted branch holder.setTitle(...) is not called at all. The material does not show whether the view holder resets the title when it is recycled.",
      "The check uses default 0 for an unset LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, so a profile with no explicit value is redacted if it is secure and locked. Profiles that are unlocked or have no secure lock are never redacted."
     ],
     "behavior_changes": [
      "Notification History entries from a secure, currently locked profile whose LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS is 0 (or unset) now display the generic notification_hidden_text string as the summary, with no title set.",
      "The NotificationHistoryAdapter and NotificationSbnAdapter constructors changed signature (extra Context and/or ArrayList<Integer> parameter); any other places that construct them must be updated.",
      "The activity's loading path now performs additional per-profile Settings.Secure reads and LockPatternUtils/KeyguardManager queries."
     ],
     "preconditions": [
      "The device has at least one profile of the current user (e.g. a work profile) with its own secure lock (LockPatternUtils.isSecure true) that is currently locked (KeyguardManager.isDeviceLocked(userId) true), with LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS unset or 0 for that profile.",
      "History, snoozed or dismissed notifications exist for that profile, so the adapters receive entries with that user id.",
      "The user opens the Settings Notification History activity, which renders the lists via NotificationHistoryAdapter.onBindViewHolder and NotificationSbnAdapter.onBindViewHolder (invoked through RecyclerView.Adapter.bindViewHolder, per index search)."
     ],
     "per_major": {
      "14": "adapted fix: commit cee46cf86cd9 in android-security-14.0.0_r22 with identical redaction logic; differs in context (OnMainSwitchChangeListener import) and omits the mContentRestrictedUsers.clear() calls and comments. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "this commit d8b1de7a18fc in android-security-15.0.0_r10 (cherry-pick of 9df37c3f8be2); includes clear() calls before the loop and in onDestroy. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "adapted fix: commit 504a34c4d4a3 in android-security-16.0.0_r1 with identical redaction logic; context adapted (GONE/VISIBLE static imports, NotificationSbnAdapter hunk without the setDividerVisible line), no clear() calls. 8/8 lines present 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 self-contained commit in packages/apps/Settings. It relies on com.android.internal.widget.LockPatternUtils, KeyguardManager.isDeviceLocked(int) and com.android.internal.R.string.notification_hidden_text, which all three branch variants use unchanged. The main adaptation is the surrounding context (imports, GONE/VISIBLE) and updating any other places that construct the two adapters."
     ],
     "confidence": "high",
     "unknowns": [
      "Which lifecycle method contains the restricted-user computation, and whether it runs again (e.g. on resume) so that lock-state or setting changes are picked up while the activity is alive.",
      "Whether the 14/16 variants, which lack clear() calls, can accumulate duplicate entries if the computation runs more than once (harmless for contains() but not shown).",
      "Whether NotificationHistoryViewHolder/NotificationSbnViewHolder reset the title when a redacted item is bound to a recycled holder (setTitle is skipped in the redacted branch).",
      "Whether other Settings or SystemUI surfaces show the same history data; the material covers only NotificationHistoryActivity."
     ],
     "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-48528": {
     "cve": "CVE-2025-48528",
     "summary": "Reverts the \"Fix biometric prompt appearing above shade\" change in SystemUI's AuthContainerView. The BiometricPrompt window moves back from TYPE_APPLICATION_OVERLAY to TYPE_KEYGUARD_DIALOG, and the panel interaction detector is enabled unconditionally again.",
     "component": "platform/frameworks/base, SystemUI biometrics (BiometricPrompt / AuthContainerView, UDFPS overlay on 16)",
     "weakness": "System dialog created with an app-overlay window type (incorrect window type). On 15, dismissal on panel interaction was also disabled when the constraintBp flag is on.",
     "root_cause": "Commit 93b417adaecdd (\"Fix biometric prompt appearing above shade\") made two changes in AuthContainerView.java. It switched the WindowManager.LayoutParams used for the BiometricPrompt window from TYPE_KEYGUARD_DIALOG to TYPE_APPLICATION_OVERLAY. It also moved mPanelInteractionDetector.enable(...) in onAttachedToWindow under if (!constraintBp()), so with constraintBp on the prompt no longer dismissed itself on panel interaction. TYPE_APPLICATION_OVERLAY is the window type available to apps holding SYSTEM_ALERT_WINDOW, while TYPE_KEYGUARD_DIALOG is a system-only type (general platform knowledge). Inference from the diff: the overlay window type is the security-relevant change. The bulletin only rates the issue as High EoP.",
     "fix_analysis": [
      "Android 15 (ca71b9a63c40, a pure revert of 93b417adaecdd): in AuthContainerView.java, the new WindowManager.LayoutParams(...) (around line 984) changes the window type from TYPE_APPLICATION_OVERLAY back to TYPE_KEYGUARD_DIALOG. SYSTEM_FLAG_SHOW_FOR_ALL_USERS is unchanged.",
      "Android 15: AuthContainerView.java:onAttachedToWindow moves mPanelInteractionDetector.enable(() -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)) out of the if (!constraintBp()) block to immediately after mWakefulnessLifecycle.addObserver(this). Only updatePositionByCapability(false) remains flag-gated.",
      "Android 16 (c2774dc575e9 \"Move BP window to Keyguard_dialog\"): AuthContainerView.java makes the same TYPE_APPLICATION_OVERLAY -> TYPE_KEYGUARD_DIALOG change. It has no panel detector hunk.",
      "Android 16: UdfpsControllerOverlay.kt adds a REASON_AUTH_BP branch in show() that binds the touch overlay to the new PromptUdfpsTouchOverlayViewModel (shouldHandleTouches always true). It also drops the controller parameter from show(), and removes the SETTING_REMOVE_ENROLLMENT_UI debug setting and many constructor dependencies. UdfpsController.java now passes Lazy<PromptUdfpsTouchOverlayViewModel> and calls overlay.show(mOverlayParams). UdfpsControllerTest.java and UdfpsControllerOverlayTest.kt are adjusted to match."
     ],
     "completeness": [
      "Both branches change the single window-type constant in AuthContainerView. No other window creation sites are touched in the provided diffs.",
      "Android 15 also restores the unconditional mPanelInteractionDetector.enable(). Android 16's diff has no equivalent hunk, so the material does not show whether 16 ever had the flag-gated detector.",
      "Android 16 goes further than 15: it gives the UDFPS touch overlay a dedicated PromptUdfpsTouchOverlayViewModel for REASON_AUTH_BP. The 15 commit does not include this part.",
      "The revert on 15 brings back whatever issue 93b417adaecdd was addressing (prompt appearing above the shade). The 15 commit contains no alternative fix for it."
     ],
     "behavior_changes": [
      "The BiometricPrompt window is created as TYPE_KEYGUARD_DIALOG instead of TYPE_APPLICATION_OVERLAY. Given the reverted commit's title, the prompt may again appear above the notification shade on Android 15.",
      "On Android 15, panel interaction while the prompt is showing dismisses it with AuthDialogCallback.DISMISSED_USER_CANCELED, even when constraintBp() is enabled.",
      "On Android 16, the UDFPS touch overlay for REASON_AUTH_BP always handles touches (PromptUdfpsTouchOverlayViewModel). The debug-only SETTING_REMOVE_ENROLLMENT_UI setting is removed, and the UdfpsControllerOverlay.show() signature changes."
     ],
     "preconditions": [
      "SystemUI shows a BiometricPrompt through AuthContainerView.",
      "The affected code creates the prompt window with TYPE_APPLICATION_OVERLAY. On 15, the detector regression also requires the constraintBp() flag to be enabled."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29, 3/8 lines of the patched region are present.",
      "15": "Resolved in android-security-15.0.0_r10 by ca71b9a63c40, which reverts 93b417adaecdd. At android-security-15.0.0_r17, 8/8 lines of the patched region are present, and the excerpt shows the unconditional mPanelInteractionDetector.enable() in onAttachedToWindow.",
      "16": "Resolved in android-security-16.0.0_r1 by a forward fix (c2774dc575e9). It changes the window type to TYPE_KEYGUARD_DIALOG and refactors UdfpsControllerOverlay/UdfpsController with PromptUdfpsTouchOverlayViewModel. At android-security-16.0.0_r8, 8/8 lines are present.",
      "17": "Not affected per the bulletin. At android-security-17.0.0_r1, 1/8 lines of the patched region are present. The material does not show why."
     },
     "backport_notes": [
      "Check which window type AuthContainerView uses for the prompt window, and whether mPanelInteractionDetector.enable() sits inside if (!constraintBp()). If the tree matches the pre-fix 15 state, the 15 commit is a 3-line revert in a single file.",
      "The 16 forward fix depends on the newer UdfpsControllerOverlay constructor shape and adds PromptUdfpsTouchOverlayViewModel plus test changes. On 15-based trees, the window-type change in the revert is the core part."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete attack scenario, what triggers it, and which privilege is gained. The bulletin only says High EoP.",
      "Whether Android 16 ever carried the flag-gated mPanelInteractionDetector.enable() regression, since its diff has no such hunk.",
      "Whether another change on Android 15 addresses the 'prompt appearing above shade' issue after the revert.",
      "How Android 14 and 17 create the prompt window. Only partial line matches (3/8 and 1/8) are known."
     ],
     "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-48529": {
     "cve": "CVE-2025-48529",
     "summary": "Stops Telephony from storing, reading back, or migrating a caller-supplied voicemail ringtone URI through its default SharedPreferences. TelephonyManager#setVoicemailRingtoneUri becomes a no-op, and getVoicemailRingtoneUri only reports the sound of the voicemail NotificationChannel.",
     "component": "platform/frameworks/opt/telephony, voicemail notification settings (VoicemailNotificationSettingsUtil / NotificationChannelController), reached via PhoneInterfaceManager in packages/services/Telephony",
     "weakness": "Caller-controlled URI persisted, then later propagated to a notification channel or returned (cross-profile issue per the commit message; the bulletin type is ID). A legacy settings path stayed reachable after the migration.",
     "root_cause": "VoicemailNotificationSettingsUtil.setRingtoneUri wrote the URI passed to TelephonyManager#setVoicemailRingtoneUri into Telephony's default SharedPreferences (key prefix voicemail_notification_ringtone_). Two code paths then used the stored value. First, getRingtoneUri fell back to getRingTonePreference when the voiceMail channel was null. Second, NotificationChannelController.migrateVoicemailNotificationSettings read getRingTonePreference and passed the value to voiceMailChannel.setSound when creating the channel. According to the commit message, this let a URI set via the setter be migrated onto the notification channel, causing a cross-profile exploit. The shared preference had been obsolete since the Android P migration to NotificationChannel#getSound, but the write and read paths remained.",
     "fix_analysis": [
      "VoicemailNotificationSettingsUtil.setRingtoneUri: body removed and the method now does nothing. TelephonyManager#setVoicemailRingtoneUri (PhoneInterfaceManager.setVoicemailRingtoneUri calls setRingtoneUri, per index search) can therefore no longer persist a URI into SharedPreferences.",
      "VoicemailNotificationSettingsUtil.getRingtoneUri: fallback to getRingTonePreference removed. It now returns channel.getSound() if the CHANNEL_ID_VOICE_MAIL channel exists, else null. A new comment says the shared preferences must never be consulted.",
      "VoicemailNotificationSettingsUtil.getRingTonePreference: deleted. It used to read the pref, call migrateVoicemailRingtoneSettingsIfNeeded, and parse the stored string into a Uri. The now-unused imports android.provider.Settings and android.text.TextUtils were removed.",
      "NotificationChannelController.migrateVoicemailNotificationSettings: removed the getRingTonePreference call and the voiceMailChannel.setSound(...) call that applied the stored URI (or DEFAULT_NOTIFICATION_URI) with USAGE_NOTIFICATION attributes. Only the vibration preference is now migrated when the channel is created. The javadoc link to getRingTonePreference and the android.net.Uri import were removed."
     ],
     "completeness": [
      "Covers all three ringtone-URI paths visible in the diff: the write (setRingtoneUri), the read fallback (getRingtoneUri/getRingTonePreference), and the channel migration (migrateVoicemailNotificationSettings).",
      "The sibling vibration setting is untouched: setVibrationEnabled still writes SharedPreferences (visible in the r17 context), and migrateVoicemailNotificationSettings still reads getVibrationPreference. That setting is a boolean, not a URI.",
      "Shared preference values already stored on disk are not cleared. The patched code simply never reads them.",
      "The diff does not show whether migrateVoicemailRingtoneSettingsIfNeeded has any remaining callers after getRingTonePreference was deleted."
     ],
     "behavior_changes": [
      "TelephonyManager#setVoicemailRingtoneUri silently has no effect; the diff adds no error path.",
      "TelephonyManager#getVoicemailRingtoneUri returns null when the voiceMail notification channel does not exist, instead of a stored value or DEFAULT_NOTIFICATION_URI. Otherwise it returns the channel's sound as before.",
      "The voiceMail NotificationChannel created by migrateVoicemailNotificationSettings no longer has setSound called on it."
     ],
     "preconditions": [
      "The caller reaches PhoneInterfaceManager.setVoicemailRingtoneUri. It must be the default dialer package or pass enforceCallingOrSelfModifyPermissionOrCarrierPrivilege for the target subscription, and the device must have FEATURE_TELEPHONY_CALLING (per index search).",
      "The stored URI is used when the voiceMail channel is created by migrateVoicemailNotificationSettings, or when getVoicemailRingtoneUri is read, for example by Dialer's VisualVoicemailNotifier.getVoicemailRingtoneUri (per index search)."
     ],
     "per_major": {
      "14": "same fix: commit 122251ced157 in android-security-14.0.0_r22. Its diff is identical to the 15 commit (same pre/post blob hashes). 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: commit e5cdca27526f in android-security-15.0.0_r10 (Change-Id I7252c692eb2a5ff4b4fcbddba77425cb423539f3). 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: commit f33f9ef6467b in android-security-16.0.0_r1, identical diff. 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": [
      "Single commit touching two files in frameworks/opt/telephony, with identical hunks on 14/15/16. The setRingtoneUri and getRingtoneUri signatures are unchanged, so the PhoneInterfaceManager caller (per index search) still compiles.",
      "After backporting, check that nothing else in a customized tree still calls the deleted getRingTonePreference."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact cross-profile mechanism is described only at a high level in the commit message.",
      "Whether migrateVoicemailRingtoneSettingsIfNeeded still has callers or is now dead code.",
      "Whether a companion change was made in packages/services/Telephony or Dialer; the material includes none.",
      "Why 1 of 8 lines of the patched region is missing at each latest tag."
     ],
     "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-48531": {
     "cve": "CVE-2025-48531",
     "summary": "Settings' CredentialStorage now identifies the launching app via ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) instead of Activity.getCallingPackage() when deciding whether the caller is the trusted certinstaller package.",
     "component": "platform/packages/apps/Settings, security/CredentialStorage (credential install flow)",
     "weakness": "Incorrect caller identification in an authorization check (wrong API used to determine calling package).",
     "root_cause": "CredentialStorage.checkCallerIsCertInstallerOrSelfInProfile() compared the result of Activity.getCallingPackage() against \"com.android.certinstaller\" and then ran a signature check against that same value. As the commit message states, Activity.getCallingPackage() does not always return the package name of the actual calling app, so the trust decision for the certinstaller branch was based on an unreliable identity.",
     "fix_analysis": [
      "src/com/android/settings/security/CredentialStorage.java: adds a private helper getCallingPackageName() that calls ActivityManager.getService().getLaunchedFromPackage(getActivityToken()); on RemoteException it returns null.",
      "src/com/android/settings/security/CredentialStorage.java:checkCallerIsCertInstallerOrSelfInProfile(): reads the launching package once into a local callingPackage, compares it to \"com.android.certinstaller\", and uses the same local for getPackageManager().checkSignatures(callingPackage, getPackageName()) == SIGNATURE_MATCH. This replaces both uses of getCallingPackage() so the identity check and the signature check use the same ActivityManager-recorded launcher package.",
      "Adds import android.app.ActivityManager; android.os.RemoteException is already imported (visible in the code context)."
     ],
     "completeness": [
      "Covers the only two uses of getCallingPackage() shown in the diff, both inside checkCallerIsCertInstallerOrSelfInProfile(); the certinstaller branch now uses the launcher package recorded by ActivityManager.",
      "The remaining part of the function (launchedFromUserId path for 'Settings running in a profile of this user') is not shown in the material and was not modified; whether it already used a reliable source cannot be judged from the diff.",
      "On RemoteException the helper returns null, so the certinstaller branch is skipped; execution then continues to the rest of the function (not shown).",
      "No other files or callers are changed; the material does not show any other getCallingPackage()-based checks in Settings."
     ],
     "behavior_changes": [
      "The certinstaller branch now trusts only the launching package recorded by ActivityManager (getLaunchedFromPackage) rather than Activity.getCallingPackage(); callers for which these two values differ are judged on the launcher package.",
      "If the ActivityManager binder call fails, the caller is not treated as certinstaller (null package)."
     ],
     "preconditions": [
      "The CredentialStorage activity in Settings is launched by an app and reaches checkCallerIsCertInstallerOrSelfInProfile(); the exact intent actions that reach this check are not shown in the material.",
      "The launching app is not actually com.android.certinstaller signed with the Settings signature."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/Settings da4efc0a0615 in android-security-14.0.0_r22, identical diff with a one-line hunk offset; patched region present at android-security-14.0.0_r29 (7/8 lines).",
      "15": "same fix: platform/packages/apps/Settings cc1b1b5e493a in android-security-15.0.0_r10 (cherry-pick of 70bd3efe0674); patched region present at android-security-15.0.0_r17 (7/8 lines).",
      "16": "same fix: platform/packages/apps/Settings 0f61e586873a in android-security-16.0.0_r1, identical diff with a hunk offset; patched region present at android-security-16.0.0_r8 (7/8 lines).",
      "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": [
      "Single self-contained Java change in CredentialStorage.java with no dependencies beyond the hidden ActivityManager.getService().getLaunchedFromPackage(IBinder) API, which Settings can access as a platform-signed system app (general platform knowledge). The diff is the same across 14/15/16 apart from hunk offsets; Android 13 is listed as affected by the bulletin but no 13 commit is in the material."
     ],
     "confidence": "high",
     "unknowns": [
      "Which intent actions / entry points of CredentialStorage invoke checkCallerIsCertInstallerOrSelfInProfile() and what privileged action is gated by it (the surrounding code is not in the material).",
      "In which situations Activity.getCallingPackage() differs from the actual launcher (the commit message does not explain).",
      "Whether the launchedFromUserId part of the same function already used a reliable caller source.",
      "The specific fix commit for Android 13, which the bulletin lists as affected.",
      "Why the latest tags report 7/8 rather than 8/8 lines of the patched region."
     ],
     "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-48534": {
     "cve": "CVE-2025-48534",
     "summary": "CellBroadcastHandler.getDefaultCBRPackageName now queries broadcast receivers with PackageManager.MATCH_SYSTEM_ONLY instead of flags 0, so only system packages can be selected as the default CellBroadcastReceiver package.",
     "component": "platform/packages/modules/CellBroadcastService, CellBroadcastHandler (default CBR package resolution)",
     "weakness": "Insufficient verification of the target package identity (non-system packages included in the receiver query, filtered only by a sourceDir string match)",
     "root_cause": "getDefaultCBRPackageName in src/com/android/cellbroadcastservice/CellBroadcastHandler.java called packageManager.queryBroadcastReceivers(intent, 0), which did not restrict results to system packages. The only filter after the query is isAppInCBApexOrAlternativeApp, which accepts a package if appInfo.sourceDir starts with CB_APEX_PATH or merely contains the substring CB_APP_PLATFORM_NAME. A package that is not part of the CellBroadcast apex could therefore pass the filter and be returned as the default CBR package. The commit message calls this a security vulnerability; its test steps install a fake cellbroadcast app and check whether the cellbroadcast message is received. The bulletin rates it High severity, type DoS.",
     "fix_analysis": [
      "src/com/android/cellbroadcastservice/CellBroadcastHandler.java:getDefaultCBRPackageName: the flags argument of the queryBroadcastReceivers call changes from 0 to PackageManager.MATCH_SYSTEM_ONLY, so the candidate list contains only receivers from system packages before the existing sourceDir-based removeIf filter runs.",
      "Same function: visibility changes from package-private static to public static with @VisibleForTesting, so the unit test can call it directly.",
      "tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java: the new testGetDefaultCBRPackageName builds an ACTION_SMS_EMERGENCY_CB_RECEIVED intent, calls getDefaultCBRPackageName with the mocked context, and verifies that queryBroadcastReceivers is called exactly once with MATCH_SYSTEM_ONLY."
     ],
     "completeness": [
      "Covers the single query in getDefaultCBRPackageName. The existing handling for an empty list and for multiple matches (logModuleError with ERRTYPE_NOTFOUND_DEFAULTCBRPKGS / ERRTYPE_FOUND_MULTIPLECBRPKGS) is unchanged.",
      "The isAppInCBApexOrAlternativeApp filter is untouched: it still accepts any sourceDir that contains the substring CB_APP_PLATFORM_NAME. The loose string check remains, but the query flag now limits it to system packages.",
      "The implicit broadcast through CellBroadcastIntents.sendSmsCbReceivedBroadcast (lines 826-832; the code comment says it is sent for third-party carrier apps) is not changed by this fix.",
      "The material shows no other queryBroadcastReceivers call sites in the project, so it cannot tell whether similar lookups exist elsewhere."
     ],
     "behavior_changes": [
      "Non-system packages that declare a receiver for the CB intent are no longer returned by the query and cannot become the default CBR package.",
      "getDefaultCBRPackageName is now public (annotated @VisibleForTesting); no functional change for callers inside the module.",
      "If no system package passes the filter, the function takes the existing path: it returns null and logs ERRTYPE_NOTFOUND_DEFAULTCBRPKGS, as before."
     ],
     "preconditions": [
      "A non-system package must be installed with a broadcast receiver for the intent passed to getDefaultCBRPackageName (the test uses Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED). Its sourceDir must also satisfy isAppInCBApexOrAlternativeApp (starts with CB_APEX_PATH or contains CB_APP_PLATFORM_NAME).",
      "Per index search, CellBroadcastAlertService in packages/apps/CellBroadcastReceiver is the target of ACTION_SMS_EMERGENCY_CB_RECEIVED intents in its unit tests. The index results show no callers of getDefaultCBRPackageName."
     ],
     "per_major": {
      "14": "same fix: platform/packages/modules/CellBroadcastService af24a165e952 in android-security-14.0.0_r22. The source change is identical (flags 0 to MATCH_SYSTEM_ONLY, @VisibleForTesting public) and the same unit test is added; the test import placement and hunk offsets differ. 7/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: 584cec4f17ea in android-security-15.0.0_r10 (RESTRICT AUTOMERGE cherry-pick of f24f40e2512f, Change-Id If2e797dd6d352627ddfac16f3d0447be0e172d25). 7/8 lines of the patched region present at android-security-15.0.0_r17; the code context there shows MATCH_SYSTEM_ONLY in place.",
      "16": "not affected per the bulletin; 7/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit: the source change is a one-argument flag change plus a visibility change and annotation. The only differences seen between branches are in the test file (import placement)."
     ],
     "confidence": "high",
     "unknowns": [
      "The values of CB_APEX_PATH and CB_APP_PLATFORM_NAME, and therefore the exact sourceDir conditions a non-apex package could meet, are not shown in the material.",
      "Which callers in CellBroadcastHandler invoke getDefaultCBRPackageName, when they do so (e.g. during message dispatch), and how the return value is used are not shown.",
      "Why one of the 8 patched-region lines is reported absent in each latest security tag is not shown, nor whether the MATCH_SYSTEM_ONLY change is present in the Android 16 and 17 tags.",
      "Whether devices receive this fix through a mainline module update separate from the platform security tag is not shown."
     ],
     "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-48535": {
     "cve": "CVE-2025-48535",
     "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is now launched as a copy. The copy is the object that was resolved, and its component and package are set to the ActivityInfo that passed the vetting check. Before, Settings relaunched the app-supplied Intent after only checking it.",
     "component": "platform/packages/apps/Settings, users/AppRestrictionsFragment (app restrictions, RestrictionsResultReceiver)",
     "weakness": "Check/use mismatch on an untrusted Intent: the vetting result was not bound to the Intent actually started, so the activity launch from Settings could act as a confused deputy.",
     "root_cause": "RestrictionsResultReceiver.onReceive reads an Intent (Intent.EXTRA_RESTRICTIONS_INTENT) from the result extras via getResultExtras(true). assertSafeToStartCustomActivity resolved that Intent with PackageManager.resolveActivity(MATCH_DEFAULT_ONLY) and checked that the resolved activity's package equals the expected packageName, but it returned nothing (void). The caller then called startActivityForResult(new Intent(restrictionsIntent), ...), which is a copy of the original app-controlled Intent. The vetting result (the resolved component) was never applied to the launched Intent. As a result, nothing guaranteed that the component resolveActivity picked was the component that would actually be started.",
     "fix_analysis": [
      "Commit e8a17485771e 'AppRestrictions - use vetted component' (first), in src/com/android/settings/users/AppRestrictionsFragment.java. RestrictionsResultReceiver.assertSafeToStartCustomActivity now returns an Intent. After the resolveActivity and packageName checks pass, it creates new Intent(intent), calls setComponent(activityInfo.getComponentName()) and setPackage(activityInfo.packageName), and returns the copy. RestrictionsResultReceiver.onReceive stores the return value in vettedIntent and passes vettedIntent to AppRestrictionsFragment.this.startActivityForResult instead of new Intent(restrictionsIntent).",
      "Commit 1e4423730f87 'startActivityForResult with earlier new Intent' (second). In assertSafeToStartCustomActivity, the copy new Intent(intent) is now created at the top, before resolution, and mPackageManager.resolveActivity is called on vettedIntent instead of the original intent. The later copy is removed. The object that is resolved is now the same object whose component and package are set and which is started.",
      "The code at android-security-15.0.0_r17 also shows vettedIntent.removeFlags(FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION), setData(null) and setClipData(null) after setPackage. These lines are not in either diff provided and come from a change not included in the material."
     ],
     "completeness": [
      "Covers the single launch site in RestrictionsResultReceiver.onReceive. The started Intent is always the copy that was resolved, with an explicit component and package taken from the ResolveInfo that passed the package check.",
      "The check itself is unchanged: resolveActivity with MATCH_DEFAULT_ONLY plus a packageName equality check. A null resolveInfo or a package mismatch still aborts the launch.",
      "The two provided commits do not strip URI grant flags, data or ClipData from the app-supplied Intent. The r17 code shows that a further change, not in the material, added that stripping.",
      "Other extras and flags of the app-supplied Intent are still carried over on the copy. These two commits override only the component and package."
     ],
     "behavior_changes": [
      "The custom restrictions activity is now started with an explicit ComponentName and package (the vetted ones), instead of whatever the app-supplied Intent would resolve to at startActivity time.",
      "resolveActivity now runs on a copy of the Intent, so the original Parcelable from the result extras is no longer passed to PackageManager.",
      "No new error paths. ActivityNotFoundException and SecurityException are still caught and logged (EventLog 0x534e4554 / 200688991), and the launch is skipped."
     ],
     "preconditions": [
      "An app answers Settings' restrictions broadcast with result extras containing Intent.EXTRA_RESTRICTIONS_INTENT. RestrictionsResultReceiver.onReceive reads it via getResultExtras(true).",
      "The receiver was created with invokeIfCustom true, and AppRestrictionsFragment isResumed() when the result arrives.",
      "The supplied Intent must resolve (MATCH_DEFAULT_ONLY) to an activity whose package equals the target app's packageName, to pass the existing check."
     ],
     "per_major": {
      "14": "Same fix (first commit): 'AppRestrictions - use vetted component' landed as 33a1cbf7bd0e in android-security-14.0.0_r22, with identical hunks at shifted line numbers (643/655/674). 8/8 lines of the patched region are present at android-security-14.0.0_r29. The material does not show the second 'earlier new Intent' commit on 14.",
      "15": "Same fix: both commits, e8a17485771e (use vetted component) and 1e4423730f87 (earlier new Intent), are in android-security-15.0.0_r10. 8/8 lines are present at android-security-15.0.0_r17, which also contains the extra URI-flag/data/ClipData stripping.",
      "16": "Same fix: 'startActivityForResult with earlier new Intent' landed as 25ed933c4cf5 in android-security-16.0.0_r1. Its base (blob c4a01797d0e) already contained the 'use vetted component' change. 8/8 lines are 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 in this order: first Iff0d820c1261c29eb6703bf89194339cba700688 (assertSafeToStartCustomActivity returns the copy with component and package set, and the caller uses it), then I860e9e606de6b8d3c99fa52a63b72ba7a99ce179 (moves new Intent(intent) before resolveActivity). The second diff's context depends on the first. Both commits touch only src/com/android/settings/users/AppRestrictionsFragment.java.",
      "Consider also picking the follow-up that adds removeFlags(FLAG_GRANT_*_URI_PERMISSION), setData(null) and setClipData(null), seen at 15.0.0_r17. It is not in the provided diffs."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not describe how the component chosen by resolveActivity could differ from the one the activity manager would start for the unpinned Intent (the commit message only says 'See bug').",
      "The material does not explain why the copy had to be made before resolveActivity (second commit).",
      "The material does not show whether the second commit was applied to Android 14. Only the first commit is given for 14, though 8/8 patched lines are reported present.",
      "The commit that added URI-flag/data/ClipData stripping, visible at 15.0.0_r17, is not part of the material.",
      "The material does not show which UI path sets invokeIfCustom, or which user types reach this screen."
     ],
     "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-48537": {
     "cve": "CVE-2025-48537",
     "summary": "ZenModeConditions now tracks CPS subscriptions by (condition Uri, ComponentName) pairs instead of by Uri alone, only lets system-owned (or manual) zen rules be bound to the built-in system ConditionProviderServices, and ignores non-system rules whose component lives in the \"android\" package.",
     "component": "platform/frameworks/base, services/core notification (ZenModeConditions / ConditionProviders, Do Not Disturb automatic rules)",
     "weakness": "Improper authorization / identity confusion: a condition Uri was treated as a unique key for a ConditionProviderService, and non-system rules could be re-bound to system condition providers",
     "root_cause": "In ZenModeConditions.evaluateRule, every rule (regardless of owning package) was matched against mConditionProviders.getSystemProviders(); if any SystemConditionProviderService.isValidConditionId(id) accepted the rule's conditionId, the code called ensureRecordExists for the system CPS and overwrote rule.component with the system CPS component. So a rule owned by an arbitrary package with a system-shaped condition Uri (e.g. condition://android/schedule?...) was bound to the system provider, and a package rule could also directly name a component in the \"android\" package. Separately, mSubscriptions was an ArrayMap<Uri, ComponentName> and the \"current\" set in evaluateConfig was an ArraySet<Uri>, so two rules with the same Uri but different CPSes collided: one subscription entry overwrote the other, and unsubscribe/keep decisions were made on the Uri alone, mixing up different CPSes.",
     "fix_analysis": [
      "services/core/java/com/android/server/notification/ZenModeConditions.java:evaluateRule: computes isSystemRule = isManual || ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getPkg()). If the rule is not a system rule but rule.component.getPackageName() equals SYSTEM_AUTHORITY (\"android\"), it logs a Slog.w and returns without creating a record, subscribing, or adding the rule to the current set.",
      "ZenModeConditions.java:evaluateRule: the loop over mConditionProviders.getSystemProviders() (which calls ensureRecordExists and rewrites rule.component to the system CPS) is now executed only when isSystemRule is true. Non-system rules always take the !isSystemCondition branch and are resolved via mConditionProviders.findConditionProvider(rule.component), i.e. their own declared CPS.",
      "ZenModeConditions.java: mSubscriptions changed from ArrayMap<Uri, ComponentName> to ArraySet<Pair<Uri, ComponentName>>; evaluateRule builds Pair<Uri, ComponentName> uriAndCps = new Pair<>(id, rule.component) and adds it to both current and mSubscriptions (replacing current.add(id) and mSubscriptions.put(conditionId, component)).",
      "ZenModeConditions.java:evaluateConfig: current is now ArraySet<Pair<Uri, ComponentName>>; the cleanup loop iterates mSubscriptions.valueAt(i) pairs and calls unsubscribeIfNecessary(subscription.second, subscription.first) only when that exact (Uri, component) pair is absent from current.",
      "services/core/java/com/android/server/notification/ConditionProviders.java: ConditionRecord made package-private (was private static class) and a @VisibleForTesting getRecord(Uri, ComponentName) wrapper around getRecordLocked(id, component, false) added; test-support only, no behavior change.",
      "Tests: new ZenModeConditionsTest covers system rule with system CPS (record created and subscribed), package rule naming the system schedule CPS (no record), and package rule with its own CPS but a schedule-like Uri (record only for the package CPS, none for the system CPS). ZenModeHelperTest.testRulesWithSameUri rewritten to use a registered package CPS instead of ScheduleConditionProvider."
     ],
     "completeness": [
      "Covers the rule evaluation path in ZenModeConditions.evaluateRule/evaluateConfig for both manual and automatic rules; the manual rule is always treated as a system rule (isManual=true).",
      "The new rejection only targets components in the \"android\" package (SYSTEM_AUTHORITY). It does not verify in this code that a non-system rule's component belongs to the rule's own package (rule.getPkg()); whether that is enforced elsewhere is not shown in the material.",
      "System-rule detection relies on rule.getPkg() equalling \"android\"; the material does not show how pkg is populated or protected when rules are created or restored from XML.",
      "ConditionProviders.getRecordLocked already keyed records by (id, component), so the ConditionProviders side needed no logic change; other ZenModeConditions callbacks are not in the diff and their Uri-based lookups, if any, are not shown."
     ],
     "behavior_changes": [
      "A non-system automatic rule whose component is in the \"android\" package is now skipped entirely in evaluateRule with a warning log (\"...which is not allowed!\"); it gets no condition record, no subscription, and its rule.component is no longer rewritten.",
      "Non-system rules with a condition Uri that a system provider would accept are no longer redirected to the system CPS (schedule, event, etc.); they are resolved against their own component via findConditionProvider.",
      "Multiple rules sharing the same condition Uri across different CPSes are now tracked and unsubscribed independently per (Uri, component) pair, instead of the last writer overwriting the subscription entry.",
      "ConditionProviders.ConditionRecord is now package-visible and a test-only getRecord(Uri, ComponentName) accessor exists."
     ],
     "preconditions": [
      "A ZenModeConfig automatic rule exists whose owning package is not \"android\" (rule.getPkg() != ZenModeConfig.SYSTEM_AUTHORITY) and whose conditionId is accepted by a SystemConditionProviderService.isValidConditionId, or whose component is in the \"android\" package.",
      "ZenModeConditions.evaluateConfig must run over that config; automatic rules are supplied by apps via the AutomaticZenRule APIs (general platform knowledge).",
      "Only automatic rules with a non-null component and no configurationActivity reach the affected code (evaluateConfig checks automaticRule.component != null; evaluateRule returns early for configurationActivity != null)."
     ],
     "per_major": {
      "14": "adapted fix (platform/frameworks/base 49078c4bfdce, android-security-14.0.0_r22): same logic, but evaluateRule gained the new isManual parameter and both evaluateConfig call sites were updated (true for manual, false for automatic); the ZenModeHelperTest hunk differs because the old test used the \"android\" package. 7/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix (63aab59ce138, android-security-15.0.0_r10); isManual parameter already existed. 7/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix (57451c039840, android-security-16.0.0_r1); test hunk differs only in context and use of the static INTERRUPTION_FILTER_PRIORITY. 7/8 patched lines present at android-security-16.0.0_r8.",
      "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": [
      "Single commit. On branches where evaluateRule lacks the isManual parameter (as on Android 14), add it and pass true for config.manualRule and false for automatic rules as in 49078c4bfdce; ConditionProviders.java hunks are test-support only (visibility of ConditionRecord plus getRecord) and their surrounding context differs by branch.",
      "ZenModeHelperTest.testRulesWithSameUri is updated alongside to register a package CPS (per its comment, otherwise ZenModeConditions clears rule.condition)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which concrete information was disclosed (bulletin type ID) and via which system provider; the material does not describe the trigger or the disclosed data.",
      "Which single line of the 8-line patched region differs in the latest security tags (7/8 present on all branches).",
      "How ZenRule.pkg is set and protected when rules are added, updated, or restored from XML, which the isSystemRule check depends on.",
      "Whether ZenModeHelper independently validates that a rule's component belongs to the rule's owning package.",
      "The callers of ZenModeConditions.evaluateConfig, and the exact fallback taken on 15/16 when a rule's own CPS is not alive (elided in the 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."
     }
    },
    "CVE-2025-48538": {
     "cve": "CVE-2025-48538",
     "summary": "Extends the existing \"cannot hide package android\" guard in PackageManagerService so that the SystemUI package (resolved via PackageManagerInternal.getSystemUiServiceComponent()) can also not be hidden, preventing a device-unusable state.",
     "component": "platform/frameworks/base, PackageManagerService (package hiding path)",
     "weakness": "Missing validation / insufficient protection of a critical system package (denial of service)",
     "root_cause": "The package-hiding code in services/core/java/com/android/server/pm/PackageManagerService.java only refused to hide the package named \"android\". The SystemUI package was not protected, so a caller with package-hiding capability (per the commit message, tested with a custom DPC, and even when done inside a managed profile) could hide SystemUI, which per the commit message makes the phone impossible to use.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageManagerService.java, in the package-hiding path (the hunk header only shows the class, the method name is not in the material): the condition `\"android\".equals(packageName)` is extended with `|| LocalServices.getService(PackageManagerInternal.class).getSystemUiServiceComponent().getPackageName().equals(packageName)`.",
      "When either matches, the code logs `Slog.w(TAG, \"Cannot hide package: \" + packageName)` (log message now includes the actual package name) and returns false, the same early exit already used for \"android\".",
      "Single-commit fix; identical diff on Android 14, 15 and 16 (only hunk line numbers differ)."
     ],
     "completeness": [
      "Covers only the hide-package path where the existing \"android\" guard lives; the SystemUI package is identified dynamically from PackageManagerInternal.getSystemUiServiceComponent(), not hard-coded.",
      "Only the one check site is modified. The material does not show whether other package state paths (for example suspend, disable, or per-component enabled-setting changes) have equivalent protection for SystemUI.",
      "No test is added (commit says Test: manual with custom DPC)."
     ],
     "behavior_changes": [
      "Requests to hide the SystemUI package now fail (return false) with a warning log instead of succeeding.",
      "Warning log text changes from the fixed string \"Cannot hide package: android\" to \"Cannot hide package: <packageName>\".",
      "Hide requests that reach this check and are not for \"android\" now perform a LocalServices lookup of PackageManagerInternal and a getSystemUiServiceComponent() call before the comparison."
     ],
     "preconditions": [
      "Caller must be able to reach the package-hiding path of PackageManagerService; the commit message indicates testing with a custom DPC and that the issue applies even inside a managed profile.",
      "Target package must be the SystemUI package as returned by PackageManagerInternal.getSystemUiServiceComponent()."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 36348592fd27 in android-security-14.0.0_r22, identical diff at a different line offset (5765). Per-major check at android-security-14.0.0_r29 reports only 1/3 lines of the patched region present, inconclusive on its own (see unknowns).",
      "15": "same fix: platform/frameworks/base bd7578b738a0 in android-security-15.0.0_r10 (double cherry-pick, Change-Id I847cb6d69db4924b2b3ddb741ac61a8f065e7c78). Per-major check at android-security-15.0.0_r17 reports 1/3 lines present, inconclusive.",
      "16": "same fix: platform/frameworks/base 4e1be9d5eb9b in android-security-16.0.0_r1, identical diff at line 5957. Per-major check at android-security-16.0.0_r8 reports 1/3 lines present, inconclusive.",
      "17": "not affected (not listed by the bulletin); at android-security-17.0.0_r1 2/3 lines of the patched region are present, the material does not show the code."
     },
     "backport_notes": [
      "Self-contained 5-line change; it relies on PackageManagerInternal exposing getSystemUiServiceComponent(), and since the diffs add no imports, LocalServices and PackageManagerInternal must already be resolvable in PackageManagerService.java on 14, 15 and 16. On a fork, locate the existing `\"android\".equals(packageName)` guard with the \"Cannot hide package\" log and extend it the same way."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact enclosing method name is not shown in the hunk header or the provided code context.",
      "Why the per-major line match at the latest security tags is only 1/3 for Android 14, 15 and 16 (possible later rewording or refactor of the region) is not shown; the material does not include the current code at those tags.",
      "Whether other package-state mutation paths for SystemUI (suspend, disable, component enabled-setting) are guarded is not shown in the material.",
      "Whether the Android 17 code contains the SystemUI guard is not shown (only a 2/3 line match).",
      "The provided index results (AdServices test, libcore charset tests) are unrelated to this change and give no caller information."
     ],
     "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-48539": {
     "cve": "CVE-2025-48539",
     "summary": "In AclArbiter::SendPacketToPeer (system/stack/arbiter/acl_arbiter.cc), the failure-path warning log now reads the length from the rust::Vec argument (buffer.size()) instead of from the BT_HDR p_buf, which L2CA_SendFixedChnlData may already have freed.",
     "component": "platform/packages/modules/Bluetooth, system/stack/arbiter (ACL arbiter that sends Rust-supplied packets on the L2CAP ATT fixed channel)",
     "weakness": "Use after free (read of a freed heap buffer in a log statement)",
     "root_cause": "AclArbiter::SendPacketToPeer allocates a BT_HDR p_buf with osi_malloc, copies the Rust-supplied bytes into it, and passes it to L2CA_SendFixedChnlData(L2CAP_ATT_CID, p_tcb->peer_bda, p_buf). According to the commit message, this intermediate call may free the buffer. When the call does not return L2CAP_DW_SUCCESS, the original code then read p_buf->len inside log::warn, which could read freed memory.",
     "fix_analysis": [
      "system/stack/arbiter/acl_arbiter.cc:AclArbiter::SendPacketToPeer: on the L2CA_SendFixedChnlData failure path, the log::warn argument changes from p_buf->len to buffer.size(). buffer is the ::rust::Vec<uint8_t> passed by value into the function and has not been freed at this point. p_buf->len was set from buffer.size() a few lines earlier, so the logged value is the same and p_buf is no longer read after the send call.",
      "No other code is touched. The allocation, the copy, the offset/len setup and the L2CA_SendFixedChnlData call are unchanged."
     ],
     "completeness": [
      "In SendPacketToPeer, after L2CA_SendFixedChnlData returns, p_buf is no longer referenced on either branch.",
      "The fix covers only this call site in acl_arbiter.cc. Other callers of L2CA_SendFixedChnlData are not examined in the material.",
      "In a TARGET_FLOSS build the function returns early at the top, so that build is unaffected either way."
     ],
     "behavior_changes": [
      "None functionally: the warning logs the same length value (buffer.size() equals the p_buf->len set earlier). Only the source of the value changes."
     ],
     "preconditions": [
      "A GATT/ATT LE connection whose tGATT_TCB still exists (gatt_get_tcb_by_idx returns non-null) when the Rust layer sends a packet through SendPacketToPeer. The free function SendPacketToPeer in acl_arbiter.cc posts that call to the main thread.",
      "L2CA_SendFixedChnlData on L2CAP_ATT_CID must return a non-success result, so that the warning branch is taken.",
      "Not a TARGET_FLOSS build (the function returns immediately there)."
     ],
     "per_major": {
      "14": "not affected per the bulletin. At android-security-14.0.0_r29, 0/5 lines of the patched region are present.",
      "15": "same fix: commit c881220f499e in android-security-15.0.0_r10 (cherry-pick of 243d7484e597), a one-line change from p_buf->len to buffer.size(). At android-security-15.0.0_r17, 4/5 lines of the region are present, in the fixed form.",
      "16": "same fix, adapted to reformatted code: commit 33be5d45cb27 in android-security-16.0.0_r1. It is the same one-line change, in code that uses stack::l2cap::get_interface().L2CA_SendFixedChnlData and tL2CAP_DW_RESULT::SUCCESS. At android-security-16.0.0_r8, 4/5 lines of the region are present.",
      "17": "not affected per the bulletin (not listed). At android-security-17.0.0_r1, 2/5 lines of the patched region are present. The material does not show the content there."
     },
     "backport_notes": [
      "A single one-line change with no dependencies. For trees with the L2CA_SendFixedChnlData(...) != L2CAP_DW_SUCCESS form, use the Android 15 diff. For trees with the stack::l2cap::get_interface() / tL2CAP_DW_RESULT::SUCCESS form, use the Android 16 diff. In both cases, replace p_buf->len with buffer.size() in the warn call."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the L2CA_SendFixedChnlData implementation, so the exact conditions under which it frees p_buf are not visible.",
      "The exact state of acl_arbiter.cc on Android 17 (2/5 lines matching) is not shown.",
      "Why Android 14 is unaffected (file absent or different code) is not shown beyond the 0/5 line match."
     ],
     "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-48540": {
     "cve": "CVE-2025-48540",
     "summary": "Three commits in libs/binder/RpcState.cpp make RPC Binder explicitly terminate the session (session->shutdownAndWait(false)) before returning an error on unrecoverable command errors: CommandData allocation failure in waitForReply and processTransact, a bad object-table span in a transaction, and RPC_COMMAND_TRANSACT received when the command type is restricted. Previously these paths returned an error and left the session alive.",
     "component": "platform/frameworks/native, libbinder RPC Binder (libs/binder/RpcState.cpp)",
     "weakness": "Improper handling of exceptional conditions in a non-self-synchronizing wire protocol: error paths returned without ending the session",
     "root_cause": "RpcState reads commands from a byte stream that, per the comment in processCommand, \"is not self synchronizing\". Several error paths returned a status code without shutting down the session: (1) RpcState::waitForReply and RpcState::processTransact returned NO_MEMORY when the CommandData allocation failed, before the body had been read; (2) the transaction path at the processTransactInternalTailCall label returned BAD_VALUE when objectTableBytes->reinterpret<const uint32_t>() failed, even though the log message said 'Terminating!'; (3) processCommand returned BAD_TYPE when RPC_COMMAND_TRANSACT arrived while type != CommandType::ANY. The commit messages call these errors \"not recoverable\" and say \"if we run out of memory, the wire protocol gets messed up\".",
     "fix_analysis": [
      "Commit ba4ea3598e6d 'shutdown on ENOMEM' (RpcState.cpp blob fe6e1a3318 -> 12e0cfc00e): in libs/binder/RpcState.cpp, RpcState::waitForReply and RpcState::processTransact now call (void)session->shutdownAndWait(false) before returning NO_MEMORY when CommandData is not valid(). The added comment references b/404210068 and says the whole body would have to be read and a reply sent to recover gracefully.",
      "Commit 570e2d6e29ee 'shutdown on SPAN error' (blob 12e0cfc00e -> b06c050dfc): in libs/binder/RpcState.cpp at the processTransactInternalTailCall label, the branch where objectTableBytes->reinterpret<const uint32_t>() yields no value ('Bad object table size ... Terminating!') now calls (void)session->shutdownAndWait(false) before return BAD_VALUE.",
      "Commit 7fb4755c9d93 'clearer errors for wrong transact type' (blob b06c050dfc -> 1f87a07494): in libs/binder/RpcState.cpp:RpcState::processCommand, the case RPC_COMMAND_TRANSACT with type != CommandType::ANY now logs 'CommandType %d, but got RPC command %d.' and calls (void)session->shutdownAndWait(false) before return BAD_TYPE. This matches the existing unknown-command path, which already shut the session down.",
      "Return codes are unchanged at all sites. The functional additions are the session shutdown and one ALOGE."
     ],
     "completeness": [
      "Covers: allocation failure in both waitForReply (reply body) and processTransact (transaction body); object-table reinterpret failure on the transaction side; RPC_COMMAND_TRANSACT received under a restricted CommandType in processCommand.",
      "Leaves out: in RpcState::waitForReply at android-security-15.0.0_r17 (lines 717-725), the reply-side check 'Bad object table size inferred from RpcWireReply ... Terminating!' still returns BAD_VALUE without calling session->shutdownAndWait(false). The adjacent splitOff failure (lines 711-715) does shut down.",
      "The ENOMEM commit message says 'More work needs to be done to ignore transactions in out of memory conditions'. The fix therefore tears the session down in those cases rather than recovering gracefully."
     ],
     "behavior_changes": [
      "An RPC session that hits a CommandData allocation failure, a malformed object-table size in a transaction, or a transact command where only other command types are accepted is now torn down via shutdownAndWait(false) instead of staying connected.",
      "New ALOGE 'CommandType %d, but got RPC command %d.' in processCommand.",
      "Error return values (NO_MEMORY, BAD_VALUE, BAD_TYPE) are unchanged. Callers will now see a shut-down session after these errors."
     ],
     "preconditions": [
      "The peer of an RPC Binder session sends the triggering data. On the processTransact and processCommand paths that is the side sending commands. On the waitForReply path it is the side sending the reply.",
      "The ENOMEM paths need a command.bodySize large enough that the CommandData allocation fails.",
      "The SPAN path needs an object-table byte count that does not reinterpret as uint32_t. The analogous waitForReply code applies this check only when the protocol version is at least RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE.",
      "The wrong-type path needs processCommand to be called with type != CommandType::ANY. The material does not show which callers do this."
     ],
     "per_major": {
      "14": "same fix: resolved in android-security-14.0.0_r22 via 995eb96a80a2 'RPC Binder: shutdown on SPAN error', the same one-line insertion at a different offset (@@ -946). The material does not show the ENOMEM or wrong-transact-type commits on 14. At android-security-14.0.0_r29, 5/5 lines of the patched region are present.",
      "15": "same fix: three commits in android-security-15.0.0_r10 (ba4ea3598e6d ENOMEM, 570e2d6e29ee SPAN, 7fb4755c9d93 wrong transact type), cherry-picked from cba4e3642a8a, dddbc115f88d and 51ed14eebf5a. At android-security-15.0.0_r17, 5/6 lines of the patched region are present, and the r17 code context shows the added shutdown calls in place.",
      "16": "same fix: resolved in android-security-16.0.0_r1 via a9e6d5a3c679 'clearer errors for wrong transact type' (same hunk at @@ -827). At android-security-16.0.0_r8, 5/6 lines of the patched region are present.",
      "17": "not affected per the bulletin. At android-security-17.0.0_r1, 5/5 lines of the patched region are present."
     },
     "backport_notes": [
      "On 15 the blob hashes give this order: ENOMEM (ba4ea3598e6d), then SPAN (570e2d6e29ee), then wrong transact type (7fb4755c9d93). Each commit is a small insertion in libs/binder/RpcState.cpp. Hunk offsets differ between branches (for example @@ -946 on 14 vs @@ -984 on 15)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Beyond the commit statement that the wire protocol 'gets messed up' and the bulletin's EoP/High rating, the material does not show what concretely follows from a session left alive after these errors.",
      "Whether the CVE needs all three commits or only some of them. All three share Bug 416734088, but 14 is shown with only the SPAN commit and 16 with only the wrong-type commit.",
      "What the '5/6 lines present' result on 15 and 16 refers to.",
      "Which callers invoke processCommand with a CommandType other than ANY.",
      "Whether the commits apply cleanly to fork branches."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48541": {
     "cve": "CVE-2025-48541",
     "summary": "FaceSettings now reads the hw_auth_token, sensor id, challenge and EXTRA_USER_ID intent extras only when the hosting SettingsActivity was launched by the Settings package itself. For any other caller, or an unknown one, it ignores those extras and uses the current user.",
     "component": "platform/packages/apps/Settings, biometrics/face (FaceSettings fragment)",
     "weakness": "Missing validation of the calling package before trusting intent extras (user id and auth token parameters that an external caller can set)",
     "root_cause": "In src/com/android/settings/biometrics/face/FaceSettings.java the fragment always read KEY_TOKEN (\"hw_auth_token\"), BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, BiometricEnrollBase.EXTRA_KEY_CHALLENGE and Intent.EXTRA_USER_ID from the launching intent, no matter who started the activity. The commit's test launches android.settings.FACE_SETTINGS from a user other than 0 with EXTRA_USER_ID 0, which shows that an external caller could choose the user the face settings screen worked on. The fix comment says these extras were meant only for internal calls from Settings.",
     "fix_analysis": [
      "FaceSettings.java (the initialization code where mUserManager and mFaceManager are obtained): gets the host via (SettingsActivity) requireActivity() and reads activity.getInitialCallingPackage().",
      "If the calling package is null or does not equal activity.getPackageName(), mUserId is set to UserHandle.myUserId(). mToken, mSensorId and mChallenge stay at their defaults (null, -1, 0), and none of the four extras is read.",
      "The four extras are read as before only when the calling package equals the Settings package (comment: 'only allow these extras when called internally by Settings').",
      "The field declarations change from 'private int mSensorId;' / 'private long mChallenge;' to 'private int mSensorId = -1;' / 'private long mChallenge = 0;', so the external-caller path gets the same defaults that the getIntExtra/getLongExtra calls used to supply."
     ],
     "completeness": [
      "Covers all four extras read in this initialization block (token, sensor id, challenge, user id). The gate is a single package-name comparison against the Settings package via SettingsActivity.getInitialCallingPackage().",
      "A null calling package is treated as external, so the check fails closed.",
      "requireActivity() is always cast to SettingsActivity. The diff does not handle the fragment being hosted by a different activity, which would throw ClassCastException. The material does not show whether that can happen.",
      "This commit does not touch other Settings fragments or biometric screens that read EXTRA_USER_ID or token extras. The material does not show whether they have similar logic."
     ],
     "behavior_changes": [
      "When FaceSettings is launched by any package other than Settings, or the calling package is unknown, EXTRA_USER_ID is ignored and the screen always uses the current user.",
      "For these external launches, mToken is null, mSensorId is -1 and mChallenge is 0, whatever extras the caller supplies.",
      "Launches from the Settings package read the extras exactly as before."
     ],
     "preconditions": [
      "An app that can start the face settings screen (for example via the android.settings.FACE_SETTINGS action, as in the commit's test command) and supply intent extras such as android.intent.extra.USER_ID.",
      "A multi-user setup for the cross-user case in the commit's test (launch from a user other than 0)."
     ],
     "per_major": {
      "14": "Same fix (f3cc1073e51e in android-security-14.0.0_r22). The logic is identical; only the context differs (FeatureFactory.getFactory(getContext()) and hunk offsets). 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix (aba2077a6c03 in android-security-15.0.0_r10). 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix (2aa27461a40b in android-security-16.0.0_r1). The logic is identical; only hunk offsets differ. 7/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (Android 17 is not listed). 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-file, self-contained change. It needs SettingsActivity.getInitialCallingPackage() to already exist in the target branch: all three branch commits use it without adding it. The only difference between branches is the FeatureFactory context line (getFactory(getContext()) on 14, getFeatureFactory() on 15 and 16), and it does not need to change."
     ],
     "confidence": "high",
     "unknowns": [
      "The diff hunks do not show the name of the method that contains the patched block.",
      "What the face settings screen could actually do to another user's data with a caller-supplied EXTRA_USER_ID. The bulletin rates it High EoP but gives no detail.",
      "How the fragment behaves when mToken is null and mChallenge is 0 (the code that handles a missing token is not shown).",
      "Which line of the 8-line patched region differs in the latest security tags (7/8 present on all branches).",
      "Whether face authentication hardware or other device conditions are needed to reach the screen."
     ],
     "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-48542": {
     "cve": "CVE-2025-48542",
     "summary": "Adds per-account storage accounting to AccountManagerService. Auth-token and user-data writes are refused once an account's share of a 30,000,000 (STORAGE_LIMIT_PER_USER) per-user budget is used up. Password length is capped at 1,000,000 chars when an account is added and when a password is set.",
     "component": "platform/frameworks/base, services/core AccountManagerService (accounts DB)",
     "weakness": "Missing resource limit (uncontrolled growth of persisted account data; no bound on auth token, user data or password size)",
     "root_cause": "Before the fix, AccountManagerService wrote auth tokens, user data values (setUserdataInternal) and passwords into the accounts database and the in-memory caches (userDataCache, authTokenCache). There was no limit on value size or on the total amount stored per account or per user. The surrounding code shows an existing check that rejects account names longer than 200 chars. Data could therefore grow without bound, which the bulletin classifies as a High-severity DoS.",
     "fix_analysis": [
      "AccountManagerService.java: adds the constants MAXIMUM_PASSWORD_LENGTH = 1000*1000 and STORAGE_LIMIT_PER_USER = 30*1000*1000. Adds a new UserAccounts field mCacheSizeForAccount (Map<Account,Integer>, protected by cacheLock) that holds an estimated size per account.",
      "AccountManagerService.computeEntrySize: the size of one entry is key.length() + value.length() + 20. A null key or value counts as 1. Sizes are Java String.length() units.",
      "AccountManagerService.recomputeCacheSizeForAccountLocked: adds up computeEntrySize over the account's userDataCache and authTokenCache maps and stores the total in mCacheSizeForAccount.",
      "AccountManagerService.shouldBlockDatabaseWrite, in order: (1) adds the new entry's size to the running estimate. (2) Allows the write if the estimate is below STORAGE_LIMIT_PER_USER/100. (3) Otherwise counts the accounts in accountCache and allows the write if the estimate is below STORAGE_LIMIT_PER_USER/numberOfAccounts. (4) Otherwise recomputes the size via recomputeCacheSizeForAccountLocked and adds the entry again. (5) Blocks (returns true and logs a warning) only if the result is still at or above the per-account share.",
      "Auth-token save path (hunk at old line 2683, likely saveAuthTokenToDatabase): calls shouldBlockDatabaseWrite under cacheLock before beginTransaction. Inside the transaction, after deleteAuthtokensByAccountIdAndType, it returns false without setTransactionSuccessful when authToken != null and the write is blocked, so insertAuthToken is skipped.",
      "AccountManagerService.setUserdataInternal: holding dbLock, then cacheLock, returns early ('fail silently') when value != null and shouldBlockDatabaseWrite is true. This happens before any DB transaction.",
      "Add-account path (hunk after the 200-char name check): returns false when the password is longer than MAXIMUM_PASSWORD_LENGTH. Set-password path (hunk at old line 2800): returns early when the new password is longer than MAXIMUM_PASSWORD_LENGTH.",
      "Bookkeeping: writeUserDataIntoCacheLocked and writeAuthTokenIntoCacheLocked recompute the size when they first fill the cache from the DB. readAuthTokenInternal and the user-data read path recompute when they fill the cache. The rename path recomputes for renamed accounts. The account-validation path and the account cache-removal path remove the account's entry from mCacheSizeForAccount."
     ],
     "completeness": [
      "Covers the two per-key write paths into the DB (auth tokens and user data) with a size budget, and limits password length on add and set.",
      "Passwords are only length-limited (1,000,000 chars) and are not counted in mCacheSizeForAccount.",
      "The budget is divided by the current number of accounts (STORAGE_LIMIT_PER_USER / numberOfAccounts). The fast path always allows up to STORAGE_LIMIT_PER_USER/100 per account. A comment assumes 100 is the upper bound on the number of accounts, but the diff does not itself enforce a maximum.",
      "A code comment says the running estimate is imprecise for updates to existing keys. It adds the full entry size on every attempt, including blocked ones. Recomputation via recomputeCacheSizeForAccountLocked happens only when the threshold is reached, and it counts only what is currently in userDataCache and authTokenCache.",
      "Sizes are measured in Java String.length() units, not encoded bytes.",
      "Removal writes (null value or null authToken) are never blocked.",
      "Other per-account data (for example visibility entries, previousNameCache) is not included in the accounting."
     ],
     "behavior_changes": [
      "Auth-token writes are dropped and the save path returns false once the account's share of the budget is reached, with Log.w 'Too much storage is used - block token update'.",
      "User-data writes are silently ignored under the same condition, with Log.w 'Too much storage is used - block user data update'.",
      "Adding an account fails (returns false, Log.w 'password is too long') when the password is longer than 1,000,000 chars. Setting a password is ignored (Log.w 'New password is too long') under the same condition.",
      "New Log.w 'Account of type=... uses too much storage' when the limit is hit.",
      "Small extra CPU cost: a full recomputation over userDataCache and authTokenCache entries when the threshold is reached and whenever these caches are filled."
     ],
     "preconditions": [
      "The caller must be able to reach the AccountManagerService auth-token, user-data, add-account or set-password write paths for an account. Which callers are allowed is not shown in the material.",
      "For the add-account path, the user must be unlocked (the existing isLocalUnlockedUser check is shown in context)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 9a4d7193f893 in android-security-14.0.0_r22, identical added lines (line offsets differ); 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 7ed50e5b5d56 in android-security-15.0.0_r10 (cherry-pick of 6b8138ca5279); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base 50ae729881da in android-security-16.0.0_r1. Identical added lines; only the context differs (setUserdataInternal takes an extra callingUid parameter). 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 that touches only AccountManagerService.java. It relies on the existing userDataCache, authTokenCache and accountCache fields and on the cacheLock/dbLock locks. Forks that restructured UserAccounts or the auth-token save transaction must keep the shouldBlockDatabaseWrite checks under cacheLock and before insertAuthToken. The bulletin lists Android 13 as affected, but the material contains no Android 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The Android 13 fix commit is not in the material.",
      "Whether a maximum number of accounts (the '100' assumed in shouldBlockDatabaseWrite) is enforced elsewhere is not shown.",
      "The exact trigger scenario and the API used in the original report are not shown; the material shows only that the bulletin classifies it as a High-severity DoS.",
      "Which callers are allowed to reach the patched write paths (for example, whether they are restricted to the authenticator that owns the account type) is not shown.",
      "The finally/endTransaction handling of the auth-token transaction is not shown, so whether the preceding delete is rolled back when the write is blocked is not confirmed.",
      "Several method names (saveAuthTokenToDatabase, addAccountInternal, setPasswordInternal, renameAccount, readUserDataInternal, removeAccountFromCacheLocked) are inferred and do not appear in the diff hunks."
     ],
     "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-48543": {
     "cve": "CVE-2025-48543",
     "summary": "Adds an IsInstantiable() check to the ART JNI entry points AllocObject, NewObjectV and NewObjectA, so that allocating an instance of an abstract class or interface throws java.lang.InstantiationException instead of creating an object. It also reduces the CheckJNI-only check to an array-class check.",
     "component": "platform/art, runtime JNI (runtime/jni/jni_internal.cc, runtime/jni/check_jni.cc)",
     "weakness": "Missing validation: JNI object allocation did not check that the target class is instantiable (not abstract and not an interface).",
     "root_cause": "In runtime/jni/jni_internal.cc, the JNI functions AllocObject, NewObjectV and NewObjectA returned early only when the decoded class was null. After that they went straight to allocation (String special case, then generic path) without checking whether the class is abstract or an interface. The only such check was in check_jni.cc (ScopedCheck::CheckInstantiableNonArray, using IsInstantiableNonArray()), which runs only when CheckJNI is enabled. Without CheckJNI, native code could therefore obtain an instance of an abstract class or interface. One example is the deserialization path exercised by test 863-serialization, where the serialized stream names java.text.DateFormat. The test's info.txt says: \"JNI::NewObject where we forgot to check if a class is instantiable.\"",
     "fix_analysis": [
      "runtime/jni/jni_internal.cc: JNI::AllocObject. After the `c == nullptr` check and before the IsStringClass special case, the fix adds `if (UNLIKELY(!c->IsInstantiable()))`. That branch calls soa.Self()->ThrowNewExceptionF(\"Ljava/lang/InstantiationException;\", \"Can't instantiate %s %s\", interface|abstract class, descriptor) and returns nullptr.",
      "runtime/jni/jni_internal.cc: JNI::NewObjectV. The same IsInstantiable() check is inserted at the same position (after the null check, before the String.<init> to StringFactory replacement).",
      "runtime/jni/jni_internal.cc: JNI::NewObjectA. The same IsInstantiable() check is inserted at the same position.",
      "runtime/jni/check_jni.cc: ScopedCheck::CheckInstantiableNonArray is renamed to CheckNonArray, and its condition changes from `!c->IsInstantiableNonArray()` to `c->IsArrayClass()`. CheckJNI now aborts only for array classes and lets abstract classes and interfaces reach the base implementation, where the new runtime check throws a Java exception. The three CheckJNI wrappers (AllocObject, NewObjectV, NewObjectA) now call CheckNonArray.",
      "test/863-serialization: a new regression test deserializes a stream that names an abstract class and expects an InvalidClassException whose cause is an InstantiationException."
     ],
     "completeness": [
      "The runtime check is added to all three allocation entry points in the diff (AllocObject, NewObjectV, NewObjectA), and it runs in all builds, not only under CheckJNI.",
      "The check sits after the null-class check and before any allocation, so neither the String path nor the generic allocation path can be reached for a non-instantiable class.",
      "The runtime check uses IsInstantiable(), while the old CheckJNI check used IsInstantiableNonArray(). Array-class rejection remains only in CheckJNI (CheckNonArray), so the material shows no rejection of array classes in these entry points when CheckJNI is off. The material does not show whether IsInstantiable() itself excludes array classes.",
      "The variadic JNI::NewObject is not changed by the diff, and the material does not show its body. Whether it is covered depends on whether it delegates to NewObjectV."
     ],
     "behavior_changes": [
      "JNI AllocObject, NewObjectV and NewObjectA now throw java.lang.InstantiationException (\"Can't instantiate interface|abstract class <descriptor>\") and return nullptr when given an abstract class or interface, in all builds.",
      "Under CheckJNI, passing an abstract class or interface no longer aborts the process. Only array classes still trigger the CheckJNI abort (\"can't make objects of type ...\").",
      "Java deserialization of a stream that names an abstract class now fails with an InvalidClassException caused by an InstantiationException, as the new test expects."
     ],
     "preconditions": [
      "Native code (app JNI or platform native code) calls the JNI AllocObject, NewObject, NewObjectV or NewObjectA function with a jclass that refers to an abstract class or interface.",
      "The regression test shows one route from the platform: java.io.ObjectInputStream.readObject() on a crafted serialized stream whose class descriptor names an abstract class (java.text.DateFormat in the test). The libcore native glue that leads to the ART JNI allocation path is not in the material."
     ],
     "per_major": {
      "14": "same fix: platform/art caeef036e31b in android-security-14.0.0_r22, same content (only hunk line offsets differ). At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "same fix: platform/art 444fc40dfb04 in android-security-15.0.0_r10, a cherry-pick of the internal change. At android-security-15.0.0_r17, 6/8 lines of the patched region are present.",
      "16": "same fix: platform/art 3eb64751e5a7 in android-security-16.0.0_r1, same content. At android-security-16.0.0_r8, 6/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": [
      "This is a single self-contained commit, and the jni_internal.cc and check_jni.cc hunks should be taken together. With only the jni_internal.cc hunks, CheckJNI would still abort on abstract classes before the new exception path is reached. With only the check_jni.cc hunks, CheckJNI would lose the abstract-class check and nothing would replace it at runtime."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete security consequence of holding an instance of an abstract class or interface (what made this an EoP) is not shown. The bulletin only gives High/EoP.",
      "The exact definition of mirror::Class::IsInstantiable() (in particular how it treats array and primitive classes) is not in the material.",
      "The libcore/native path by which ObjectInputStream reaches JNI AllocObject/NewObject is not shown. Only the Java-level test demonstrates it.",
      "Whether JNI::NewObject (the varargs variant) delegates to NewObjectV in these branches is not shown in the diff.",
      "Whether the AllocObject, NewObjectV and NewObjectA bodies call EnsureInitialized before the inserted check is not shown in the diff context.",
      "Which 2 of the 8 patched-region lines are missing from each latest tag is not stated."
     ],
     "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-48545": {
     "cve": "CVE-2025-48545",
     "summary": "AccountManagerService.isSystemUid() now returns false immediately for SDK sandbox UIDs instead of resolving the UID's packages and treating the caller as system if any resolved package carries FLAG_SYSTEM.",
     "component": "platform/frameworks/base, services/core AccountManagerService (accounts)",
     "weakness": "Improper privilege/identity check (system-UID check satisfiable by an SDK sandbox caller)",
     "root_cause": "isSystemUid(int callingUid) in services/core/java/com/android/server/accounts/AccountManagerService.java decided whether a caller is \"system\" only by calling mPackageManager.getPackagesForUid(callingUid) (with calling identity cleared) and returning true if any returned package has ApplicationInfo.FLAG_SYSTEM set. It had no special handling for SDK sandbox UIDs (Process.isSdkSandboxUid). The fix (commit title: \"Don't allow SdkSandbox to bypass systemUid check\") implies that an SDK sandbox caller could pass this check before the fix; the material does not show the exact mechanism. The bulletin classifies the issue as information disclosure (ID), High severity.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountManagerService.java:isSystemUid - adds an early guard at the top of the method: if Process.isSdkSandboxUid(callingUid) is true, return false before any package lookup. The existing getPackagesForUid / FLAG_SYSTEM logic is unchanged for all other UIDs.",
      "The guard runs before Binder.clearCallingIdentity(), so sandbox callers never reach the identity clearing or the PackageManager query."
     ],
     "completeness": [
      "The fix covers exactly one helper, isSystemUid(), and only the SDK sandbox UID case. The underlying heuristic (any FLAG_SYSTEM package for the UID means a system caller) remains in place for non-sandbox UIDs.",
      "The material does not show the callers of isSystemUid (the index search found none among top results), so the provided code does not let us list which AccountManagerService entry points this change hardens.",
      "The neighbouring permission check shown in context (UserHandle.getAppId(callerUid) == Process.SYSTEM_UID followed by a grants-count lookup) is a separate path and this commit does not modify it."
     ],
     "behavior_changes": [
      "isSystemUid() never treats a caller with an SDK sandbox UID as system. Any AccountManagerService path gated on that helper now takes the non-system branch for such callers.",
      "No new exception or error code is introduced; the method simply returns false earlier."
     ],
     "preconditions": [
      "The caller's Binder UID must be an SDK sandbox UID (Process.isSdkSandboxUid(callingUid) true).",
      "The caller must reach an AccountManagerService code path that consults isSystemUid(callingUid); the material does not show the specific entry points."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 30dc386d4a57 in android-security-14.0.0_r22 adds the identical 3-line guard to isSystemUid (hunk at line ~5985); 3/3 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 66ac17909252 in android-security-15.0.0_r10 (cherry-pick of d400709160c8), hunk at line ~6080; 3/3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c21f79312d3f in android-security-16.0.0_r1, hunk at line ~6146; 3/3 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the 3-line guard is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit, self-contained 3-line change that depends on Process.isSdkSandboxUid, which the 14, 15 and 16 fixes use. Apply it at the top of isSystemUid, before the Binder.clearCallingIdentity() call. The hunk is identical across 14, 15 and 16 apart from line-number offsets."
     ],
     "confidence": "high",
     "unknowns": [
      "Which AccountManagerService entry points call isSystemUid, and therefore what account data could be disclosed; the material does not include the callers.",
      "The exact mechanism by which an SDK sandbox UID could satisfy the FLAG_SYSTEM package check is not shown in the material.",
      "Whether a fix was applied on the Android 13 branch, and whether Process.isSdkSandboxUid is available there (the bulletin lists 13 as affected, but the material has no 13 commit)."
     ],
     "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-48546": {
     "cve": "CVE-2025-48546",
     "summary": "SafeActivityOptions now rejects, with a SecurityException, activity launches whose ActivityOptions carry setLaunchWindowingMode(WINDOWING_MODE_PINNED), closing a path by which a caller could request a launch directly into the pinned (picture-in-picture) windowing mode.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (services/core/java/com/android/server/wm/SafeActivityOptions.java)",
     "weakness": "Missing authorization check (unvalidated caller-supplied launch windowing mode)",
     "root_cause": "The permission-checking path in SafeActivityOptions validated several caller-supplied ActivityOptions fields (for example the launch activity type, gated on isSystemOrSystemUI) but did not examine the launch windowing mode. A caller could pass ActivityOptions with setLaunchWindowingMode(WINDOWING_MODE_PINNED). The deleted TaskLaunchParamsModifierTests (testKeepsPictureInPictureLaunchModeInOptions, testKeepsPictureInPictureLaunchModeWithBoundsInOptions) show that TaskLaunchParamsModifier kept that PINNED mode in the computed launch params. Per the fix comment, the intended public path is ActivityOptions#makeLaunchIntoPip.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/SafeActivityOptions.java, in the permission-check method (hunk at line ~391/392, immediately before the getLaunchActivityType / isSystemOrSystemUI check): imports WindowConfiguration.WINDOWING_MODE_PINNED and adds a check with no caller condition: if options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED, log a 'Permission Denial ... with setLaunchWindowingMode=PINNED' message via Slog.w and throw SecurityException. The comment directs callers to ActivityOptions#makeLaunchIntoPip instead.",
      "services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java: adds a case that builds ActivityOptions.makeBasic() with setLaunchWindowingMode(WINDOWING_MODE_PINNED) and calls verifySecureExceptionThrown.",
      "services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java: removes testKeepsPictureInPictureLaunchModeInOptions and testKeepsPictureInPictureLaunchModeWithBoundsInOptions, which asserted the old behavior of preserving PINNED from options."
     ],
     "completeness": [
      "The check sits in the SafeActivityOptions permission-check path, so it covers launches whose options are validated there. The check itself has no caller-UID condition; whether privileged callers reach this path is not shown.",
      "Only WINDOWING_MODE_PINNED is rejected; other launch windowing modes set via setLaunchWindowingMode are not restricted by this change.",
      "TaskLaunchParamsModifier itself is not changed (only its tests were deleted), so enforcement relies on the SafeActivityOptions gate. The material does not show whether any launch path consumes ActivityOptions without passing through this check."
     ],
     "behavior_changes": [
      "An activity start whose ActivityOptions are validated through this check and carry launch windowing mode WINDOWING_MODE_PINNED now fails with SecurityException and a Slog.w 'Permission Denial' log line.",
      "Callers that need launch-into-PiP must use ActivityOptions#makeLaunchIntoPip (per the fix comment).",
      "Two TaskLaunchParamsModifierTests cases asserting the old PINNED-preserving behavior were removed."
     ],
     "preconditions": [
      "Caller starts an activity with an ActivityOptions bundle in which setLaunchWindowingMode(WINDOWING_MODE_PINNED) was set, and the launch is processed through the SafeActivityOptions permission check in system_server."
     ],
     "per_major": {
      "14": "same fix: commit 41bdf43266da in android-security-14.0.0_r22 applies the identical SafeActivityOptions.java change (hunk at line ~407/408) and the TaskLaunchParamsModifierTests deletions; the shown diff contains no SafeActivityOptionsTest addition. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 8b660e887005 in android-security-15.0.0_r10 (RESTRICT AUTOMERGE cherry-pick of 88496615b1c7); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 3668e1d4bbec in android-security-16.0.0_r1, identical source and test changes (hunk at line ~372/373); 8/8 lines present 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 self-contained commit: one import plus an 11-line block inserted right before the 'final int activityType = options.getLaunchActivityType();' check in SafeActivityOptions.java. It applied to 14, 15 and 16 with only line-offset differences; the test file changes are not needed for the fix itself."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the SafeActivityOptions method containing the new check is not shown in the diff hunk header.",
      "Whether privileged (system/SystemUI) callers reach this check, and what permissions were required before the fix, is not shown.",
      "The concrete security impact of launching into PINNED mode (what the bulletin's EoP consisted of) is not described in the material.",
      "Whether any activity-launch path consumes the launch windowing mode without going through SafeActivityOptions is not shown."
     ],
     "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-48547": {
     "cve": "CVE-2025-48547",
     "summary": "PermissionController now calls PermissionManager.stopOneTimePermissionSession(packageName) on revoke only when no permission of the package remains granted as one-time, instead of stopping the package-wide session whenever a single one-time permission (group) is revoked.",
     "component": "platform/packages/modules/Permission, PermissionController (one-time permission session handling in AppPermissionGroup.java and KotlinUtils.kt)",
     "weakness": "Incorrect state handling: a per-package one-time permission session was ended while other one-time grants in the same package were still active (no check before stopping the session).",
     "root_cause": "Per the commit message, there is a single one-time permission session per package covering all one-time grants. In AppPermissionGroup.java, the revoke path called stopOneTimePermissionSession(packageName) unconditionally in its else branch. In KotlinUtils.kt, the revoke path called anyPermsOfPackageOneTimeGranted(app, packageInfo, newGroup) with the group argument. That helper (a) returned true early if group.isOneTime, (b) skipped every permission belonging to the current group, (c) masked the flags with FLAG_PERMISSION_ONE_TIME before testing FLAG_PERMISSION_REVOKED_COMPAT, so that test always passed, and (d) tested the granted state with an equality comparison, requestedPermissionsFlags[idx] == PackageManager.PERMISSION_GRANTED, rather than the PackageInfo.REQUESTED_PERMISSION_GRANTED bit. Per the commit message, the net effect was that revoking one one-time permission stopped the only session running for all one-time grants.",
     "fix_analysis": [
      "AppPermissionGroup.java (method containing the stopOneTimePermissionSession call, after the Binder.restoreCallingIdentity finally block; the enclosing method name is not shown): the unconditional else branch becomes `else if (!anyPermsOfPackageOneTimeGranted(getApp()))`, so the session is stopped only when no permission in the package is still granted one-time.",
      "AppPermissionGroup.java: new private helper anyPermsOfPackageOneTimeGranted(PackageInfo). It returns false if requestedPermissions or requestedPermissionsFlags is null. Otherwise it walks all requested permissions, skips entries without PackageInfo.REQUESTED_PERMISSION_GRANTED, calls mPackageManager.getPermissionFlags(perm, packageName, getUser()), and returns true if FLAG_PERMISSION_ONE_TIME is set and FLAG_PERMISSION_REVOKED_COMPAT is clear. It is annotated @SuppressLint(\"MissingPermission\"), and static imports are added for FLAG_PERMISSION_ONE_TIME and FLAG_PERMISSION_REVOKED_COMPAT.",
      "KotlinUtils.kt (revoke path computing wasOneTime / newGroup; function name not shown): the call becomes anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo). Without the group argument, the current group's permissions are no longer excluded from the scan.",
      "KotlinUtils.kt: anyPermsOfPackageOneTimeGranted rewritten. The group parameter, the early `group?.isOneTime == true` return and the skip of group members are removed. The granted test now uses `requestedPermissionsFlags[index] and PackageInfo.REQUESTED_PERMISSION_GRANTED`. Flags are no longer pre-masked with FLAG_PERMISSION_ONE_TIME, so the FLAG_PERMISSION_REVOKED_COMPAT test now has an effect. The user is always UserHandle.getUserHandleForUid(packageInfo.uid). Adds import android.content.pm.PackageInfo."
     ],
     "completeness": [
      "Both PermissionController call sites of stopOneTimePermissionSession shown in the diff (the Java AppPermissionGroup model and the Kotlin KotlinUtils revoke path) are now gated by a package-wide scan with equivalent logic. Only the Java helper adds a null check on the requested-permission arrays.",
      "The check covers every requested permission of the package, not just the group being revoked.",
      "The Java helper takes the granted bit from the getApp() PackageInfo; the Kotlin helper takes it from newGroup.packageInfo (LightPackageInfo). Both read the one-time and revoked-compat bits from a live getPermissionFlags query. The material does not show whether these PackageInfo snapshots reflect the revocation just performed.",
      "The diff touches only the two PermissionController files; no system-server-side code is changed."
     ],
     "behavior_changes": [
      "stopOneTimePermissionSession(packageName) is no longer called while another permission of the same package is still granted with FLAG_PERMISSION_ONE_TIME and without FLAG_PERMISSION_REVOKED_COMPAT. The session keeps running until the last one-time grant is revoked.",
      "On the Java path, reaching the former else branch now triggers one getPermissionFlags query per granted requested permission. The Kotlin helper now queries flags only for granted permissions.",
      "The Kotlin helper's optional LightAppPermGroup parameter was removed (private function, no external API impact)."
     ],
     "preconditions": [
      "A package holds more than one permission granted as one-time, so a single one-time session covers several grants.",
      "One of those one-time permissions is revoked through PermissionController, via the AppPermissionGroup.java or KotlinUtils.kt revoke path shown in the diff.",
      "How an untrusted party could trigger this revoke path is not shown in the material (unknown)."
     ],
     "per_major": {
      "14": "adapted fix: commit 122e39ced57a in android-security-14.0.0_r22. The Java change is the same. The Kotlin change is adapted to the Android 14 code: no deviceId/ContextCompat.createDeviceContext in the caller, no @Suppress(\"MissingPermission\") on the helper, different line numbers. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 5dca0ccb26f2 in android-security-15.0.0_r10 (cherry-pick of the internal change); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 78fc913decb3 in android-security-16.0.0_r1. Same logic as 15; only the Kotlin line numbers and the trailing comma on the removed parameter differ. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected: Android 17 is not listed by the bulletin. The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. Take both files together, since the Java and Kotlin paths each call stopOneTimePermissionSession on their own. On Android 14, the Kotlin caller passes group.packageName directly to stopOneTimePermissionSession (no device context); the Kotlin change there is the condition line, the PackageInfo import and the helper rewrite. PermissionController ships in the Permission mainline module, so the fix can also arrive via a module update (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact names of the enclosing revoke methods in AppPermissionGroup.java and KotlinUtils.kt (not in the diff context).",
      "What happens on the system-server side when the session is stopped while other one-time grants remain. Only the bulletin classification (EoP, High) is known.",
      "Whether the PackageInfo snapshots used by the helpers are refreshed after the revocation in both paths.",
      "The concrete effect of the old equality-based granted check on which permissions were counted."
     ],
     "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-48548": {
     "cve": "CVE-2025-48548",
     "summary": "Makes the audioserver recording silenced state and the AppOps started-op state consistent: startRecording now returns the exact permission result, a recording client is un-silenced only when the op is actually GRANTED, soft-denied starts use a rejected noteOp instead of leaving a paused started op, virtual sources use only their own op, and SystemUI suppresses the mic indicator only when all recordings of a uid are silenced.",
     "component": "platform/frameworks/av (libmediautils ServiceUtilities, audiopolicy service AudioPolicyService/AudioRecordClient) plus platform/frameworks/base (SystemUI AppOpsControllerImpl, PermissionManagerService)",
     "weakness": "Logic error / inconsistent authorization state: a soft-denied AppOps result was treated as success, so the recording client was un-silenced and start/finish op refcounts diverged from the AppOps side (incorrect authorization), with a companion indicator-suppression logic error in SystemUI.",
     "root_cause": "In media/utils/ServiceUtilities.cpp, checkRecordingInternal/startRecording collapsed the PermissionChecker result into a bool that was true for anything other than PERMISSION_HARD_DENIED, so a soft-denied start (an AppOps restriction in place, where per the commit message the op is started but left paused) looked like success. AudioPolicyService::startInput (AudioPolicyInterfaceImpl.cpp) then always called setAppState_l(client, APP_STATE_TOP), un-silencing the client. stopInput and the startInput failure path always called finishRecording, whether or not an op had been started. This produced a refcount mismatch between the APS notion of silenced and AppOps. Per the commit message, a paused op left behind by SOFT_DENY could restart unexpectedly when the restriction was lifted. In addition, startInput exempted FM_TUNER, REMOTE_SUBMIX and ECHO_REFERENCE from the permission result by name, VOICE_DOWNLINK still required RECORD_OP, and OpRecordAudioMonitor also listened for and evaluated OP_RECORD_AUDIO for sources whose own op differed. Finally, SystemUI AppOpsControllerImpl.isAnyRecordingPausedLocked hid the microphone indicator for a uid if any one of its AudioRecordingConfigurations was silenced.",
     "fix_analysis": [
      "frameworks/av media/utils/ServiceUtilities.cpp: checkRecordingInternal now returns int (the PermissionChecker result) instead of bool. When isRecordOpRequired(source) is true and start==true, it first calls checkPermissionForPreflightFromDatasource. Only if that returns PERMISSION_GRANTED does it call checkPermissionForStartDataDeliveryFromDatasource. Otherwise it calls checkPermissionForDataDeliveryFromDatasource (a note that also fails), so op-rejected listeners still fire but no paused started op is left. The non-start path keeps the preflight check.",
      "frameworks/av media/utils/ServiceUtilities.cpp: the new isRecordOpRequired(source) returns false for AUDIO_SOURCE_FM_TUNER, ECHO_REFERENCE, REMOTE_SUBMIX and VOICE_DOWNLINK, and getOpForSource now maps FM_TUNER to OP_NONE. For these sources, checkRecordingInternal returns PERMISSION_GRANTED for OP_NONE; otherwise it calls AppOpsManager::startOpNoThrow on the attributed op only, using resolveCallingPackage. finishRecording mirrors this with AppOpsManager::finishOp. The recordingAllowed wrappers compare the int against PERMISSION_HARD_DENIED. startRecording now returns int, and ServiceUtilities.h is updated and exports isRecordOpRequired.",
      "frameworks/av services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp AudioPolicyService::startInput: stores permitted = startRecording(...) and returns PERMISSION_DENIED only on PERMISSION_HARD_DENIED. The inline FM_TUNER/REMOTE_SUBMIX/ECHO_REFERENCE exemptions are removed. setAppState_l is called with APP_STATE_TOP only when permitted == PERMISSION_GRANTED, otherwise with APP_STATE_IDLE. In the startInput failure path and in AudioPolicyService::stopInput, finishRecording is called only if !client->silenced.",
      "frameworks/av services/audiopolicy/service/AudioPolicyService.cpp AudioPolicyService::setAppState_l: on the un-silence transition, if startRecording(...) != PERMISSION_GRANTED the function returns early. It no longer sets silenced = true and continues.",
      "frameworks/av services/audiopolicy/service/AudioRecordClient.cpp/.h: OpRecordAudioMonitor gains mShouldMonitorRecord (= isRecordOpRequired(attr.source), passed from createIfNeeded). onFirstRef registers the OP_RECORD_AUDIO callback, and checkOp evaluates OP_RECORD_AUDIO, only when mAppOp != OP_RECORD_AUDIO && mShouldMonitorRecord. getTargetSdkForPackageName returns __ANDROID_API_FUTURE__ instead of -1 when getTargetSdkVersionForPackage fails.",
      "frameworks/base services/core/java/com/android/server/pm/permission/PermissionManagerService.java (20e363e22258, preflight check): when skipProxyOperation && !singleReceiverFromDatasource, the first link in the chain gets opMode MODE_ALLOWED instead of a call to appOpsManager.unsafeCheckOpRawNoThrow. This makes preflight consistent with the data-delivery path, which skips checking the first attribution from datasources. The commit message says audioserver implicitly relies on this, because it fails checkOp without a valid Uid/PackageState.",
      "frameworks/base packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java (00344da68fce): isAnyRecordingPausedLocked is renamed to isAllRecordingPausedLocked. It returns false as soon as one AudioRecordingConfiguration for the uid is not silenced, and true only if all are silenced (or mMicMuted is set). It is used in updateActives and updateSensorDisabledStatus. AppOpsControllerTest is updated for multi-config cases."
     ],
     "completeness": [
      "Covers the start, stop, failure and un-silence (setAppState_l) paths in AudioPolicyService: the client is un-silenced only on PERMISSION_GRANTED, and finish happens only if the client is not silenced.",
      "Covers the four non-record-op sources listed in isRecordOpRequired. Any other source still goes through the RECORD_AUDIO PermissionChecker path with its attributed op.",
      "The soft-deny handling is explicitly a workaround: the code comment calls it 'technically racy' between preflight and start and references TODO b/294609684 (removal of the op pause state).",
      "The SystemUI change only alters indicator suppression derived from AudioRecordingConfiguration; a TODO (b/365843152) marks that listening for removal.",
      "The pre-existing isAppOpSource() helper in services/audiopolicy/service/AudioRecordClient.cpp still lists only FM_TUNER, ECHO_REFERENCE and REMOTE_SUBMIX (not VOICE_DOWNLINK) and is not touched by this diff."
     ],
     "behavior_changes": [
      "startRecording() in libmediautils changes its return type from bool to int (the PermissionChecker result). Callers must compare it against PERMISSION_GRANTED / PERMISSION_HARD_DENIED.",
      "A recording whose op is soft-denied at startInput now begins silenced (APP_STATE_IDLE) instead of un-silenced, and a rejected noteOp is emitted instead of a startOp.",
      "AUDIO_SOURCE_FM_TUNER maps to OP_NONE, so no app op is started or finished. ECHO_REFERENCE, REMOTE_SUBMIX and VOICE_DOWNLINK use startOpNoThrow/finishOp on their own op only, and OP_RECORD_AUDIO is no longer checked or monitored for them.",
      "finishRecording is skipped for clients that are silenced at stop or failure time.",
      "getTargetSdkForPackageName treats a package-manager lookup failure as a future SDK level rather than -1.",
      "SystemUI shows the microphone indicator for a uid unless every known AudioRecordingConfiguration of that uid is silenced.",
      "The PermissionManagerService preflight no longer runs unsafeCheckOpRawNoThrow on the first chain link when skipProxyOperation is set and the source is not a singleReceiverFromDatasource."
     ],
     "preconditions": [
      "A client reaches AudioPolicyService::startInput / setAppState_l for a recording whose attribution source is not audioserver, mediaserver, system or root, so checkRecordingInternal consults AppOps.",
      "Per the commit message, the recording op for that client is soft-denied (an AppOps restriction is in place) at start or un-silence time.",
      "For the SystemUI part: a uid with more than one AudioRecordingConfiguration, at least one of them silenced."
     ],
     "per_major": {
      "14": "Resolved via bug_id at android-security-14.0.0_r22. The fix shown is frameworks/base bbd8db54b3c9 ('appop: Finish all when last in chain fail') in PermissionManagerService: during startDataDelivery, when a later chain link fails, it finishes the proxy op already started for the previous link. The material does not show a frameworks/av commit for 14. At android-security-14.0.0_r29 the patched region is fully present (8/8 lines).",
      "15": "Same fix: frameworks/av 37e7f808fad1 plus frameworks/base 00344da68fce (SystemUI) and 20e363e22258 (PermissionManagerService preflight), all RESTRICT AUTOMERGE cherry-picks in android-security-15.0.0_r10. At android-security-15.0.0_r17 the resolver reports 6/8 lines of the patched region present. The r17 context shows isAllRecordingPausedLocked and the av changes (int startRecording, isRecordOpRequired) in place.",
      "16": "not affected (not listed by the bulletin); at android-security-16.0.0_r8 the patched region is present (8/8 lines).",
      "17": "not affected (not listed by the bulletin); at android-security-17.0.0_r1 the patched region is present (8/8 lines)."
     },
     "backport_notes": [
      "Apply the PermissionManagerService preflight change (20e363e22258) together with the frameworks/av change. The new checkRecordingInternal calls checkPermissionForPreflightFromDatasource before starting the op, and the commit message says audioserver relies on preflight not failing its checkOp. The av change also changes the startRecording signature in ServiceUtilities.h, so downstream callers must be updated. Per index search, nothing outside av was found among the top results, but the search may be incomplete.",
      "For Android 14 the material shows a different base-side commit (bbd8db54b3c9, which finishes started proxy ops when a later chain link fails). Backports to 13/14-based trees should check which PermissionManagerService changes match the local chain-walking code."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether a frameworks/av counterpart of 37e7f808fad1, or the preflight-skip change, was applied on Android 14 or 13 (only base commit bbd8db54b3c9 is shown for 14).",
      "Why only 6/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "Whether the PermissionManagerService preflight hunk of 20e363e22258 is present at android-security-15.0.0_r17 (the context shown covers a different region of the file).",
      "The full body of AudioPolicyService::setAppState_l after the early return, so the behavior of the silenced flag on that path is inferred from the diff only.",
      "How the startOpNoThrow mode values for ECHO_REFERENCE/REMOTE_SUBMIX/VOICE_DOWNLINK are compared against PERMISSION_HARD_DENIED / PERMISSION_GRANTED in startInput (the value mapping is not shown).",
      "How the EoP manifests concretely (the bulletin gives only severity and type)."
     ],
     "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-48549": {
     "cve": "CVE-2025-48549",
     "summary": "Makes audioserver's OpRecordAudioMonitor check and watch the recording app op (and always OP_RECORD_AUDIO) for every link of the AttributionSource chain instead of only the top entry. Two companion changes ship with it: SystemUI now hides the mic indicator only when all of a UID's recordings are silenced, and PermissionManagerService's preflight check skips the first chain link the same way data delivery does.",
     "component": "platform/frameworks/av audiopolicy service (AudioRecordClient / OpRecordAudioMonitor), plus platform/frameworks/base SystemUI AppOpsControllerImpl and PermissionManagerService",
     "weakness": "Incomplete authorization check (missing permission check): app op state was validated and monitored only for the first AttributionSource in a chained (proxy) attribution, so later links were never checked.",
     "root_cause": "In services/audiopolicy/service/AudioRecordClient.cpp, OpRecordAudioMonitor::onFirstRef registered a single AppOpsManager startWatchingMode for mAppOp on mAttributionSource.packageName. OpRecordAudioMonitor::checkOp likewise called mAppOpsManager.checkOp(mAppOp, mAttributionSource.uid, mAttributionSource.packageName) only. Entries reachable via AttributionSourceState.next were neither checked nor watched. OP_RECORD_AUDIO was also not checked when mAppOp was a different op; the code had two TODOs noting this. As a result, the computed hasIt state could stay true when an op for a later link of the chain was denied or changed. Separately, SystemUI's AppOpsControllerImpl.isAnyRecordingPausedLocked disabled the microphone indicator item if any AudioRecordingConfiguration of the UID was silenced. That meant an unsilenced recording of the same UID could run without the indicator. Finally, the PermissionManagerService preflight path ran unsafeCheckOpRawNoThrow on the first chain link even where the data-delivery path skips that link. The commit message says this let preflight fail where delivery would succeed, and that audioserver happens to fail checkOp.",
     "fix_analysis": [
      "frameworks/av 8c09eb1034cb, AudioRecordClient.cpp: adds a forward iterator, AttrSourceItr, in the anonymous namespace. It walks an AttributionSourceState through its .next vector; end() is a null iterator.",
      "AudioRecordClient.cpp:OpRecordAudioMonitor::onFirstRef: a reg(op) lambda calls mAppOpsManager.startWatchingMode(op, attr.packageName, flags, mOpCallback) for every attr in the chain. reg(mAppOp) is called, and reg(OP_RECORD_AUDIO) too if mAppOp != AppOpsManager::OP_RECORD_AUDIO. flags (WATCH_FOREGROUND_CHANGES) is still derived from the top package's target SDK only.",
      "AudioRecordClient.cpp:OpRecordAudioMonitor::checkOp: a check(op) lambda returns std::all_of over the chain, testing mAppOpsManager.checkOp(op, x.uid, x.packageName) == MODE_ALLOWED for each link. hasIt = check(mAppOp), and also requires check(OP_RECORD_AUDIO) when mAppOp differs. The existing record_audio_device_aware_permission / recordingAllowed branch below is unchanged.",
      "frameworks/base 00344da68fce, packages/SystemUI/.../AppOpsControllerImpl.java: isAnyRecordingPausedLocked is renamed to isAllRecordingPausedLocked. The loop now returns false at the first config with !isClientSilenced(), and true only when all configs are silenced. The mMicMuted short-circuit (returns true) and configs == null (returns false) are unchanged. Both call sites (updateActives, updateSensorDisabledStatus) are switched. AppOpsControllerTest now captures the registered AudioRecordingCallback instead of mocking it, and adds multi-config cases.",
      "frameworks/base 20e363e22258, services/core/.../PermissionManagerService.java (preflight check): when skipProxyOperation && !singleReceiverFromDatasource, opMode is set to AppOpsManager.MODE_ALLOWED instead of calling appOpsManager.unsafeCheckOpRawNoThrow(op, resolvedAttributionSource). The commit message says this makes preflight consistent with the for-data-delivery path, which does not check the first link of chains of 2 or more."
     ],
     "completeness": [
      "The av fix covers both monitoring (startWatchingMode) and evaluation (checkOp) for the full chain. It also adds the OP_RECORD_AUDIO check that the old TODOs said was missing.",
      "checkOp uses only uid and packageName per link; attributionTag is not passed. The WATCH_FOREGROUND_CHANGES flag is computed from the top-of-chain package only and reused for all links.",
      "Per the comment in the diff, some record tracks get no monitor or callback 'due to the UID in createIfNeeded()'. The material does not show createIfNeeded, so which clients are monitored is not visible.",
      "The diff does not show the body of RecordAudioOpCallback::opChanged (only the closing namespace comment changed), so whether it filters incoming op/package notifications is not visible.",
      "The SystemUI change only alters when the indicator is suppressed; it does not stop recording. The PermissionManagerService change relaxes the preflight check for proxy chains to match data delivery; it does not tighten it."
     ],
     "behavior_changes": [
      "For record clients with chained attribution, hasIt is now false when any link in the chain lacks mAppOp or OP_RECORD_AUDIO. Changes to those ops on any link now trigger a callback.",
      "audioserver registers more app-op watchers per record client: one per chain link per op, up to two ops.",
      "SystemUI shows the microphone indicator for a UID unless all of its known AudioRecordingConfigurations are silenced. Previously it was hidden if any one of them was silenced.",
      "PermissionManagerService preflight checks treat the first link of a 2+ attribution chain as MODE_ALLOWED when skipProxyOperation is true and the chain is not a singleReceiverFromDatasource."
     ],
     "preconditions": [
      "An audio record client whose AttributionSourceState has one or more .next entries (proxy/chained attribution), where a link other than the first has the relevant app op denied or later changed.",
      "An OpRecordAudioMonitor must exist for the client; the conditions in createIfNeeded are not shown.",
      "For the SystemUI part: the same UID has multiple recording configurations and at least one of them is silenced."
     ],
     "per_major": {
      "14": "adapted fix: the SystemUI AppOpsControllerImpl change is applied in feedcb492661 (android-security-14.0.0_r22); the material shows it without the test file changes. The material does not show whether the frameworks/av AudioRecordClient.cpp or PermissionManagerService commits were applied to 14. At 14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "same fix: three commits in android-security-15.0.0_r10 (av 8c09eb1034cb, base 00344da68fce, base 20e363e22258). At 15.0.0_r17, 6/8 lines of the patched region are reported present. The r17 context shows isAllRecordingPausedLocked and the new AudioRecordClient.cpp includes.",
      "16": "not affected (per the bulletin); 8/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected (per the bulletin); 8/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply the frameworks/av AudioRecordClient.cpp change together with the PermissionManagerService preflight change. The latter's commit message says audioserver relies on it, because audioserver fails checkOp. AttrSourceItr uses a defaulted operator==, which requires C++20 (general platform knowledge).",
      "On Android 14 the material shows only the SystemUI commit; confirm whether the av and PermissionManagerService counterparts exist on that branch."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the frameworks/av and PermissionManagerService commits were applied to Android 14 (only the SystemUI commit is listed for 14).",
      "Why only 6/8 lines of the patched region are present at 14.0.0_r29 and 15.0.0_r17.",
      "The body of RecordAudioOpCallback::opChanged, the OpRecordAudioMonitor::createIfNeeded conditions, and how hasIt is used to silence a client.",
      "Which concrete chained-attribution scenario 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-48550": {
     "cve": "CVE-2025-48550",
     "summary": "Adds package-name validation (FrameworkParsingPackageUtils.validateName with requireSeparator=false, requireFilename=true) to both PkgUser constructors in SlicePermissionManager, so an invalid package string is rejected with IllegalArgumentException.",
     "component": "platform/frameworks/base, system_server slice permission manager (services/core/java/com/android/server/slice/SlicePermissionManager.java)",
     "weakness": "Missing input validation of a package name (DoS per bulletin)",
     "root_cause": "SlicePermissionManager.PkgUser stored whatever package string it was given, either directly (PkgUser(String pkg, int userId)) or parsed from a string (PkgUser(String pkgUserStr)), and did not check that it was a valid package name. The commit title ties this to granting slice permissions. The bulletin rates the result as a High-severity DoS. The material does not show the exact failure mode.",
     "fix_analysis": [
      "services/core/java/com/android/server/slice/SlicePermissionManager.java: PkgUser(String pkg, int userId) now calls enforceValidPackage() after assigning mPkg and mUserId.",
      "SlicePermissionManager.java: PkgUser(String pkgUserStr), which is already declared to throw IllegalArgumentException, now also calls enforceValidPackage() after its existing try/catch parsing block.",
      "SlicePermissionManager.java: the new private PkgUser.enforceValidPackage() calls FrameworkParsingPackageUtils.validateName(mPkg, false /* requireSeparator */, true /* requireFilename */) and throws IllegalArgumentException(error) if the returned error string is not empty. requireSeparator=false allows single-segment names, and requireFilename=true also requires the name to be a valid filename (general platform knowledge).",
      "Imports added: android.content.pm.parsing.FrameworkParsingPackageUtils and android.text.TextUtils."
     ],
     "completeness": [
      "Validation sits in both PkgUser constructors, so any path that builds a PkgUser through either constructor goes through the check. The material does not show which call sites use which constructor.",
      "Only the package part (mPkg) is validated. This change adds no range check on the userId. Parse failures in the string constructor were already turned into IllegalArgumentException by the existing try/catch.",
      "The fix adds no handling at call sites. The material does not show whether callers of the string constructor catch the new IllegalArgumentException.",
      "Other identifiers handled by this class (e.g. SliceProviderPermissions.SliceAuthority) are not touched by this diff. The material does not show whether they take caller-controlled input."
     ],
     "behavior_changes": [
      "Constructing a PkgUser with a package name that fails FrameworkParsingPackageUtils.validateName now throws IllegalArgumentException. Grant paths that use such a name get that exception instead of succeeding.",
      "PkgUser(String pkgUserStr) now also throws IllegalArgumentException when the parsed package part is invalid.",
      "No change for valid package names."
     ],
     "preconditions": [
      "A caller must reach a SlicePermissionManager path that builds a PkgUser from a package string. The commit message names the slice permission grant path (\"when granting slice permission\"). The material does not show the exact public entry point or its permission requirements.",
      "Index search for PkgUser users found no other callers (per index search). The TvSettings SliceManagerWrapper hit is only a client-side wrapper and does not bear on the server-side check."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 54158e2925dc in android-security-14.0.0_r22, identical diff (same blob indices 343d2e353abb..27578a01959f); 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 354820f6ec38 in android-security-15.0.0_r10; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix, only the line offsets differ (hunks at 424/434 instead of 410/420, blob d118eaea37d9..b66aaf10b993): platform/frameworks/base bae8ab23b03d in android-security-16.0.0_r1; 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); patched region already present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. The only new dependency is android.content.pm.parsing.FrameworkParsingPackageUtils.validateName(String, boolean, boolean). The bulletin lists Android 13 as affected, but the material includes no Android 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the concrete DoS failure mode (which operation fails with a malformed package name).",
      "The material does not show how PkgUser values are persisted or used later (e.g. as file names), or whether the package string is caller-controlled.",
      "The material does not show the public entry point or caller permission requirements for granting slice permissions.",
      "The material does not show whether callers of PkgUser(String pkgUserStr) handle the newly thrown IllegalArgumentException.",
      "The material includes no Android 13 fix commit, although the bulletin lists 13 as 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."
     }
    },
    "CVE-2025-48551": {
     "cve": "CVE-2025-48551",
     "summary": "The Sharesheet \"Edit\" image action in IntentResolver's ChooserActivity now launches the image editor as the user that launched the sharesheet (mUserInteractor.getLaunchedAs()) instead of always as the personal profile user (mProfiles.getPersonalHandle()).",
     "component": "platform/packages/modules/IntentResolver, Sharesheet (ChooserActivity / ChooserActionFactory image edit action)",
     "weakness": "Incorrect user/profile selection when starting an activity (the edit target was always started as the personal profile user)",
     "root_cause": "ChooserActivity.createChooserActionFactory supplied an ActionActivityStarter with two methods: safelyStartActivityAsPersonalProfileUser and its shared-element variant. Both called safelyStartActivityAsUser with mProfiles.getPersonalHandle(). The edit action in ChooserActionFactory used these starters to launch editSharingTarget. As a result, the image editor was started as the personal profile user, regardless of which user or profile launched the sharesheet.",
     "fix_analysis": [
      "ChooserActivity.java:createChooserActionFactory (anonymous ChooserActionFactory.ActionActivityStarter): safelyStartActivityAsLaunchingUser now calls safelyStartActivityAsUser(targetInfo, mUserInteractor.getLaunchedAs()) instead of mProfiles.getPersonalHandle(). safelyStartActivityAsLaunchingUserWithSharedElementTransition likewise passes mUserInteractor.getLaunchedAs(), together with the scene-transition ActivityOptions bundle.",
      "ChooserActionFactory.java:ActionActivityStarter interface: the methods are renamed from safelyStartActivityAsPersonalProfileUser / ...WithSharedElementTransition to safelyStartActivityAsLaunchingUser / safelyStartActivityAsLaunchingUserWithSharedElementTransition. The default implementation of the shared-element variant now delegates to the renamed method.",
      "ChooserActionFactory.java (edit action lambda, around line 354): the two call sites that start editSharingTarget now use the renamed starter methods. The rename is mechanical; the only behavioural change is the UserHandle argument in ChooserActivity.",
      "The commit message says this is the companion to ag/34038177, covering the case where the use_preferred_image_editor flag is off or does not exist. That other change is not in the material."
     ],
     "completeness": [
      "The fix covers both launch paths of the image edit action (plain launch and shared-element-transition launch) by changing the single UserHandle source in the ActionActivityStarter implementation in ChooserActivity.",
      "Per the commit message, this commit addresses only the path where use_preferred_image_editor is off or absent. The flag-on path is handled by a separate change (ag/34038177) that is not shown, so the material cannot confirm which branches have it.",
      "The stale comment \"Action bar is user-independent; always start as primary.\" in ChooserActionFactory and the Log.d string \"safelyStartActivityAsPersonalProfileUser(\" in ChooserActivity were left unchanged. Both are cosmetic.",
      "safelyStartActivityAsUser itself and other action paths (createCustomActions, getModifyShareAction) are not modified. The material does not show whether they select a user handle."
     ],
     "behavior_changes": [
      "The image editor started from the Sharesheet edit action now starts as the user that launched the sharesheet, rather than always as the personal profile user.",
      "The ActionActivityStarter interface methods are renamed inside the module, so any other implementer of the interface must be updated."
     ],
     "preconditions": [
      "Sharesheet (ChooserActivity) launched from a user or profile other than the personal profile (e.g. a work profile). This follows from the fix's switch from getPersonalHandle() to getLaunchedAs().",
      "Shared content includes an image, so that the edit action is offered (ChooserActionFactory edit action, firstImageView / editSharingTarget path).",
      "use_preferred_image_editor is off or absent (this commit). The flag-on path is covered by a separate change."
     ],
     "per_major": {
      "14": "adapted fix (75f41ba2e89d, android-security-14.0.0_r22): the same interface rename in ChooserActionFactory. In ChooserActivity (which extends ResolverActivity in this branch), the handle changes from getPersonalProfileUserHandle() to UserHandle.of(UserHandle.myUserId()). The shared-element path calls startFinishAnimation() instead of setting mFinishWhenStopped. 6/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "fix as analysed (13c30b464d04, android-security-15.0.0_r10). At android-security-15.0.0_r17, 6/8 lines of the patched region are present, and the code context shows mUserInteractor.getLaunchedAs() in both starter methods.",
      "16": "same fix (9810de0fcb5a, android-security-16.0.0_r1). Only the surrounding edit-action condition differs (no requireFullVisibility variable). 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": [
      "On Android 14 style branches (ChooserActivity extends ResolverActivity), Google used UserHandle.of(UserHandle.myUserId()); on 15+ it used mUserInteractor.getLaunchedAs(). If use_preferred_image_editor exists on the target branch, also check for the companion flag-on change (ag/34038177)."
     ],
     "confidence": "high",
     "unknowns": [
      "The content and branch coverage of the companion change ag/34038177 (flag-on path) are not in the material.",
      "The material does not say exactly what information is exposed, beyond the bulletin's ID classification.",
      "Whether the edit intent carries URI permission grant flags is not shown in the diff.",
      "Whether IntentResolver tests were added for this change (the commit cites a manual repro plus the existing atest suite)."
     ],
     "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-48552": {
     "cve": "CVE-2025-48552",
     "summary": "Wraps the three Settings.Global writes in DevicePolicyManagerService.saveGlobalProxyLocked in a try/catch(Exception), so a SettingsProvider failure while applying a device admin's global proxy no longer propagates and aborts the calling operation (such as removing an ActiveAdmin, per the commit message).",
     "component": "platform/frameworks/base, services/devicepolicy (DevicePolicyManagerService)",
     "weakness": "Unhandled exception in a security-sensitive path (improper handling of exceptional conditions): a failure writing proxy settings aborts the enclosing admin-management operation",
     "root_cause": "saveGlobalProxyLocked(String proxySpec, String exclusionList) parses the proxy spec, builds a ProxyInfo and checks ProxyInfo.isValid(). It then calls mInjector.settingsGlobalPutString/settingsGlobalPutInt for GLOBAL_HTTP_PROXY_HOST, GLOBAL_HTTP_PROXY_PORT and GLOBAL_HTTP_PROXY_EXCLUSION_LIST with no exception handling. The commit message says exceptions are thrown when invalid proxy settings are applied, and the comment in the fix attributes them to SettingsProvider. Because the writes come after the isValid() early return, such exceptions must arise for values that got past that check. saveGlobalProxyLocked is called from code that iterates the ActiveAdmins (context lines 8638-8644): if an admin has specifiesGlobalProxy set, it calls saveGlobalProxyLocked(ap.globalProxySpec, ap.globalProxyExclusionList), otherwise saveGlobalProxyLocked(null, null). An exception thrown there unwinds through the caller, so an operation that re-applies proxy state fails before it completes. Per the commit message, removing an ActiveAdmin is one such operation. The bulletin rates this as a High severity EoP.",
     "fix_analysis": [
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:saveGlobalProxyLocked: the three mInjector.settingsGlobalPutString/settingsGlobalPutInt calls (GLOBAL_HTTP_PROXY_HOST, GLOBAL_HTTP_PROXY_PORT, GLOBAL_HTTP_PROXY_EXCLUSION_LIST) are now inside try { ... } catch (Exception e) { Slogf.w(LOG_TAG, \"Fail to save proxy\", e); }. Any exception from the settings writes is logged and swallowed, so the method returns normally to its caller.",
      "In the 14 and 15 backports the same hunk also changes the constant references from Settings.Global.* to Global.*. The Android 16 file already used Global.* before the fix. The rename is cosmetic.",
      "The existing ProxyInfo.isValid() early return and the proxy spec parsing are unchanged. The parsing splits on ':', calls Integer.parseInt on the port while ignoring NumberFormatException, and defaults the port to 8080."
     ],
     "completeness": [
      "The fix covers only the three Settings.Global put calls in saveGlobalProxyLocked. Because the exception is contained inside the method, every caller benefits, including the loop over ActiveAdmins shown in the context.",
      "There is no rollback: if the first or second put succeeds and a later one throws, GLOBAL_HTTP_PROXY_HOST may be written without a matching port or exclusion list. The fix accepts this partial state and only logs it.",
      "The fix adds no stricter validation of the proxy spec. It only contains the failure.",
      "catch (Exception e) is broad and also swallows unrelated RuntimeExceptions from the injector. This matches the comment referencing b/365975561, which says to ignore any potential errors from SettingsProvider."
     ],
     "behavior_changes": [
      "SettingsProvider failures during global proxy application no longer propagate out of saveGlobalProxyLocked. Instead, a warning 'Fail to save proxy' is logged with the exception via Slogf.w.",
      "Operations that re-apply the global proxy (for example ActiveAdmin removal, per the commit message) now complete even when the proxy settings cannot be written.",
      "If a write fails midway, the global proxy settings can be left partially updated and the enclosing operation now continues. Before the fix, the same partial state could occur but the operation was aborted."
     ],
     "preconditions": [
      "An ActiveAdmin with specifiesGlobalProxy set, whose globalProxySpec/globalProxyExclusionList pass ProxyInfo.isValid() but make the SettingsProvider write throw. The exact failing value is not shown in the material.",
      "A code path that reaches saveGlobalProxyLocked through the ActiveAdmin iteration shown in the context (lines 8638-8644). Per the commit message, this includes removing an ActiveAdmin."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 58e0b9a15338 in android-security-14.0.0_r22. The hunk is identical, including the Settings.Global to Global rename, at a different line offset (8468). The resolver reports 1/4 patched-region lines present at android-security-14.0.0_r29. No code context for that tag is provided, so the reason for the low figure is not shown.",
      "15": "same fix: platform/frameworks/base bb6d2f17243d in android-security-15.0.0_r10. The code context at android-security-15.0.0_r17 (lines 8671-8679) shows the try/catch fully present, so the resolver's 1/4 line figure does not mean the fix is missing.",
      "16": "same fix: platform/frameworks/base d5fee58e64ae in android-security-16.0.0_r1. The file already used Global.*, so only the try/catch was added. All 4/4 patched lines are present at android-security-16.0.0_r8.",
      "17": "not affected (the bulletin does not list Android 17). The resolver reports 1/4 patched lines present at android-security-17.0.0_r1, and the material does not show the state of the region there."
     },
     "backport_notes": [
      "Single self-contained hunk in saveGlobalProxyLocked. If you port it to a tree that still uses Settings.Global.*, either keep that form or confirm that Global is imported, because the 14/15 hunk renames the constants to Global.* in the same change.",
      "If a fork adds further settings writes in this method, consider whether they belong inside the same try block."
     ],
     "confidence": "high",
     "unknowns": [
      "The specific proxy value and the exception type thrown by SettingsProvider that triggered b/365975561 are not shown.",
      "The caller chain from ActiveAdmin removal to saveGlobalProxyLocked is only described in the commit message. The material shows the iteration over admins but not the name of the enclosing method.",
      "Which API lets an admin set the global proxy, and its feature or permission gating, are not shown.",
      "The state of the patched region on Android 14 (r29) and Android 17 (r1) beyond the resolver's 1/4 line figures."
     ],
     "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-48553": {
     "cve": "CVE-2025-48553",
     "summary": "DevicePolicyManagerService.handlePackagesChanged now re-parses the device admin receiver's meta-data (new DeviceAdminInfo) on package change and removes the active admin if the package, receiver or meta-data is missing or malformed; DeviceAdminInfo's constructor additionally converts OutOfMemoryError during meta-data parsing into XmlPullParserException so it is treated as malformed.",
     "component": "platform/frameworks/base, DevicePolicyManagerService (services/devicepolicy) and android.app.admin.DeviceAdminInfo",
     "weakness": "Missing revalidation of persisted device admin state on package update (stale ActiveAdmin kept after its hosting package changed), plus uncaught OutOfMemoryError during meta-data parsing",
     "root_cause": "In DevicePolicyManagerService.handlePackagesChanged, when a package hosting an ActiveAdmin was changed, the service only checked that mIPackageManager.getPackageInfo(...) and getReceiverInfo(...) (without GET_META_DATA) returned non-null. It never re-parsed the receiver's device-admin meta-data, so an ActiveAdmin whose stored DeviceAdminInfo was parsed from an earlier version of the package stayed in policy.mAdminList/mAdminMap even if the updated package no longer carried valid device-admin meta-data. Separately, DeviceAdminInfo's XML-parsing constructor caught only NameNotFoundException; an OutOfMemoryError raised while parsing meta-data propagated as an Error rather than being reported as a parse failure.",
     "fix_analysis": [
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:isActiveAdminPackageValid (new, @GuardedBy getLockObject()): checks getPackageInfo(adminPackage, 0, userHandle) != null, then getReceiverInfo(admin.info.getComponent(), GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle) != null, then constructs new DeviceAdminInfo(mContext, ai) inside try/catch(Exception); any failure logs via Slogf.e and returns false. The user id is taken from admin.getUserHandle().getIdentifier() rather than the handlePackagesChanged argument.",
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:handlePackagesChanged: replaces the inline package/receiver existence check with !isActiveAdminPackageValid(aa); on false it sets removedAdmin, removes the admin from policy.mAdminList and policy.mAdminMap and calls pushActiveAdminPackagesLocked(userHandle), as before. Log message changed to 'not found or invalid'.",
      "core/java/android/app/admin/DeviceAdminInfo.java:DeviceAdminInfo(Context, ActivityInfo) constructor: adds catch (OutOfMemoryError e) that rethrows as XmlPullParserException(\"Out of memory when parsing\", null, e), so the catch(Exception) in isActiveAdminPackageValid (and any other caller catching XmlPullParserException) treats an OOM during meta-data parsing as malformed meta-data instead of letting the Error escape."
     ],
     "completeness": [
      "Revalidation is added only on the handlePackagesChanged path (package change events); the diff does not touch other places where ActiveAdmin/DeviceAdminInfo state is loaded or trusted (e.g. loading persisted policy, setActiveAdmin), so whether those paths parse fresh meta-data is not shown in the material.",
      "Only OutOfMemoryError is converted to XmlPullParserException in DeviceAdminInfo; other java.lang.Error subclasses thrown during parsing are still not caught by isActiveAdminPackageValid's catch(Exception).",
      "The validity check covers the three conditions package installed, receiver present, meta-data parses; it does not compare the newly parsed DeviceAdminInfo (e.g. uses-policies) with the stored admin.info, the stored ActiveAdmin is kept as-is when parsing succeeds."
     ],
     "behavior_changes": [
      "On a package change event, an active device admin whose receiver meta-data no longer parses (or whose receiver lookup with GET_META_DATA fails) is now removed from the active admin list, with the same downstream effects as a removed admin package.",
      "getReceiverInfo in this path is now called with GET_META_DATA, and a DeviceAdminInfo is constructed per matching admin on every package change event (extra XML parsing).",
      "DeviceAdminInfo(Context, ActivityInfo) now throws XmlPullParserException instead of OutOfMemoryError when parsing runs out of memory; this affects all callers of that constructor, not only DPMS.",
      "New error log lines: '<pkg> no longer installed', '<pkg> no longer has the receiver', '<pkg> contains malformed metadata', and 'Admin package %s not found or invalid for user %d, removing active admin'."
     ],
     "preconditions": [
      "An ActiveAdmin exists in DevicePolicyData.mAdminList for the user.",
      "A package change for that admin's package (or a null packageName, meaning all packages) reaches DevicePolicyManagerService.handlePackagesChanged; the caller of handlePackagesChanged is not shown in the material.",
      "For the OutOfMemoryError path: the receiver's device-admin meta-data causes an OOM during XML parsing in the DeviceAdminInfo constructor."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 0f9ca51bfd42 in android-security-14.0.0_r22, identical content (old code used PackageManager.-qualified MATCH_* constants, new method uses unqualified constants); 8/8 lines present at android-security-14.0.0_r29",
      "15": "same fix: platform/frameworks/base 660c7075dc00 in android-security-15.0.0_r10 (cherry-pick of ffb37079a138); 8/8 lines present at android-security-15.0.0_r17",
      "16": "same fix: platform/frameworks/base c0978bc04a25 in android-security-16.0.0_r1, identical content; 8/8 lines present at android-security-16.0.0_r8",
      "17": "not affected per the bulletin (Android 17 not listed); the patched region is present (8/8 lines) at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single commit, two files, applied identically on 14/15/16. The new isActiveAdminPackageValid uses unqualified GET_META_DATA, MATCH_DIRECT_BOOT_AWARE, MATCH_DIRECT_BOOT_UNAWARE and ActiveAdmin.getUserHandle(); on older or customized DPMS trees confirm those imports and that method exist, otherwise qualify the constants. Both the DeviceAdminInfo OOM catch and the DPMS change are needed for the OOM case to be handled by catch(Exception)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete scenario behind bugs 384514657 and 414603411 (what a stale ActiveAdmin with changed meta-data allowed) is not shown in the material.",
      "Which component dispatches handlePackagesChanged (package monitor/broadcast) and the exact set of package events that trigger it are not in the material.",
      "Whether other code paths that construct DeviceAdminInfo rely on the new OutOfMemoryError conversion is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "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-2025-48554": {
     "cve": "CVE-2025-48554",
     "summary": "DevicePolicyManagerService now re-validates an active admin's device_admin metadata whenever its package changes, by constructing a DeviceAdminInfo from the receiver's ActivityInfo fetched with GET_META_DATA, and removes the admin if the metadata is malformed. DeviceAdminInfo's metadata parser also converts OutOfMemoryError into an XmlPullParserException so it is handled like any other parse failure.",
     "component": "platform/frameworks/base, DevicePolicyManagerService / DeviceAdminInfo (device admin metadata handling)",
     "weakness": "Missing validation of device admin metadata on package update (an active admin with unparseable metadata was kept), plus unhandled OutOfMemoryError during XML metadata parsing",
     "root_cause": "When an active admin's package changed, DevicePolicyManagerService.handlePackagesChanged only checked that the package still existed (getPackageInfo) and that the receiver component still existed (getReceiverInfo without GET_META_DATA). It never checked that the receiver's device_admin metadata could still be parsed. An admin whose updated package carried malformed metadata, or metadata that exhausts memory when parsed, therefore stayed in policy.mAdminList and mAdminMap. Separately, the metadata parse in the DeviceAdminInfo(Context, ActivityInfo) constructor converted NameNotFoundException to XmlPullParserException but did not catch OutOfMemoryError, so an OOM during parsing escaped the constructor as an Error. The bulletin classifies the result as a High severity DoS.",
     "fix_analysis": [
      "services/devicepolicy/.../DevicePolicyManagerService.java: new @GuardedBy(getLockObject()) helper isActiveAdminPackageValid(ActiveAdmin admin). Step 1: returns false if mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) is null. Step 2: fetches the receiver via mIPackageManager.getReceiverInfo(admin.info.getComponent(), GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle) and returns false if that is null. Step 3: constructs new DeviceAdminInfo(mContext, ai) inside try/catch(Exception), and on any exception logs 'contains malformed metadata' and returns false. userHandle is taken from admin.getUserHandle().getIdentifier().",
      "services/devicepolicy/.../DevicePolicyManagerService.java:handlePackagesChanged: the inline getPackageInfo/getReceiverInfo null checks are replaced by if (!isActiveAdminPackageValid(aa)). On failure the existing removal path runs: removedAdmin = true, the admin is removed from policy.mAdminList and policy.mAdminMap, and pushActiveAdminPackagesLocked is called. The log message now says 'not found or invalid' and passes Slogf format args directly instead of using String.format.",
      "core/java/android/app/admin/DeviceAdminInfo.java: the try block that parses metadata in the DeviceAdminInfo(Context, ActivityInfo) constructor gets a new catch (OutOfMemoryError e), which rethrows as XmlPullParserException(\"Out of memory when parsing\", null, e). This turns the Error into a checked exception, so the catch (Exception e) in isActiveAdminPackageValid treats it as malformed metadata."
     ],
     "completeness": [
      "Only the package-change path is covered: metadata validity is re-checked in handlePackagesChanged for admins whose package matches, or for all admins when packageName is null. The material does not show other places that construct DeviceAdminInfo for an existing admin, and this commit does not change any.",
      "The OutOfMemoryError conversion is limited to the try block in the DeviceAdminInfo(Context, ActivityInfo) constructor. Only OutOfMemoryError is converted; other Errors are not caught, and isActiveAdminPackageValid catches Exception only.",
      "The validation is a full DeviceAdminInfo construction with GET_META_DATA, so any XmlPullParserException or other exception the constructor throws now leads to removal of the admin. The visible cases are an invalid headless-system-user mode when the compatibility flag is off, context creation failure (NameNotFoundException) and OOM.",
      "isActiveAdminPackageValid uses the admin's own user id (admin.getUserHandle()) rather than the userHandle parameter of handlePackagesChanged. The material does not show a case where the two differ."
     ],
     "behavior_changes": [
      "An active admin whose package is updated with metadata that DeviceAdminInfo cannot parse is now removed from the active admin list. Before, this removal path ran only for uninstalled packages or missing receivers. New error logs: 'no longer installed', 'no longer has the receiver', 'contains malformed metadata'.",
      "getReceiverInfo in this path now requests GET_META_DATA, so the metadata is loaded and parsed on every matching package change.",
      "When the metadata parse runs out of memory, DeviceAdminInfo construction now throws XmlPullParserException instead of OutOfMemoryError."
     ],
     "preconditions": [
      "A package hosting an active device admin (present in DevicePolicyData.mAdminList for the user) is changed or updated, so handlePackagesChanged runs for it.",
      "The updated package's device_admin receiver metadata is malformed, or parsing it exhausts memory. Before the fix the admin stayed registered in that state."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 0f9ca51bfd42 in android-security-14.0.0_r22. The added code is identical; the removed lines used PackageManager.MATCH_*. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 660c7075dc00 in android-security-15.0.0_r10 (cherry-pick of ffb37079a138). 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c0978bc04a25 in android-security-16.0.0_r1. The added code is identical; the removed lines already used unqualified MATCH_* constants. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region is fully present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply both hunks together. Without the OutOfMemoryError to XmlPullParserException conversion in DeviceAdminInfo, an OOM escapes the catch (Exception e) in isActiveAdminPackageValid.",
      "The helper uses GET_META_DATA, MATCH_DIRECT_BOOT_AWARE and MATCH_DIRECT_BOOT_UNAWARE unqualified, and the diff adds no imports. A customized branch needs the matching static imports in DevicePolicyManagerService, or must qualify the constants."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show where the retained invalid admin's metadata was later parsed and failed (which code path crashed or was disrupted). It shows only that the bulletin classifies the issue as a DoS.",
      "The material does not show which callers of DeviceAdminInfo(Context, ActivityInfo), if any, were affected by an OutOfMemoryError escaping it before the fix.",
      "The specific metadata shape that triggered the issue (malformed XML or memory exhaustion) is not shown; the fix handles both.",
      "The material does not show whether other DeviceAdminInfo construction sites in DevicePolicyManagerService guard against the same failures."
     ],
     "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-48556": {
     "cve": "CVE-2025-48556",
     "summary": "NotificationChannel.setId() and setConversationId() now pass their string arguments through the existing getTrimmedString() helper so mId, mParentId and mConversationId are capped at MAX_TEXT_LENGTH instead of being stored unbounded.",
     "component": "platform/frameworks/base, core/java/android/app/NotificationChannel (notification framework)",
     "weakness": "Missing length bound / input validation on string fields (oversized strings stored without the trimming applied to sibling fields)",
     "root_cause": "NotificationChannel already limits some text fields to MAX_TEXT_LENGTH: setDescription uses getTrimmedString(), and an existing parcel round-trip test (NotificationChannelTest lines 204-215) shows that an unparceled channel has id, name, description, parent id, group and conversation id bounded to MAX_TEXT_LENGTH. The setters setId(String) (@hide) and setConversationId(String parentChannelId, String conversationId) assigned their arguments directly to mId, mParentId and mConversationId, so a channel built or mutated through these setters could hold ids longer than MAX_TEXT_LENGTH. The bulletin rates the issue as a High-severity EoP; the material does not show the concrete downstream consequence of the oversized ids.",
     "fix_analysis": [
      "core/java/android/app/NotificationChannel.java:setId - `mId = id;` becomes `mId = getTrimmedString(id);`, truncating ids longer than MAX_TEXT_LENGTH to their first MAX_TEXT_LENGTH characters (null passes through unchanged).",
      "core/java/android/app/NotificationChannel.java:setConversationId - `mParentId = parentChannelId;` and `mConversationId = conversationId;` become `getTrimmedString(parentChannelId)` / `getTrimmedString(conversationId)`, applying the same cap to both conversation fields.",
      "core/tests/coretests/src/android/app/NotificationChannelTest.java - adds testSetId_longStringIsTrimmed and testSetConversationId_longStringsAreTrimmed, each feeding MAX_TEXT_LENGTH + 10 character strings and asserting the getters return exactly the first MAX_TEXT_LENGTH characters.",
      "No new helper is introduced; the fix reuses the private getTrimmedString(String) already present (lines 556-561 in the r17 context, used by setDescription at line 553)."
     ],
     "completeness": [
      "Covers the three fields reachable through setId and setConversationId (mId, mParentId, mConversationId).",
      "setGroup(String groupId) in the same class still assigns `this.mGroup = groupId;` without getTrimmedString (r17 context lines 601-603); group is shown bounded only after a parcel round trip, so the setter path for the group id is left as it was.",
      "The fix truncates rather than rejects: an oversized id silently becomes a shorter id, with no error to the caller."
     ],
     "behavior_changes": [
      "setId() and setConversationId() silently truncate arguments longer than MAX_TEXT_LENGTH; getId(), getParentChannelId() and getConversationId() then return the truncated value rather than the string the caller supplied.",
      "Two ids that differ only beyond MAX_TEXT_LENGTH now collapse to the same stored id when set through these setters.",
      "No new exceptions or error codes; null input is returned as is by getTrimmedString."
     ],
     "preconditions": [
      "A caller invokes NotificationChannel.setConversationId() or setId() (@hide) with a string longer than NotificationChannel.MAX_TEXT_LENGTH; which callers can reach these setters is not shown.",
      "The oversized channel object must then be consumed on a path that does not go through a parcel round trip, since unparceled channels already have these fields bounded (per the existing parcel test); the material does not identify that path."
     ],
     "per_major": {
      "14": "not affected (not listed by the bulletin); no fix commit linked; android-security-14.0.0_r29 shows 7/8 lines of the patched region",
      "15": "same fix: platform/frameworks/base fc5d9b6ef530 in android-security-15.0.0_r10 (cherry-pick of a9a1b4365b8d, Change-Id Id721d3123ee8d38753f550fe57ba0f5d15d743ac); patched region present at android-security-15.0.0_r17",
      "16": "same fix: platform/frameworks/base 0da1ac9dda56 in android-security-16.0.0_r1, identical hunks with different line offsets (setId at ~650, setConversationId at ~894); patched region present at android-security-16.0.0_r8",
      "17": "not affected (not listed by the bulletin); android-security-17.0.0_r1 shows 7/8 lines of the patched region"
     },
     "backport_notes": [
      "Single self-contained commit; the only dependency is the private getTrimmedString(String) helper and MAX_TEXT_LENGTH constant in NotificationChannel.java, both already present in the 15 code context. The three one-line substitutions are identical on 15 and 16 apart from line offsets.",
      "The new tests use Truth assertThat and Guava Strings.repeat; the existing testLongAlertFields already uses Strings.repeat, and the 15/16 commits add no imports."
     ],
     "confidence": "high",
     "unknowns": [
      "Which caller path consumed the untrimmed mId/mParentId/mConversationId and how an oversized id led to the EoP described by the bulletin; the material only shows the setter-level fix.",
      "Why Android 14 is listed as not affected even though the patched region largely matches there; the per-major data does not show a 14 commit.",
      "Who calls setId and setConversationId in practice; no relevant callers were surfaced by the index search.",
      "Whether parcel-path trimming of these fields happens in the Parcel constructor or in writeToParcel."
     ],
     "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-48558": {
     "cve": "CVE-2025-48558",
     "summary": "BatteryService now resolves the implicit ACTION_REQUEST_SHUTDOWN intent itself and pins it to an activity whose owning uid holds android.permission.SHUTDOWN before calling startActivityAsUser, so a non-system app should not be chosen as the shutdown target when a permitted activity resolves.",
     "component": "platform/frameworks/base, system_server BatteryService (services/core/java/com/android/server/BatteryService.java)",
     "weakness": "Implicit intent sent from a privileged process to an unverified target (missing permission check on the resolved component)",
     "root_cause": "In the low-battery shutdown path (guarded by shouldShutdownLocked()) and in shutdownIfOverTempLocked, BatteryService (running in system_server) built an implicit Intent with action Intent.ACTION_REQUEST_SHUTDOWN, EXTRA_KEY_CONFIRM=false, EXTRA_REASON and FLAG_ACTIVITY_NEW_TASK, and passed it directly to mContext.startActivityAsUser(intent, UserHandle.CURRENT) once mActivityManagerInternal.isSystemReady(). No component was set and no check was made on the resolved target, so per the commit title a non-system activity handling ACTION_REQUEST_SHUTDOWN could be launched by BatteryService when the low-battery or over-temperature condition fired.",
     "fix_analysis": [
      "services/core/java/com/android/server/BatteryService.java: in the shouldShutdownLocked() branch and in shutdownIfOverTempLocked, the Intent is built synchronously (same action, extras and flags as before) and the posted anonymous Runnable is replaced by mHandler.post(() -> startShutdownActivity(intent)).",
      "services/core/java/com/android/server/BatteryService.java:startShutdownActivity (new): returns early if !mActivityManagerInternal.isSystemReady() (preserving the old gate) or if LocalServices.getService(PackageManagerInternal.class) is null.",
      "startShutdownActivity: calls pmi.queryIntentActivities(intent, null, 0, Process.myUid(), mActivityManagerInternal.getCurrentUserId()) to enumerate candidate activities for the current user, skips entries with null activityInfo/applicationInfo, and for each candidate calls mContext.checkPermission(android.Manifest.permission.SHUTDOWN, 0, ri.activityInfo.applicationInfo.uid); candidates without the permission are logged via Slog.w and skipped.",
      "startShutdownActivity: the first candidate holding SHUTDOWN is pinned with intent.setComponent(ri.activityInfo.getComponentName()), then mContext.startActivityAsUser(intent, UserHandle.CURRENT) is called as before.",
      "New imports: android.content.pm.PackageManager, PackageManagerInternal, ResolveInfo, android.os.Process, java.util.List."
     ],
     "completeness": [
      "Both shutdown triggers in BatteryService (critically low battery and over-temperature) are routed through the single new startShutdownActivity helper, so both paths get the permission-gated component selection.",
      "If resolveInfos is null, or no resolved activity holds android.permission.SHUTDOWN, intent.setComponent is never called and startActivityAsUser is still invoked with the implicit intent; the code does not abort in that case. The protection therefore relies on a SHUTDOWN-holding activity being present among the resolved candidates.",
      "Candidate enumeration uses the current user (getCurrentUserId) with flags 0 and Process.myUid() as the calling uid; the launch still uses UserHandle.CURRENT.",
      "Only BatteryService is changed; the material does not show other senders of ACTION_REQUEST_SHUTDOWN, so nothing can be said about them."
     ],
     "behavior_changes": [
      "The shutdown request intent is now explicit (component set) when a SHUTDOWN-permitted activity resolves.",
      "A warning is logged (Slog.w, TAG) naming each resolved activity whose uid lacks android.permission.SHUTDOWN.",
      "If PackageManagerInternal is not available from LocalServices the shutdown activity is not started at all (new early return).",
      "Intent construction moved from the handler thread to the calling thread; extras, flags and the isSystemReady gate are unchanged."
     ],
     "preconditions": [
      "System must be booted (mActivityManagerInternal.isSystemReady()).",
      "Either shouldShutdownLocked() is true (per the code comment, battery critically low and not powered) or mHealthInfo.batteryTemperatureTenthsCelsius exceeds mShutdownBatteryTemperature; the commit's Test line shows 'adb shell dumpsys battery set temp 1001' as a way to trigger the over-temperature path.",
      "An activity installed for the current user that matches Intent.ACTION_REQUEST_SHUTDOWN without holding android.permission.SHUTDOWN would previously have been eligible as the launch target."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin; no commit for Android 13 is present in the material.",
      "14": "Same fix: platform/frameworks/base e22e656b5e88 in android-security-14.0.0_r22 (identical hunk, offset differs; import block also contains android.content.IntentFilter). At android-security-14.0.0_r29 8/8 patched lines present.",
      "15": "Same fix: platform/frameworks/base 0d30c78c8953 in android-security-15.0.0_r10 (Change-Id I6c00b47d424d81712bd9634c31de4b7d2e9cbe31). At android-security-15.0.0_r17 8/8 patched lines present.",
      "16": "Same fix: platform/frameworks/base b1c60eeadb2c in android-security-16.0.0_r1 (identical hunk at a different line offset). At android-security-16.0.0_r8 8/8 patched lines present.",
      "17": "Not affected per the bulletin (not listed); android-security-17.0.0_r1 contains 8/8 lines of the patched region."
     },
     "backport_notes": [
      "Single self-contained commit touching one file; the hunk is identical on 14, 15 and 16 with only line offsets differing, so it should apply with fuzz to any branch whose BatteryService still has the two anonymous Runnable blocks calling mContext.startActivityAsUser(intent, UserHandle.CURRENT).",
      "Requires PackageManagerInternal.queryIntentActivities(Intent, String, int, int, int) and ActivityManagerInternal.getCurrentUserId() in the target branch; verify their signatures on older branches."
     ],
     "confidence": "high",
     "unknowns": [
      "The Android 13 fix commit is not included in the material.",
      "The name of the enclosing low-battery shutdown method is not shown in the diff context.",
      "Whether other system components send ACTION_REQUEST_SHUTDOWN implicitly is not shown.",
      "The internal review links (googleplex-android-review) are not resolvable from the material; the original upstream change content beyond this cherry-pick is unknown."
     ],
     "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-48559": {
     "cve": "CVE-2025-48559",
     "summary": "AppOpsService no longer treats attribution tags of operations proxied for the root UID as valid unless the proxy is null or a system package (via a new isPackageNullOrSystem helper shared with the special-UID branch), and finishProxyOperation now passes the proxy identity into verifyAndGetBypass.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop/AppOpsService.java)",
     "weakness": "Missing validation of attribution tags for root-UID proxied operations (unverified attribution tags can pile up in the appops state; the bulletin classifies this as DoS)",
     "root_cause": "In AppOpsService.verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, suppressErrorLogs), the uid == Process.ROOT_UID branch returned a PackageVerificationResult with isAttributionTagValid = true no matter who the proxy was. The existing special-UID branch already limited tag validity to a null or system proxy (proxyIsSystemAppOrNull), but root had no such limit. Also, finishProxyOperation called finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, ...), which called the 3-argument verifyAndGetBypass without proxyUid/proxyPackageName. Per the commit message, this let an unverified proxy app's attribution tag be treated as valid on finish for one of the special proxied apps. Per the commit message, a malicious proxy app could exploit the root bypass, and the code comment says the check prevents apps from clogging the appops system with unlimited attribution tags.",
     "fix_analysis": [
      "AppOpsService.java:verifyAndGetBypass (6-arg): in the uid == Process.ROOT_UID branch, isAttributionTagValid changes from the constant true to isPackageNullOrSystem(proxyPackageName, proxyUid). bypass stays null.",
      "AppOpsService.java:verifyAndGetBypass (6-arg): the existing special-UID branch (which returns RestrictionBypass.UNRESTRICTED) drops its inline proxyIsSystemAppOrNull computation and uses the same isPackageNullOrSystem(proxyPackageName, proxyUid) helper.",
      "AppOpsService.java:isPackageNullOrSystem (new): returns true if packageName is null, and true if UserHandle.getAppId(uid) is > 0 and < Process.FIRST_APPLICATION_UID. Otherwise it returns mPackageManagerInternal.isSystemPackage(packageName). Because of the appId > 0 lower bound, a non-null proxy package with appId 0 now goes to the isSystemPackage check. The old inline code returned true for any appId below FIRST_APPLICATION_UID.",
      "AppOpsService.java:finishOperationUnchecked: a new overload takes proxyUid and proxyPackageName along with proxiedUid/proxiedPackageName and calls verifyAndGetBypass(proxiedUid, proxiedPackageName, attributionTag, proxyUid, proxyPackageName). The old signature becomes a thin wrapper that passes proxyUid = -1 and proxyPackageName = null. The getOpLocked lookup and the error logs that follow use proxiedUid/proxiedPackageName.",
      "AppOpsService.java:finishProxyOperation: now calls the new overload with proxyUid and resolvedProxyPackageName, so proxy identity counts in attribution tag validation on finish."
     ],
     "completeness": [
      "Covers the ROOT_UID branch and the special-UID branch of verifyAndGetBypass, plus the finishProxyOperation path.",
      "Callers of the old finishOperationUnchecked signature pass a null proxy package through the wrapper, so isPackageNullOrSystem returns true for them. The material does not show how other non-proxy callers of verifyAndGetBypass delegate.",
      "The diff does not touch the Process.isSdkSandboxUid branch or the general package verification path of verifyAndGetBypass. The material does not show how they treat proxy identity.",
      "The check relies on mPackageManagerInternal.isSystemPackage(proxyPackageName). The material does not show how resolvedProxyPackageName is checked against proxyUid upstream."
     ],
     "behavior_changes": [
      "When the proxied UID is root and the proxy package is non-null and not a system package, the attribution tag is now treated as invalid: it is set to null and isAttributionTagValid=false is passed to getOpLocked.",
      "finishProxyOperation now passes the proxy identity into verifyAndGetBypass, so a non-system proxy finishing an op for a special UID gets its tag nulled instead of treated as valid.",
      "A proxy with appId 0 and a non-null package name now goes through the isSystemPackage check instead of being trusted automatically (new isPackageNullOrSystem bounds).",
      "Error log messages in finishOperationUnchecked now report proxiedUid/proxiedPackageName. These are the same values as before, only renamed."
     ],
     "preconditions": [
      "A non-system app calls proxy appops operations, including finishing a proxied op, with root or another special-cased UID as the proxied UID and supplies attribution tags. The access checks on those entry points are not shown in the material.",
      "The bulletin lists affected versions 13, 14, 15, 16 and 16-next."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base 1c6779245598 in android-security-14.0.0_r22 (RESTRICT AUTOMERGE). The logic is the same, but finishOperationUnchecked has no virtualDeviceId parameter, the catch block uses the older Process.isIsolated logging instead of logVerifyAndGetBypassFailure, and attributedOp is looked up via op.mAttributions.get(attributionTag).",
      "15": "same fix: platform/frameworks/base 7b88db4928f3 in android-security-15.0.0_r10 (cherry-pick, Change-Id I9b44465554e10b803bc9b4ab76130aaf9933f605). attributedOp is looked up via op.mDeviceAttributedOps with getPersistentId(virtualDeviceId).",
      "16": "same fix: platform/frameworks/base f745b6763bff in android-security-16.0.0_r1. The logic is identical, and attributedOp is looked up via getPersistentDeviceIdForOp(virtualDeviceId, code).",
      "17": "not affected: not listed by the bulletin. At android-security-17.0.0_r1, 5/8 lines of the patched region are present, and no commit is shown."
     },
     "backport_notes": [
      "Single commit. Apply the ROOT_UID branch change, the new isPackageNullOrSystem helper, and the passing of proxyUid/proxyPackageName from finishProxyOperation through the new finishOperationUnchecked overload together. Without the last piece, finishProxyOperation keeps using the verification path that ignores the proxy. Match the finishOperationUnchecked parameters to the branch: virtualDeviceId / mDeviceAttributedOps (15+) or op.mAttributions (14).",
      "At the latest security tags only 4/8 (15) to 5/8 (14, 16) of the patched region lines match. Check directly that isPackageNullOrSystem exists and that the ROOT_UID branch uses it."
     ],
     "confidence": "high",
     "unknowns": [
      "How the note/start proxy paths reach verifyAndGetBypass, and whether they already passed proxy identity before this fix.",
      "Which permission or caller checks gate the proxy op entry points for a non-system app that names root as the proxied UID.",
      "The exact memory or resource impact. It can only be inferred from the AppOpsMemoryUsageTest reference and the DoS classification.",
      "How the 3-arg verifyAndGetBypass delegates to the 6-arg version.",
      "Why only part of the patched region matches at 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-48560": {
     "cve": "CVE-2025-48560",
     "summary": "Moves the ACCESSIBILITY_MOTION_EVENT_OBSERVING permission check in AbstractAccessibilityServiceConnection out of the property-setting code that ran under a cleared Binder identity, and into the service-info update method before Binder.clearCallingIdentity(). The check is now evaluated against the calling accessibility service instead of system_server, and requested observed motion event sources are reset to 0 when the caller lacks the permission.",
     "component": "platform/frameworks/base, services/accessibility (AbstractAccessibilityServiceConnection)",
     "weakness": "Permission check performed under the wrong identity: per the commit message, the check ran inside a Binder clear-identity call and so used system_server's permissions. In effect, this is a missing permission check.",
     "root_cause": "In AbstractAccessibilityServiceConnection.java, the code that copies info.getObservedMotionEventSources() into mObservedMotionEventSources was guarded by mContext.checkCallingOrSelfPermission(ACCESSIBILITY_MOTION_EVENT_OBSERVING), gated on Flags.motionEventObserving(). Per the commit message, this check ran inside a Binder clear-identity call, so it used the permissions of system_server instead of those of the calling AccessibilityService.",
     "fix_analysis": [
      "AbstractAccessibilityServiceConnection.java, property-setting code (hunk at about line 405 on 15 and line 420 on 16): the flag-gated checkCallingOrSelfPermission block is removed and replaced by an unconditional mObservedMotionEventSources = info.getObservedMotionEventSources();.",
      "Same file, in the service-info update method (the one that throws 'Cannot update service info: size is larger than safe parcelable limits.'): a new check runs before Binder.clearCallingIdentity(). If info.getObservedMotionEventSources() != 0 and mContext.checkCallingPermission(ACCESSIBILITY_MOTION_EVENT_OBSERVING) != PERMISSION_GRANTED, it logs via Slog.e and calls info.setObservedMotionEventSources(0).",
      "The new check uses checkCallingPermission instead of checkCallingOrSelfPermission. A static import of ACCESSIBILITY_MOTION_EVENT_OBSERVING is added."
     ],
     "completeness": [
      "Covers the service-info update entry point, where the new check runs before the identity is cleared.",
      "The property-setting code no longer performs any permission check itself. Any other path that supplies an AccessibilityServiceInfo with non-zero observed motion event sources to that code is not covered by the new check. The material does not show whether such paths exist.",
      "The removed code was gated on android.view.accessibility.Flags.motionEventObserving(). The replacement assignment is unconditional, so the flag no longer gates the copy into mObservedMotionEventSources.",
      "No change to how mObservedMotionEventSources is consumed downstream. Only the setter side is changed."
     ],
     "behavior_changes": [
      "When a caller without ACCESSIBILITY_MOTION_EVENT_OBSERVING passes non-zero observed motion event sources in a service-info update, the sources are now reset to 0 and a Slog.e message is logged. No exception or error code is returned to the caller.",
      "Observed motion event sources are copied from the info regardless of the motionEventObserving flag state."
     ],
     "preconditions": [
      "The caller is an accessibility service that updates its service info through the connection, with non-zero observed motion event sources."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14 (7/8 lines of the patched region present at android-security-14.0.0_r29).",
      "15": "Same fix: commit 580118ec5b41 in android-security-15.0.0_r10 (cherry-pick of 01bfef47f3b1, Change-Id If64838388fa31bdc9abb0896d4011bfef8501a7c). 7/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "Same fix: commit ff8b0d46a53a in android-security-16.0.0_r1. The diff is identical, with hunks offset by about 15 lines. 7/8 lines of the patched region 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 present at android-security-17.0.0_r1)."
     },
     "backport_notes": [
      "Single-commit fix. The diff is identical on 15 and 16 apart from line offsets. The fix drops the Flags.motionEventObserving() gate entirely. Keep the new check before Binder.clearCallingIdentity() in the service-info update method."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact names of the enclosing methods and whether the update method calls the property-setting code; the diff hunks do not show this.",
      "Whether other paths into the property-setting code (for example, initial bind) can supply non-zero observed motion event sources.",
      "What data a service without the permission could observe before the fix; the consumer side is not in the material."
     ],
     "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-48562": {
     "cve": "CVE-2025-48562",
     "summary": "PrintSpooler now opens the destination URI with mode \"wt\" (write + truncate) in RemotePrintDocument.writeContent when saving a print job to PDF, so overwriting an existing file requests an emptied output stream instead of possibly leaving old content of the previous file in the output.",
     "component": "platform/frameworks/base, packages/PrintSpooler (RemotePrintDocument, save-to-PDF path)",
     "weakness": "Missing truncation when overwriting an existing output file: data from the previous file content can be retained (information disclosure, per bulletin type ID)",
     "root_cause": "RemotePrintDocument.writeContent copies the rendered PDF from the spooler's file provider into the user-selected destination URI. Before the fix it did this with contentResolver.openOutputStream(uri), which did not explicitly request truncation. ContentResolver's one-argument openOutputStream uses mode \"w\", and whether \"w\" truncates depends on the provider (general platform knowledge). Per the commit message, the fix requests a truncated stream in case the user selects to overwrite an existing file. If the stream is not truncated and the old file is larger than the new PDF, the old file's trailing content would stay in the output.",
     "fix_analysis": [
      "packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java:writeContent: the call contentResolver.openOutputStream(uri) is changed to contentResolver.openOutputStream(uri, \"wt\"). This explicitly requests write-with-truncate so the destination is emptied before the PDF bytes are copied.",
      "No other logic changes: the copy loop (8192-byte buffer), the IOException handling (logged via Log.e) and the finally block that closes the streams and calls fileProvider.releaseFile() are unchanged."
     ],
     "completeness": [
      "The fix covers the single openOutputStream call in RemotePrintDocument.writeContent, which is the only output path shown in the material.",
      "Correctness depends on the backing content provider honoring the \"wt\" mode. The fix does not verify truncation (for example, no size check and no explicit truncate after opening).",
      "Failure behavior on an IOException mid-copy is unchanged: a partially written file is left as is and the error is only logged."
     ],
     "behavior_changes": [
      "If the provider honors \"wt\", an existing destination file selected for overwrite is truncated before the new PDF content is written, so no old trailing content remains.",
      "Any IOException raised when opening or writing the stream is still caught and logged as \"Error writing document content.\" (unchanged handler)."
     ],
     "preconditions": [
      "The user prints to the Save as PDF destination through PrintSpooler and picks an existing file as the output, so RemotePrintDocument.writeContent is invoked with that file's URI (from the commit message and code).",
      "Inferred: the existing file is larger than the new PDF, and the destination provider does not truncate in default write mode."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base a52780f0ce9e in android-security-14.0.0_r22, an identical one-line change to RemotePrintDocument.writeContent (same pre/post blob ids as the 15 commit). 4/5 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 252efec89b01 in android-security-15.0.0_r10. The code at android-security-15.0.0_r17 shows openOutputStream(uri, \"wt\") in place (4/5 lines of the patched region are present per the per-major facts).",
      "16": "same fix: platform/frameworks/base 177eda822164 in android-security-16.0.0_r1, an identical hunk (different blob ids, same context lines). 4/5 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not listed by the bulletin (not affected per the bulletin). 4/5 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single self-contained one-line change. The hunk context (acquireFile(null), FileInputStream, 8192-byte buffer loop) is identical on 14, 15 and 16, so it should apply cleanly wherever RemotePrintDocument.writeContent still calls the one-argument openOutputStream."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which content providers (for example, the one behind the Save as PDF picker) failed to truncate in default mode.",
      "The material does not show whether all destination providers accept \"wt\", or how a provider that rejects the mode would affect saving.",
      "The upstream commits d65493c65a94 and be7962d37f07 referenced in the cherry-pick lines are not included. Only the AOSP security-branch versions are shown.",
      "The material does not explain why only 4/5 lines of the patched region match at the latest tags."
     ],
     "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-2025-48563": {
     "cve": "CVE-2025-48563",
     "summary": "Adds an onNullBinding() override to RemoteFillService that calls unbind(), so an autofill service returning null from onBind() is no longer kept bound by system_server with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS.",
     "component": "platform/frameworks/base, system_server autofill service (services/autofill, RemoteFillService)",
     "weakness": "Missing handling of a null service binding (no onNullBinding callback) that leaves a privileged binding in place; improper resource/privilege lifecycle management",
     "root_cause": "RemoteFillService extends ServiceConnector.Impl<IAutoFillService> and binds the autofill service component with Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS (constructor, RemoteFillService.java lines 101-104). The class did not override ServiceConnection.onNullBinding(), so nothing in RemoteFillService reacted when the bound service returned null from onBind(). Per the commit message, such a service therefore stayed bound by system_server and could launch activities from the background, which matches the BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag on that binding.",
     "fix_analysis": [
      "services/autofill/java/com/android/server/autofill/RemoteFillService.java:onNullBinding(ComponentName) is added as an @Override of the ServiceConnection callback. It logs at verbose level when sVerbose is set and then calls unbind().",
      "Effect, per the commit message: an autofill service whose onBind() returns null no longer stays bound by system_server, so the binding and its background-activity-start allowance do not persist."
     ],
     "completeness": [
      "The fix covers the null-binding case for RemoteFillService (the IAutoFillService connection). It is a single 8-line addition and does not change the bind flags or ServiceConnector itself.",
      "Only RemoteFillService is changed. The material does not show whether other ServiceConnector.Impl subclasses or other bindings that use BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS have the same gap.",
      "The material does not show the ServiceConnector.Impl code, so it is not visible whether any other state (pending jobs, callbacks) is cleaned up on this path."
     ],
     "behavior_changes": [
      "When an autofill service returns null from onBind(), system_server now calls unbind() instead of leaving the binding in place.",
      "A new verbose log line \"onNullBinding\" is emitted under TAG when sVerbose is enabled."
     ],
     "preconditions": [
      "system_server's autofill subsystem builds a RemoteFillService for the attacker's component and binds it (AutofillService.SERVICE_INTERFACE intent). The material does not show how that component is selected.",
      "The autofill service's onBind() returns null, which triggers the onNullBinding() path.",
      "The binding uses Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, which the RemoteFillService constructor always sets."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 1f704bff05f0 in android-security-14.0.0_r22, the same 8-line onNullBinding() addition at a different offset (hunk @@ -94); 2/2 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base a6a570a6f497 in android-security-15.0.0_r10 (cherry-picked from d883efa912c0 / 0cbc42f4dc6b, Change-Id Ieb3e63fe2ac1f440be3d164730ef3110209ec1a6); 2/2 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base dee86220f445 in android-security-16.0.0_r1, same addition (hunk @@ -117); 2/2 lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); the patched region (2/2 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained 8-line commit, applied the same way on 14, 15 and 16. Android 13 is listed as affected by the bulletin, but the material includes no Android 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show ServiceConnector.Impl, so the state of the connection (pending jobs, auto-disconnect timer) after a null binding, before and after the fix, is not visible.",
      "How the autofill service component is selected and bound is not shown.",
      "The material includes no Android 13 fix commit, although the bulletin lists 13 as affected.",
      "The STS test Bug_401545800 referenced in the commit message is not included.",
      "Whether other system_server bindings that use BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS also lack onNullBinding handling 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."
     }
    }
   }
  },
  "16": {
   "counts": {
    "announced": 42,
    "certain": 42,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2025-32321",
     "osv_id": "ASB-A-378902342",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AccountTypePreferenceLoader.isSafeIntent, once an authenticator-supplied preference intent has been confirmed to resolve to an activity with the authenticator's uid, the fix pins the intent to that resolved component with intent.setComponent().",
      "confidence": "medium",
      "backport_notes": [
       "This is a single, self-contained commit to one method. The hunk is identical on 14, 15 and 16, with only a one-line offset on 14. Because the fix works by changing the passed-in Intent, check that your Settings caller launches that same instance."
      ],
      "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/apps/Settings",
       "sha": "e985e294f15db1267bd56a91952976b54f9970ae",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32323",
     "osv_id": "ASB-A-397216537",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DocumentsUI now sanitizes the calling application's label with TextUtils.makeSafeForPresentation (500 chars, trimmed, single line) in Shared.getCallingAppName before it is shown in the ACTION_OPEN_DOCUMENT_TREE confirmation dialog, and quotes the app name in the dialog message string.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The Shared.java hunk is the same across 14/15/16. If the target has a different open_tree_dialog_message wording, add the quotes by hand.",
       "TextUtils.makeSafeForPresentation and the SAFE_STRING_FLAG_* constants have been public SDK API since API level 30 (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_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/apps/DocumentsUI",
       "sha": "35a75b2455084a2c77663682780d7b64fb60c161",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2025-32324",
     "osv_id": "ASB-A-406763872",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The `am start-in-vsync` shell command no longer calls runStartActivity() from inside a Choreographer frame callback posted through mUiHandler. It now only waits for that callback and then starts the activity on the same thread that runs the shell command, fixing an activity start 'with incorrect pid/uid' (commit title).",
      "confidence": "high",
      "backport_notes": [
       "Self-contained one-hunk change in ActivityManagerShellCommand.onCommand. Its only dependency is the \"start-in-vsync\" case with ProgressWaiter/runWithScissors. If a branch lacks that case, there is nothing to patch."
      ],
      "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_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/frameworks/base",
       "sha": "55361a549fc2a46a91c6cf23f9ea498eb04ef643",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32325",
     "osv_id": "ASB-A-402319736",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens libbinder Parcel::appendFrom so it refuses to write over existing objects in the destination, copies and acquires only the source objects actually inside the requested [offset, offset+len) range instead of an index span, clears mObjectsSorted after appending or after a non-tail object write, and keeps the RPC mObjectPositions vector sorted.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. Across 14/15/16 the only differences are context: the acquire_object signature (tagFds appears only in the 16 diff) and the growData condition line (mDataSize+len on 14/15, mDataPos+len on 16). validateReadData must exist in the target tree and must handle an unsorted mObjects table for the new check to work."
      ],
      "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_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/frameworks/native",
       "sha": "46e72b490edd80ed1a10cde29732a807f16c2754",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-32326",
     "osv_id": "ASB-A-365739560",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' AppRestrictionsFragment now starts a copy of the app-supplied restrictions Intent. The copy's component and package are set to the ActivityInfo that was actually vetted. Before the fix, Settings started a copy of the original untrusted Intent after the check.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained change to one file. The 14/15/16 commits are identical apart from line offsets. If a fork has modified assertSafeToStartCustomActivity, keep the resolved activityInfo in scope so that its getComponentName() and packageName can be used to build the returned Intent.",
       "The bulletin lists Android 13 as affected, but the material includes no Android 13 commit."
      ],
      "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/apps/Settings",
       "sha": "e12156e2d805c492f01f1d218a1e840a03edbddb",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32331",
     "osv_id": "ASB-A-404252173",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "KeyguardService.showDismissibleKeyguard() now returns early when ActivityManager reports that lock task mode is active. Folding a foldable with the \"Swipe up to continue\" setting therefore no longer replaces a pinned app with a dismissible keyguard.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix. The method body differs between 15 and 16 (KeyguardViewMediator path vs KeyguardInteractor path), so place the lock task check right after checkPermission() instead of relying on the surrounding context. ActivityManager must be injectable in SystemUI's Dagger graph; the Android 15 and 16 commits both inject it the same way."
      ],
      "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_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/frameworks/base",
       "sha": "705d322dc8b623a10ab46d7b0e7f8ee17b6f3570",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32345",
     "osv_id": "ASB-A-409318132",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' ContentProtectionTogglePreferenceController now disables the Content Protection main switch for every non-admin user (via !UserManager.isAdminUser()) instead of only for guest users (UserManager.isGuestUser()).",
      "confidence": "high",
      "backport_notes": [
       "The source change is a single condition swap (isGuestUser() -> !isAdminUser()) in updateState(). The test change uses ShadowUserManager.setIsAdminUser()."
      ],
      "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/apps/Settings",
       "sha": "dc41d27f1bd9ebab400a85e31e81315092fd9477",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32346",
     "osv_id": "ASB-A-337785563",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In VoicemailSettingsActivity.onActivityResult, the contact URI returned by a picker activity is now validated with ComponentCaller.checkContentUriPermission(FLAG_GRANT_READ_URI_PERMISSION) against the component that delivered the result, instead of only comparing the user ID encoded in the URI with the Phone app's own user ID; denial now logs an EventLog entry and throws SecurityException.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix, self-contained in VoicemailSettingsActivity.java. It depends on android.app.ComponentCaller, Activity.getCurrentCaller() and ComponentCaller.checkContentUriPermission(); a branch without these APIs needs an alternative way to identify the result's sender and check its URI grant.",
       "No fork comparison was provided in the material, so no per-file fork verdict can be reported."
      ],
      "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_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/services/Telephony",
       "sha": "ccfb4f0691ca97be5b5593e65bd26e843641f929",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/services/Telephony"
     ]
    },
    {
     "cve": "CVE-2025-32347",
     "osv_id": "ASB-A-388528350",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricEnrollIntroduction now removes the MultiBiometricEnrollHelper extras EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT from its launch intent in onStart() whenever the calling package is not Settings itself.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit in packages/apps/Settings touching BiometricEnrollIntroduction.java (plus two robolectric tests); it relies on MultiBiometricEnrollHelper defining the three EXTRA_* constants and adds a new onStart() override to BiometricEnrollIntroduction."
      ],
      "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/apps/Settings",
       "sha": "d50de07930ee2dc3f896d8932be3dbeb9c25b939",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-32349",
     "osv_id": "ASB-A-408215749",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowManager now ignores app-supplied window animations for untrusted TYPE_TOAST windows (forcing R.style.Animation_Toast) and uses a fixed short alpha fade-out when forcibly hiding non-system overlay windows, so an app-chosen exit animation no longer controls how long such windows stay visible after the system asks for them to be hidden.",
      "confidence": "high",
      "backport_notes": [
       "The two commits share no symbols in the diffs, so they can be applied independently; apply both. The WindowStateAnimator change relies on the pre-existing WindowState.mForceHideNonSystemOverlayWindow field and config_shortAnimTime. The DisplayPolicy change relies on R.style.Animation_Toast and Session.mCanAddInternalSystemWindow.",
       "The material shows only one of the two commits for Android 14 (animator) and one for Android 15 (DisplayPolicy). Check that the other commit is present on the target branch before treating the backport as complete."
      ],
      "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_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/frameworks/base",
       "sha": "3a76075721bb9fa7ab20bd2eb56f490a87c84f19",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "ab5874fecde66f75d74347cc1283ef40a18fc87c",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-32350",
     "osv_id": "ASB-A-404256832",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes android:exported=\"true\" from SystemUI's ControlsActivity manifest entry and adds SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS to the window of the dialog built in ControlsSettingsDialogManager.kt, so other packages can no longer launch the activity and non-system overlay windows can no longer cover the lockscreen-controls settings dialog.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained: one manifest attribute, one addSystemFlags call and one test. The only per-branch drift is the SystemUI R import path (com.android.systemui.R on 14 vs com.android.systemui.res.R on 15/16) and the manifest line offset. Window.addSystemFlags and SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS are hidden platform APIs that SystemUI can use as a platform-signed app built against the framework (general platform knowledge).",
       "Before dropping android:exported, check that no downstream customization starts ControlsActivity from outside the SystemUI package, since the commit removes the attribute unconditionally."
      ],
      "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_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/frameworks/base",
       "sha": "d6b86833ee06789e557015d75d961d39407ab937",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48522",
     "osv_id": "ASB-A-418773283",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a 1024-character upper bound on the display name accepted by AssociationRequest.Builder.setDisplayName in the Companion Device Manager API, throwing IllegalArgumentException for longer values.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained: one constant plus a 4-line check inside Builder.setDisplayName, with no dependency on other commits shown. The 14, 15 and 16 commits carry the same hunk content at different line offsets. In the 16 file, the mDeviceIcon field follows the constant.",
       "The bulletin lists Android 13 as affected, but the material includes no 13 commit. The hunk targets Builder.setDisplayName where checkNotUsed() is followed by mDisplayName = requireNonNull(displayName)."
      ],
      "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_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/frameworks/base",
       "sha": "eb3cf182f9299ddf4013db10881614395b2b8965",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48523",
     "osv_id": "ASB-A-388032224",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the branch in Contacts' SelectAccountActivity.onCreate that, when exactly one writable account existed, immediately returned that account as RESULT_OK and finished without showing the account selection dialog, so the vCard import target account must now be selected by the user in the dialog.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, deletion-only change (9 lines) with identical pre/post blob hashes on 14, 15 and 16, so it should apply cleanly to any branch whose SelectAccountActivity.onCreate still contains the `else if (accountList.size() == 1)` block; after applying, verify that showDialog(resId) is reached whenever accountList.size() >= 1."
      ],
      "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": [
      "change_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/apps/Contacts",
       "sha": "c314edb9bde88247781af2c0e8bf494b458260d4",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref c314edb9bde8",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Contacts"
     ]
    },
    {
     "cve": "CVE-2025-48524",
     "osv_id": "ASB-A-399885815",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WifiPermissionsUtil.isSystem() now returns false early when the calling uid is an SDK sandbox uid (on T and later), so an SDK sandbox caller is no longer classified as a system app from the package name's ApplicationInfo.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained 3-line hunk. It depends on Process.isSdkSandboxUid and SdkLevel.isAtLeastT() being available in the tree.",
       "Android 13 is listed as affected, but the material includes no Android 13 commit. The same hunk should apply to isSystem there if the method has the same shape."
      ],
      "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_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/modules/Wifi",
       "sha": "c47809aec03172f8fa097b61b2130e727cb48618",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2025-48526",
     "osv_id": "ASB-A-407764858",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ChooserActivity now hands cross-profile tabs a sanitized copy of the payload intents (package and component cleared on each intent and its selector) instead of the caller's original, possibly explicit, intents.",
      "confidence": "high",
      "backport_notes": [
       "For 15/16-style trees the fix depends on the ChooserRequest data model and ProfileHelper.getLaunchedAsProfile(); for 14-style trees (ChooserActivity extends ResolverActivity, mIntents/selectedProfile) use the android-security-14.0.0_r22 shape and the nullable-list IntentUtils.kt variant.",
       "IntentUtils.kt is created by this commit; on branches where java/src/com/android/intentresolver/util/IntentUtils.kt already exists (e.g. with prepareCrossProfileIntents), add sanitizePayloadIntents to the existing file rather than taking the new-file hunk."
      ],
      "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_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/modules/IntentResolver",
       "sha": "247af9e3e487aabb03d7aad79f71e9f78571d142",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48527",
     "osv_id": "ASB-A-378088320",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' Notification history screen now shows the generic \"notification_hidden_text\" string, with no title, in place of the title and text of historical, snoozed and dismissed notifications. This applies to any profile of the current user that has a secure lock, is currently locked, and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS disabled.",
      "confidence": "high",
      "backport_notes": [
       "The change touches only three Settings files. It uses com.android.internal.widget.LockPatternUtils, KeyguardManager.isDeviceLocked(int), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS and com.android.internal.R.string.notification_hidden_text, all of which Settings can access (general platform knowledge). Any fork code or tests that construct NotificationHistoryAdapter or NotificationSbnAdapter directly must be updated to the new constructor signatures.",
       "The 14 and 16 variants are logically identical. The 15 variant also adds clear() calls on mContentRestrictedUsers in onCreate and onDestroy, plus comments."
      ],
      "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_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/apps/Settings",
       "sha": "504a34c4d4a3b216fdac7bd1daa3077e7a6d0844",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48528",
     "osv_id": "ASB-A-384727394",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes the BiometricPrompt window (AuthContainerView) back to window type TYPE_KEYGUARD_DIALOG from TYPE_APPLICATION_OVERLAY. On Android 16 it also binds the UDFPS touch overlay for REASON_AUTH_BP to a new PromptUdfpsTouchOverlayViewModel that always handles touches.",
      "confidence": "medium",
      "backport_notes": [
       "The window type change in AuthContainerView.getLayoutParams() is common to both versions. Google used a plain revert on 15 and a larger refactor on 16. When taking the 16 version: PromptUdfpsTouchOverlayViewModel has an @Inject constructor and is injected as Lazy<> into UdfpsController. The UdfpsControllerOverlay constructor and show() signature changes require matching updates in UdfpsController and in any downstream callers or tests.",
       "Check whether the target branch also has mPanelInteractionDetector.enable() moved under !constraintBp(). The 15 revert undoes that move; the 16 commit does not touch it."
      ],
      "meta": {
       "generated": "2026-10-04",
       "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-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/frameworks/base",
       "sha": "c2774dc575e9de48d04e05c826cfe04a2fa5f77c",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48529",
     "osv_id": "ASB-A-325030433",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the shared-preference storage, read-back and channel migration of the voicemail ringtone URI in frameworks/opt/telephony. As a result, a URI supplied through TelephonyManager#setVoicemailRingtoneUri is no longer persisted by the phone process, migrated onto the voicemail NotificationChannel, or returned by getVoicemailRingtoneUri.",
      "confidence": "high",
      "backport_notes": [
       "A single self-contained commit with an identical diff on 14, 15 and 16. It deletes the public static getRingTonePreference(Context), so any fork code that calls it must be updated. The SharedPreferences and PreferenceManager imports must stay because the vibration methods still use 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_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/frameworks/opt/telephony",
       "sha": "f33f9ef6467b0c9c4e1fbb7b2449c8b46b9792a3",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/opt/telephony"
     ]
    },
    {
     "cve": "CVE-2025-48531",
     "osv_id": "ASB-A-389681530",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' CredentialStorage now identifies its caller via ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) instead of Activity.getCallingPackage() when deciding whether the caller is com.android.certinstaller.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that relies on ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) being available. The same diff appears on 14, 15 and 16, differing only in line offsets."
      ],
      "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_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/apps/Settings",
       "sha": "0f61e586873a164175543563a1f09f395595b68a",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48532",
     "osv_id": "ASB-A-417194323",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider.markMediaAsFavorite no longer applies a caller-supplied ContentValues from the call extras. It now reads only a boolean IS_FAVORITE extra and builds the ContentValues itself, and the MediaStore client helper now sends that boolean instead of EXTRA_CONTENT_VALUES.",
      "confidence": "high",
      "backport_notes": [
       "A single commit touches both halves of the module: apex/framework MediaStore.java (client) and MediaProvider.java (provider). Apply both together, because the extras contract changes from EXTRA_CONTENT_VALUES to a boolean MediaColumns.IS_FAVORITE extra. MediaProvider is a mainline module, so both halves normally ship together (general platform knowledge)."
      ],
      "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_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": "a6fcb52734f2a873453e88ef9b7c15b17830cfb3",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2025-48535",
     "osv_id": "ASB-A-353680402",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent returned by an app is now copied before vetting, and the copy is pinned to the exact component and package that passed the resolveActivity/package check. That pinned copy, not a fresh copy of the caller-supplied Intent, is what startActivityForResult launches.",
      "confidence": "high",
      "backport_notes": [
       "Apply the two commits in order. First the \"use vetted component\" change (return type change of assertSafeToStartCustomActivity and the onReceive call site), then the \"earlier new Intent\" change, which moves the copy above resolveActivity. Both touch only src/com/android/settings/users/AppRestrictionsFragment.java."
      ],
      "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_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/apps/Settings",
       "sha": "25ed933c4cf5acb7a15c146332f9586f3dd1abe0",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "e12156e2d805c492f01f1d218a1e840a03edbddb",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48537",
     "osv_id": "ASB-A-391894257",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes ZenModeConditions track CPS subscriptions by (condition Uri, ComponentName) instead of Uri alone. It also restricts matching against built-in system ConditionProviderServices (schedule, event, etc.) to system-owned rules and the manual rule, and rejects non-system rules whose component lives in the \"android\" package.",
      "confidence": "high",
      "backport_notes": [
       "On branches where evaluateRule lacks the isManual parameter (as on 14), the fix must also pass isManual through from evaluateConfig (true for config.manualRule, false for automatic rules), because isSystemRule depends on it.",
       "The ConditionProviders changes (getRecord, package-private ConditionRecord) are only needed for the new ZenModeConditionsTest. The runtime fix is entirely in ZenModeConditions.java."
      ],
      "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_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/frameworks/base",
       "sha": "57451c039840812b630f50f16b5b46873df512d1",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48538",
     "osv_id": "BUL-CVE-2025-48538",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PackageManagerService's package-hiding path now refuses to hide the SystemUI package (resolved via PackageManagerInternal.getSystemUiServiceComponent()) in addition to the \"android\" package, returning false and logging a warning.",
      "confidence": "high",
      "backport_notes": [
       "The hunk is identical across 14/15/16 (only line offsets differ) and depends on PackageManagerInternal.getSystemUiServiceComponent() and LocalServices being available in the target branch.",
       "The per-major facts report only 1/3 of the patched lines present at the latest 14/15/16 security tags even though the fix is resolved. Check the current form of the check in the target branch before assuming it is missing (the region may have been reworded or moved later)."
      ],
      "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_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/frameworks/base",
       "sha": "4e1be9d5eb9b4f73823bc219183b66a494417602",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48539",
     "osv_id": "ASB-A-406785684",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In AclArbiter::SendPacketToPeer (system/stack/arbiter/acl_arbiter.cc), the warning log on the failure path now reports buffer.size() from the rust::Vec the function still owns. Before, it read p_buf->len from a BT_HDR that L2CA_SendFixedChnlData may already have freed.",
      "confidence": "high",
      "backport_notes": [
       "A single one-line change with no dependencies. On trees with the older call style (L2CA_SendFixedChnlData(...) != L2CAP_DW_SUCCESS), apply the Android 15 variant c881220f499e. Otherwise apply 33be5d45cb27."
      ],
      "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_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/modules/Bluetooth",
       "sha": "33be5d45cb275aa9c13d975a4c556b2e7a769089",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-48540",
     "osv_id": "ASB-A-416734088",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Three libbinder RPC Binder commits change RpcState.cpp so that it ends the RPC session (session->shutdownAndWait(false)) on error returns that previously left the session open: CommandData allocation failure (NO_MEMORY) in waitForReply and processTransact, object-table span failure (BAD_VALUE) in processTransactInternal, and RPC_COMMAND_TRANSACT received under a restricted CommandType (BAD_TYPE) in processCommand.",
      "confidence": "medium",
      "backport_notes": [
       "The commits on 16 are consecutive (300a057414d7 (ENOMEM), then 91aec28d0f59 (SPAN), then a9e6d5a3c679 (transact type)). All of them touch only libs/binder/RpcState.cpp and add a few lines around existing return statements. Offsets differ between branches: the SPAN hunk is at line 946 on 14 and line 987 on 16.",
       "For 13/14/15 branches, check that all three changes are present, because the material shows only one of the three commits for each of 14 and 15. A tree has all three changes when shutdownAndWait is called after the object-table span ALOGE in processTransactInternal, after both CommandData valid() checks, and in the RPC_COMMAND_TRANSACT/BAD_TYPE branch."
      ],
      "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_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/frameworks/native",
       "sha": "300a057414d7705457370bd07199bef92526f84a",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "91aec28d0f5979d083b46e02e87c0b7d2e2587d2",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/native",
       "sha": "a9e6d5a3c6797ad715a0e54a472c58a0c9468215",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2025-48541",
     "osv_id": "ASB-A-411418366",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "FaceSettings now reads the hw_auth_token, sensor id, challenge and EXTRA_USER_ID intent extras only when the SettingsActivity's initial calling package is Settings itself. For any other caller, or when no calling package is known, it ignores them and uses the current user.",
      "confidence": "high",
      "backport_notes": [
       "The diff touches only FaceSettings.java and applies with small context adjustments across 14 to 16. It depends on SettingsActivity.getInitialCallingPackage() and on the fragment being hosted in a SettingsActivity, because the cast will throw otherwise. Check that both hold on the target branch. Android 13 is listed as affected, but the material contains no 13 commit."
      ],
      "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_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/apps/Settings",
       "sha": "2aa27461a40b17462e57801d790545a7aeb897be",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-48542",
     "osv_id": "ASB-A-273501008",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds per-account storage accounting to AccountManagerService. New user-data and auth-token writes are refused once an account has used its share of a 30,000,000-char per-user budget, and passwords longer than 1,000,000 chars are rejected when an account is added or a password is set.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches only AccountManagerService.java and adds no dependencies. On older trees, adjust for the two context differences seen in the 14/15 versions: setUserdataInternal without callingUid, and cancelNotification(..., UserHandle.of(accounts.userId)). The hunk guarding insertAuthToken must stay inside the existing transaction, before setTransactionSuccessful, so the preceding delete is not committed.",
       "The material provides no comparison against a fork, so no per-file fork verdict can be given."
      ],
      "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/frameworks/base",
       "sha": "50ae729881dabe9a78025b99bed77eef5b67c9f6",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48543",
     "osv_id": "ASB-A-421834866",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an IsInstantiable() check to the ART JNI AllocObject/NewObjectV/NewObjectA implementations, so that allocating an abstract class or interface throws java.lang.InstantiationException, and relaxes the CheckJNI wrapper so it aborts only for array classes.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained (two runtime files plus a test) and was applied with the same content on 14, 15 and 16, with only line-number drift. It uses mirror::Class::IsInstantiable(), IsInterface() and IsArrayClass(). The material indicates no dependency commit.",
       "ART ships as a mainline module, so the fix may also reach devices through an ART module update and not only through a platform security tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "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-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/art",
       "sha": "3eb64751e5a7e69b06a0979edbdfe9c4d503ea57",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2025-48545",
     "osv_id": "ASB-A-397438392",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountManagerService.isSystemUid() now returns false immediately when the calling UID is an SDK sandbox UID, so sandbox callers can no longer be classified as system callers via the package-flag lookup.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk. It applies wherever isSystemUid exists and Process.isSdkSandboxUid is available. Only the line offset differs between 14, 15 and 16."
      ],
      "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_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/frameworks/base",
       "sha": "c21f79312d3f22d854ddf4d1fd613fdae7cd182f",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48546",
     "osv_id": "ASB-A-388029380",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SafeActivityOptions now throws a SecurityException when the supplied ActivityOptions carry setLaunchWindowingMode(WINDOWING_MODE_PINNED), so an activity can no longer be started directly into the pinned (picture-in-picture) windowing mode through that option.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit: one import plus one if-block inserted before the getLaunchActivityType check in SafeActivityOptions. Only the line offset differs across 14/15/16 (about 407/391/372). Vendor or system components that launch into PiP through setLaunchWindowingMode(PINNED) will get a SecurityException.",
       "The bulletin lists Android 13 as affected, but the material includes no Android 13 commit."
      ],
      "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_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/frameworks/base",
       "sha": "3668e1d4bbecf4491a6a1fc3ba3271ac836996b6",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48547",
     "osv_id": "ASB-A-419105158",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PermissionController now calls PermissionManager.stopOneTimePermissionSession(packageName) after a revocation only when no granted permission of the package still carries FLAG_PERMISSION_ONE_TIME. This applies to both the legacy AppPermissionGroup.java revoke path and the KotlinUtils.kt revoke path. Before the fix, the package-wide one-time session was stopped whenever a single one-time group was revoked.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, two files. Port the new guard condition and the rewritten anyPermsOfPackageOneTimeGranted helper, and drop the `group` parameter at all call sites. The Android 14 variant shows the KotlinUtils.kt call site without the deviceId/ContextCompat.createDeviceContext wrapper. The material does not include an Android 13 fix.",
       "PermissionController ships as part of the Permission mainline module, so the fix may also arrive through a module update rather than a platform build (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-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/modules/Permission",
       "sha": "78fc913decb391bbe8d0ac234e9713dfce90b905",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2025-48550",
     "osv_id": "ASB-A-401256328",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds package-name validation (FrameworkParsingPackageUtils.validateName) to both PkgUser constructors in SlicePermissionManager, so a malformed package name is rejected with IllegalArgumentException instead of being stored.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It depends only on FrameworkParsingPackageUtils.validateName(String, boolean, boolean) and TextUtils, both already present on the 14, 15 and 16 branches where it applied. Android 13 is listed as affected, but the material includes no Android 13 commit, so check that the helper is available there before backporting."
      ],
      "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_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/frameworks/base",
       "sha": "bae8ab23b03d57b4e9a7c24eb087d058faf7fa73",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48551",
     "osv_id": "ASB-A-407991863",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Sharesheet \"Edit\" image action in IntentResolver is changed to start the image editor as the user that launched the sharesheet (mUserInteractor.getLaunchedAs(), or UserHandle.of(UserHandle.myUserId()) on Android 14) instead of always as the personal profile user.",
      "confidence": "high",
      "backport_notes": [
       "On Android 14 style branches (ChooserActivity extending ResolverActivity) Google used UserHandle.of(UserHandle.myUserId()); on 15+ it used mUserInteractor.getLaunchedAs(). Also confirm the companion flag-on change (ag/34038177, use_preferred_image_editor path) is present if that flag exists on the branch."
      ],
      "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_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/modules/IntentResolver",
       "sha": "9810de0fcb5a104f5297671d654b8e0f2fba143c",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48552",
     "osv_id": "ASB-A-365975561",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Wraps the three Settings.Global writes in DevicePolicyManagerService.saveGlobalProxyLocked in a try/catch so that an exception thrown by SettingsProvider while applying a device admin's global proxy no longer propagates and breaks the calling operation (for example removal of an ActiveAdmin).",
      "confidence": "high",
      "backport_notes": [
       "The 14/15 hunks change the qualifier from Settings.Global.* to Global.*. When backporting, make sure Global resolves in the target file, or keep the Settings.Global.* spelling.",
       "Single commit with no other commits in the fix. On trees older than 16, the removed lines use Settings.Global.* rather than Global.*."
      ],
      "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_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/frameworks/base",
       "sha": "d5fee58e64ae237d4720b67de152c72cad432eae",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48553",
     "osv_id": "BUL-CVE-2025-48553",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DevicePolicyManagerService.handlePackagesChanged now re-parses the device admin metadata of a changed DPC package (new isActiveAdminPackageValid) and drops the active admin when the package, receiver, or its admin metadata is missing or malformed; DeviceAdminInfo additionally converts OutOfMemoryError during metadata parsing into an XmlPullParserException.",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching two files. Both hunks belong together: the DPMS helper only catches Exception, so OOM during parsing is handled only if DeviceAdminInfo converts OutOfMemoryError to XmlPullParserException.",
       "The added code uses unqualified GET_META_DATA, MATCH_DIRECT_BOOT_AWARE and MATCH_DIRECT_BOOT_UNAWARE, and none of the 14/15/16 commits change imports. Forks should check that these static imports resolve in their DevicePolicyManagerService."
      ],
      "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_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/frameworks/base",
       "sha": "c0978bc04a25c0a1e5114974719f88232c35b5bb",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48554",
     "osv_id": "ASB-A-414603411",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DevicePolicyManagerService.handlePackagesChanged now re-validates an active admin's package on package change by fetching the receiver with GET_META_DATA and constructing a DeviceAdminInfo from it, removing the admin if the metadata is missing or malformed; DeviceAdminInfo's XML parsing additionally converts OutOfMemoryError into XmlPullParserException.",
      "confidence": "high",
      "backport_notes": [
       "Both files must go together: isActiveAdminPackageValid catches Exception, so without the DeviceAdminInfo OutOfMemoryError-to-XmlPullParserException conversion an OOM during the new parse would still escape the check.",
       "The new code uses unqualified GET_META_DATA / MATCH_DIRECT_BOOT_AWARE / MATCH_DIRECT_BOOT_UNAWARE and ActivityInfo; on branches where the removed lines used PackageManager.-qualified names (as in the 14 and 15 diffs), confirm the static imports and ActivityInfo import exist in DevicePolicyManagerService."
      ],
      "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_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/frameworks/base",
       "sha": "c0978bc04a25c0a1e5114974719f88232c35b5bb",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48556",
     "osv_id": "BUL-CVE-2025-48556",
     "source": "bulletin",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "NotificationChannel.setId() and setConversationId() now pass their string arguments through getTrimmedString(), which caps the channel id, parent channel id and conversation id at MAX_TEXT_LENGTH. Before the fix these values were stored without any length limit.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. Its only dependencies are the private getTrimmedString(String) helper and the MAX_TEXT_LENGTH constant already in NotificationChannel.java, which must exist in the target tree. The new tests use Strings.repeat, which the existing test file already uses, and Truth assertThat, which must be available in the test file."
      ],
      "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-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/frameworks/base",
       "sha": "0da1ac9dda567d507bf31d79ec2df68964d3afe9",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48558",
     "osv_id": "ASB-A-380885270",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BatteryService now resolves the ACTION_REQUEST_SHUTDOWN intent itself and pins it to an activity whose uid holds android.permission.SHUTDOWN before launching it, instead of sending an implicit intent that any matching activity could receive.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, single-file change applied the same way on 14, 15 and 16; only the import block context differs per branch. The new code calls PackageManagerInternal.queryIntentActivities(Intent, String, int, int, int), LocalServices and ActivityManagerInternal.getCurrentUserId()/isSystemReady(), so a fork must have these available.",
       "Android 13 is listed as affected by the bulletin, but the material includes no 13 commit or 13 code."
      ],
      "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/frameworks/base",
       "sha": "b1c60eeadb2c94113ce74c0f6a03b39a0e8ae727",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48559",
     "osv_id": "ASB-A-416491779",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppOpsService.verifyAndGetBypass no longer marks an attribution tag as valid for a ROOT_UID proxied op unless the proxy is null or a system package, and finishProxyOperation now passes the proxy UID/package through finishOperationUnchecked so the same proxy check applies when finishing ops for root and the other special proxied UIDs.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, single-file change. The only version-specific differences are: the finishOperationUnchecked signature (virtualDeviceId parameter present on 15/16, absent on 14); the attributed-op lookup (op.mAttributions vs op.mDeviceAttributedOps with getPersistentId / getPersistentDeviceIdForOp); and the SecurityException logging helper. The change requires mPackageManagerInternal.isSystemPackage, which the pre-existing special-UID branch already uses.",
       "Keep the old-signature finishOperationUnchecked wrapper (which passes proxyUid -1 and proxyPackageName null) so non-proxy callers are unchanged. Only finishProxyOperation switches to the new overload."
      ],
      "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_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/frameworks/base",
       "sha": "f745b6763bffabb0d28e17efbcef5b1d261b5814",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48560",
     "osv_id": "ASB-A-419110583",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Moves the ACCESSIBILITY_MOTION_EVENT_OBSERVING permission check in AbstractAccessibilityServiceConnection out of code that runs under Binder.clearCallingIdentity() and into the service-info update Binder method, before identity is cleared. The check is now evaluated against the calling accessibility service rather than system_server, and the requested observed motion event sources are set to zero when the caller lacks the permission.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit fix confined to AbstractAccessibilityServiceConnection.java. The same diff was applied on 15 and 16 at different line offsets. It depends on AccessibilityServiceInfo.getObservedMotionEventSources()/setObservedMotionEventSources() and on the existing Flags.motionEventObserving() block."
      ],
      "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_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/frameworks/base",
       "sha": "ff8b0d46a53a8317b68f534ace4e43ee5b1538b3",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48562",
     "osv_id": "ASB-A-423815728",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PrintSpooler's RemotePrintDocument.writeContent now opens the destination URI with mode \"wt\", so the output stream is truncated before the generated PDF is written. Previously it used the openOutputStream(uri) overload, which passes no mode.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained one-line change with no dependencies. The same hunk is used on 14, 15 and 16, and 14 and 15 share the same pre-image blob (bfc00bb8b94d). On a customized PrintSpooler, find the openOutputStream call in RemotePrintDocument.writeContent and add the \"wt\" mode argument."
      ],
      "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_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/frameworks/base",
       "sha": "177eda822164aee78245a3d474f3a10aa9f2bd64",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48563",
     "osv_id": "ASB-A-401545800",
     "source": "osv",
     "announced": "2025-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an onNullBinding() override to RemoteFillService that calls unbind(), so system_server no longer stays bound (with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) to an autofill service whose onBind() returns null.",
      "confidence": "high",
      "backport_notes": [
       "The patch adds one self-contained 8-line method. On 14, 15 and 16 the hunk content and surrounding context are identical and only the line offsets differ. It relies on RemoteFillService extending ServiceConnector.Impl, which provides unbind(). When applying it, check that ComponentName, NonNull and Slog are imported."
      ],
      "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_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/frameworks/base",
       "sha": "dee86220f445f19c87762dfd508f8d7f4f97bd9d",
       "tag": "android-security-16.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    }
   ],
   "tags": [],
   "unannounced": [],
   "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-32321": {
     "cve": "CVE-2025-32321",
     "summary": "In Settings' AccountTypePreferenceLoader.isSafeIntent, once an authenticator-supplied preference intent has been confirmed to resolve to an activity with the authenticator's uid, the fix pins the intent to that resolved component with intent.setComponent().",
     "component": "platform/packages/apps/Settings, accounts (AccountTypePreferenceLoader, authenticator-provided account preference intents)",
     "weakness": "Validation/launch mismatch: the intent's resolution was checked, but the intent was not bound to the component that was checked",
     "root_cause": "isSafeIntent(PackageManager, Intent, String accountType) resolved the intent with pm.resolveActivityAsUser(intent, 0, mUserHandle.getIdentifier()). It returned true if the resolved activity's applicationInfo.uid matched the uid of the authenticator package. The Intent object was not modified, so nothing tied a later launch to the component that passed the uid check. The commit message says this allowed arbitrary intents to be launched from Settings. The material does not show the caller or how the launch resolves the intent.",
     "fix_analysis": [
      "src/com/android/settings/accounts/AccountTypePreferenceLoader.java:isSafeIntent: the line 'return resolvedAppInfo.uid == authenticatorAppInf.uid;' is replaced with an if/else. On a uid match, the method calls intent.setComponent(resolvedActivityInfo.getComponentName()) on the passed-in Intent and returns true, which makes the intent explicit and bound to the resolved, checked activity. On a mismatch it returns false, as before.",
      "The rest of the method is unchanged: content-scheme intents are still rejected, a null ResolveInfo still returns false, and a NameNotFoundException still logs and returns false.",
      "The fix works through a side effect on the passed-in Intent. It is effective only if the caller then launches that same instance."
     ],
     "completeness": [
      "The change touches only the uid-match success path of isSafeIntent. Every intent that passes the check is now explicit.",
      "Only intents that go through isSafeIntent are affected. The fragment restriction through allowedFragments in the surrounding code (lines 236-241) is not touched.",
      "The fix assumes callers launch the same Intent instance that was passed to isSafeIntent. The caller code is not in the material, so this cannot be verified.",
      "The diff adds no new test."
     ],
     "behavior_changes": [
      "Authenticator-supplied preference intents that pass validation become explicit intents that target the resolved component.",
      "Rejection behavior is unchanged (content scheme, no resolution, uid mismatch, missing authenticator package)."
     ],
     "preconditions": [
      "An installed account authenticator supplies a preference with an Intent, which is the input to isSafeIntent.",
      "At validation time, the intent resolves to an activity whose application has the authenticator's uid. Otherwise it is rejected."
     ],
     "per_major": {
      "14": "Same fix: platform/packages/apps/Settings fd19bf11ef08 in android-security-14.0.0_r22, identical hunk with a one-line offset (@@ -264). At android-security-14.0.0_r29, 4/5 lines of the patched region are present.",
      "15": "Same fix: platform/packages/apps/Settings bb6e84fd04fc in android-security-15.0.0_r10, identical hunk. At android-security-15.0.0_r17, 4/5 lines of the patched region are present.",
      "16": "Same fix: platform/packages/apps/Settings e985e294f15d in android-security-16.0.0_r1 (cherry-pick of 6a896b6b26d4). The code context at android-security-16.0.0_r8 shows the full patched method, although the resolver reports 4/5 lines.",
      "17": "Not affected (not listed by the bulletin). At android-security-17.0.0_r1, 4/5 lines of the patched region are present."
     },
     "backport_notes": [
      "This is a single, self-contained commit to one method. The hunk is identical on 14, 15 and 16, with only a one-line offset on 14. Because the fix works by changing the passed-in Intent, check that your Settings caller launches that same instance."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show the caller of isSafeIntent or how the intent is started afterwards, so the launch-time mechanism and the user flow that reaches it are not shown.",
      "The material does not say whether Android 13 (listed as affected by the bulletin) received the fix.",
      "The '4/5 lines present' resolver metric is not explained. The Android 16 code context shows the complete fix, which suggests the metric is a measurement artifact."
     ],
     "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-32323": {
     "cve": "CVE-2025-32323",
     "summary": "DocumentsUI now sanitizes the calling application's label with TextUtils.makeSafeForPresentation (500 chars, trimmed, single line) in Shared.getCallingAppName before it is shown in the ACTION_OPEN_DOCUMENT_TREE confirmation dialog, and quotes the app name in the dialog message string.",
     "component": "platform/packages/apps/DocumentsUI, picker / open-document-tree confirmation dialog (Shared.getCallingAppName, ConfirmFragment strings)",
     "weakness": "Improper neutralization of caller-controlled text presented in a permission-like UI (UI spoofing via unbounded, multi-line application label)",
     "root_cause": "Shared.getCallingAppName returned pm.getApplicationLabel(ai).toString() unmodified. The label belongs to the calling application and was inserted into the open_tree_dialog_title / open_tree_dialog_message strings presented in the ConfirmFragment when an app requests directory access via ACTION_OPEN_DOCUMENT_TREE. Nothing limited the label's length or stripped newlines. Per the commit message, the unquoted placeholder also let the label read as a continuation of the dialog sentence.",
     "fix_analysis": [
      "src/com/android/documentsui/base/Shared.java:getCallingAppName: after the empty-label check, the label is passed through TextUtils.makeSafeForPresentation(result.toString(), 500, 0, SAFE_STRING_FLAG_TRIM | SAFE_STRING_FLAG_SINGLE_LINE). This cuts it to 500 characters, trims it and turns newlines into single-line text (the test expects 'label\\nlabel' to become 'label label'). The return type changes from String to CharSequence. Empty labels still return R.string.anonymous_application.",
      "res/values/strings.xml: open_tree_dialog_message now wraps the %1$s appName placeholder in double quotes, so the label is visually separated from the dialog's own text.",
      "src/com/android/documentsui/dirlist/DocumentsAdapter.java (Environment interface), src/com/android/documentsui/dirlist/DirectoryFragment.java (AdapterEnvironment), tests/common/.../TestEnvironment.java: the unused Environment.getCallingAppName() and its implementations are deleted. Per the commit message, it caused false positives when looking for callers of the Shared function. This is cleanup, not a functional change.",
      "tests/unit/com/android/documentsui/picker/ApplicationNameTest.kt: new parameterized test. It asserts that normal labels pass through unchanged, newlines become spaces, 600-char labels are cut to 500 and empty labels map to the anonymous name."
     ],
     "completeness": [
      "All sanitization happens in Shared.getCallingAppName, so every consumer of that helper (including the ConfirmFragment dialog) gets the bounded single-line label.",
      "Only open_tree_dialog_message received quotation marks. open_tree_dialog_title still embeds the appName placeholder without quotes (its CHAR_LIMIT=80 comment is a translator hint, not a runtime limit). The title still gets the 500-char / single-line sanitization.",
      "makeSafeForPresentation is called with ellipsizeDip 0 and without SAFE_STRING_FLAG_FIRST_LINE, so a 500-character label can still be long on screen.",
      "The material does not include the ConfirmFragment code or any other place where the caller's label or package name might be shown, so the diff does not let us judge coverage of other display points."
     ],
     "behavior_changes": [
      "Calling app labels longer than 500 characters are truncated. Leading and trailing whitespace is trimmed and newlines are replaced, so the label shows on one line.",
      "The open_tree_dialog_message text now shows the app name inside double quotes. Only the default res/values/strings.xml is changed in the shown diff.",
      "Shared.getCallingAppName returns CharSequence instead of String. Any caller that assigns the result to a String must adapt.",
      "DocumentsAdapter.Environment no longer declares getCallingAppName(). Implementations outside the shown files that use @Override on it would fail to compile."
     ],
     "preconditions": [
      "An app with a long or multi-line application label starts the DocumentsUI picker with ACTION_OPEN_DOCUMENT_TREE, and the user reaches the directory confirmation dialog (ConfirmFragment), which shows the calling app name.",
      "Shared.getCallingAppName must resolve the calling package via getCallingPackageName and PackageManager.getApplicationInfo. Otherwise the anonymous name is shown and the label is not used."
     ],
     "per_major": {
      "14": "Same fix: commit 1259beab2b8f in android-security-14.0.0_r22 makes identical changes (Shared.java, strings.xml, DocumentsAdapter.java, DirectoryFragment.java, TestEnvironment.java, new ApplicationNameTest.kt). Only hunk line numbers differ, and the Shared.java context lacks the RESTRICT_STORAGE_ACCESS_FRAMEWORK import. 7/8 patched-region lines are present at android-security-14.0.0_r29.",
      "15": "Same fix: commit f0336ada165f in android-security-15.0.0_r10. Its Shared.java change matches Android 14 (same blob ids 5ac9de4d7..9f328be69). 7/8 patched-region lines are present at android-security-15.0.0_r17.",
      "16": "Same fix: commit 35a75b245508 in android-security-16.0.0_r1 (cherry-pick of c8ef2db3bb46). 7/8 patched-region lines are present at android-security-16.0.0_r8. The code context at r8 shows the makeSafeForPresentation imports and the quoted message string in place.",
      "17": "Not affected per the bulletin (not listed). At android-security-17.0.0_r1, 4/8 lines of the patched region are present. The material does not show the code there."
     },
     "backport_notes": [
      "Single self-contained commit. The Shared.java hunk is the same across 14/15/16. If the target has a different open_tree_dialog_message wording, add the quotes by hand.",
      "TextUtils.makeSafeForPresentation and the SAFE_STRING_FLAG_* constants have been public SDK API since API level 30 (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The ConfirmFragment source that uses getCallingAppName is not included, so the material cannot confirm the exact display path or whether the unquoted placeholder in the title matters in practice.",
      "The material does not show why Android 17 is not listed by the bulletin or what its partially matching (4/8) code looks like.",
      "The material does not show the Android 13 fix, even though the bulletin lists 13 as affected.",
      "The material does not show whether other DocumentsUI screens display the calling app's label or package name without this sanitization."
     ],
     "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-32324": {
     "cve": "CVE-2025-32324",
     "summary": "The `am start-in-vsync` shell command no longer calls runStartActivity() from inside a Choreographer frame callback posted through mUiHandler. It now only waits for that callback and then starts the activity on the same thread that runs the shell command, fixing an activity start 'with incorrect pid/uid' (commit title).",
     "component": "platform/frameworks/base, ActivityManager shell command (services/core/java/com/android/server/am/ActivityManagerShellCommand.java)",
     "weakness": "Incorrect caller identity: the activity start ran in a UI-handler frame callback instead of on the shell command's own thread, which gave it an incorrect pid/uid (per commit title)",
     "root_cause": "In ActivityManagerShellCommand.onCommand, the \"start-in-vsync\" case used mInternal.mUiHandler.runWithScissors to post a Choreographer frame callback and called runStartActivity(pw) inside that callback. The start therefore ran on the UI handler's thread instead of the thread running the shell command, and per the commit title the activity was started with an incorrect pid/uid. The result was stored in a startResult array and returned after waiting on a ProgressWaiter.",
     "fix_analysis": [
      "ActivityManagerShellCommand.java:onCommand, case \"start-in-vsync\": the frame callback body is reduced to waiter.onFinished(0, null). It no longer calls runStartActivity(pw) and no longer catches and prints exceptions.",
      "ActivityManagerShellCommand.java:onCommand: the startResult int[] holder and its -1 initialisation are removed. After waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS) the command returns runStartActivity(pw) directly, so the start runs on the calling thread, just like the plain \"start\" / \"start-activity\" case just above it.",
      "Single-commit fix. The vsync wait is kept only as a timing step."
     ],
     "completeness": [
      "Covers the only place in the shown code where runStartActivity was called from a UI-handler/Choreographer callback. The sibling \"start\" / \"start-activity\" case already called runStartActivity(pw) directly and is unchanged.",
      "Choreographer.postFrameCallback is still posted through mUiHandler, but the callback now only signals the ProgressWaiter.",
      "Per the shown context, no other sub-command in the visible onCommand switch (startservice, broadcast, compact, freeze, instrument) uses the runWithScissors/frame-callback pattern. The rest of the file is not shown."
     ],
     "behavior_changes": [
      "For start-in-vsync, runStartActivity now runs after the vsync wait ends instead of inside the frame callback, and its return value is returned directly. Before the fix, -1 was returned if the start had not completed when the wait ended.",
      "Exceptions thrown by runStartActivity are no longer caught and printed as \"Error: unable to start activity, ...\" inside the callback. They now reach onCommand the same way as in the ordinary \"start\" case.",
      "The activity start now runs on the shell command's own thread, so it uses the same caller context as \"am start\"."
     ],
     "preconditions": [
      "Ability to run the ActivityManagerService shell command with the \"start-in-vsync\" sub-command (e.g. `am start-in-vsync`), as shown by the Test line and the onCommand switch.",
      "The start-in-vsync case must exist in the branch. The per-major data suggests it is absent on Android 14 (1/8 lines of the region present)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. Only 1/8 lines of the patched region are present at android-security-14.0.0_r29, which suggests the start-in-vsync code is absent.",
      "15": "Same fix: platform/frameworks/base 0fb2788dac39 in android-security-15.0.0_r10, identical diff at a 4-line offset (hunk @@ -246). 5/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/frameworks/base 55361a549fc2 in android-security-16.0.0_r1 (cherry-pick of fca9c788b336). 5/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). 5/8 lines of the patched region are present at android-security-17.0.0_r1, matching the fixed Android 16 code."
     },
     "backport_notes": [
      "Self-contained one-hunk change in ActivityManagerShellCommand.onCommand. Its only dependency is the \"start-in-vsync\" case with ProgressWaiter/runWithScissors. If a branch lacks that case, there is nothing to patch."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the body of runStartActivity or how the activity start path gets the caller's pid/uid. Which identity was used in the old callback path, and which checks were affected, can only be inferred from the commit title.",
      "How ProgressWaiter.waitForFinish behaves on timeout is not shown.",
      "Whether the original upstream commit fca9c788b336 included test changes is not shown (the diff contains only the Java change)."
     ],
     "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-32325": {
     "cve": "CVE-2025-32325",
     "summary": "Hardens libbinder Parcel::appendFrom so it refuses to write over existing objects in the destination, copies and acquires only the source objects actually inside the requested [offset, offset+len) range instead of an index span, clears mObjectsSorted after appending or after a non-tail object write, and keeps the RPC mObjectPositions vector sorted.",
     "component": "platform/frameworks/native, libs/binder (Parcel)",
     "weakness": "Missing overlap validation, plus an unsound sortedness assumption about object metadata (the object table can become inconsistent with the parcel data)",
     "root_cause": "Parcel::appendFrom in libs/binder/Parcel.cpp had two problems. (1) It did not check whether the destination range [mDataPos, mDataPos+len) overlapped objects already in the destination, so appending at a rewound data position could overwrite object bytes with plain data while the object table still pointed at them. (2) To pick source objects, it scanned otherKernelFields->mObjects, recorded the firstIndex and lastIndex of entries inside the range, and then copied every entry from firstIndex to lastIndex. That is only correct if mObjects is sorted. setDataPosition clears mObjectsSorted, and the fix comment says an object write that is not at the end of the data can break the ordering. So entries between firstIndex and lastIndex could lie outside the range. For those entries, off = objects[i] - offset + startPos was computed relative to data that was not copied (and could underflow when objects[i] < offset). That gave object offsets that do not match a copied object in the destination, and those offsets were then acquired. The kernel path also never cleared mObjectsSorted after appending, and the RPC path added to mObjectPositions with push_back, which does not keep the vector sorted.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::appendFrom: after the existing argument checks, calls validateReadData(mDataPos + len) and returns its error if that call fails. The comment says this makes sure the code is not appending over objects; the new test expects PERMISSION_DENIED.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (kernel path): replaces the firstIndex/lastIndex span with an explicit numObjects count of entries that satisfy pos >= offset && pos + sizeof(flat_binder_object) <= offset + len. The copy/acquire loop now goes through all of otherKernelFields->mObjects and skips entries that fail the same test, so only in-range objects are appended and acquire_object'd, and off = pos - offset + startPos is always >= startPos.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (kernel path): sets kernelFields->mObjectsSorted = false after appending objects, because the code cannot easily tell whether the result is still sorted.",
      "libs/binder/Parcel.cpp:Parcel::appendFrom (RPC path): inserts newDataPos into rpcFields->mObjectPositions at std::upper_bound instead of using push_back, which keeps the vector sorted.",
      "libs/binder/Parcel.cpp:Parcel::writeFileDescriptor (RPC path): the same upper_bound insertion of dataPos into rpcFields->mObjectPositions instead of push_back.",
      "libs/binder/Parcel.cpp (hunk at the restart_write: label, object write path): after storing the new object offset and incrementing mObjectsSize, applies kernelFields->mObjectsSorted &= (mDataPos == mDataSize), so a write that is not at the end marks the table unsorted.",
      "libs/binder/tests/binderParcelUnitTest.cpp: adds TEST(Parcel, AppendOverObject), which asserts that appendFrom returns PERMISSION_DENIED when the destination position (8) overlaps a file descriptor object written earlier."
     ],
     "completeness": [
      "Covers both the kernel-binder (mObjects) and RPC (mObjectPositions) branches of appendFrom, plus the sortedness bookkeeping in the object write path and in writeFileDescriptor.",
      "The overlap check relies on validateReadData. This commit does not change it and the material does not show it. The fix depends on it correctly detecting objects in [mDataPos, mDataPos+len), including when mObjectsSorted is false.",
      "The kernel path still selects objects only if they fit entirely inside the range. Objects that only partly overlap the range edges are not appended. This behavior is unchanged and not addressed.",
      "Only the appendFrom overlap case gets a new unit test. The commit message lists binder_parcel_fuzzer as the test for the rest."
     ],
     "behavior_changes": [
      "Parcel::appendFrom now fails with the error from validateReadData (PERMISSION_DENIED in the test) when the destination range overlaps an existing object. Before, it succeeded and overwrote the data. Through the NDK, AParcel_appendFrom returns this as a pruned status (per index search).",
      "appendFrom no longer copies or acquires source objects outside the requested range, even when they sit between in-range entries in an unsorted mObjects array. Only in-range objects are appended.",
      "mObjectsSorted is cleared after any appendFrom that appends objects, and after an object write that is not at the end of the data.",
      "In RPC mode, mObjectPositions is now kept sorted on insertion in appendFrom and writeFileDescriptor."
     ],
     "preconditions": [
      "A process calls Parcel::appendFrom, directly or through AParcel_appendFrom in libs/binder/ndk/parcel.cpp (per index search), with either a destination parcel whose data position is not at the end and which already holds objects, or a source parcel whose object table is not sorted.",
      "The source parcel contains binder objects or file descriptors (kernelFields->mObjects is not empty, or RPC mObjectPositions is not empty)."
     ],
     "per_major": {
      "14": "same fix logic with context differences: commit 7be10040534a in android-security-14.0.0_r22 (acquire_object without the tagFds argument, growData condition uses mDataSize+len, test placed after DebugReadAllFds). 6/8 patched-region lines present at android-security-14.0.0_r29.",
      "15": "same fix logic with context differences: commit 96f0dd71bc07 in android-security-15.0.0_r10 (acquire_object without tagFds, growData condition uses mDataSize+len). 6/8 patched-region lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 46e72b490edd in android-security-16.0.0_r1 (cherry-pick of 28e7af08b92e). 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. Across 14/15/16 the only differences are context: the acquire_object signature (tagFds appears only in the 16 diff) and the growData condition line (mDataSize+len on 14/15, mDataPos+len on 16). validateReadData must exist in the target tree and must handle an unsorted mObjects table for the new check to work."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how validateReadData is implemented or exactly which error it returns in every case. The test expects PERMISSION_DENIED.",
      "The material does not show which production caller or attack surface led to the report (bug 402319736). Only the NDK AParcel_appendFrom caller appears in the index.",
      "The material does not show whether the 1 to 2 patched-region lines missing in the latest security tags come from later refactoring or from an incomplete application.",
      "The material does not show how other Parcel code uses mObjectsSorted or a sorted mObjectPositions, or what performance effect clearing the flag has."
     ],
     "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-32326": {
     "cve": "CVE-2025-32326",
     "summary": "Settings' AppRestrictionsFragment now starts a copy of the app-supplied restrictions Intent. The copy's component and package are set to the ActivityInfo that was actually vetted. Before the fix, Settings started a copy of the original untrusted Intent after the check.",
     "component": "platform/packages/apps/Settings, users / app restrictions (AppRestrictionsFragment)",
     "weakness": "Check/use mismatch on an untrusted Intent: the vetting was applied to the resolution result, but the Intent that was launched was not bound to the vetted component.",
     "root_cause": "In src/com/android/settings/users/AppRestrictionsFragment.java, RestrictionsResultReceiver.onReceive reads an Intent (Intent.EXTRA_RESTRICTIONS_INTENT) from the broadcast result extras. assertSafeToStartCustomActivity(Intent) resolved it with mPackageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) and could throw a SecurityException (\"Application <packageName> is not allowed to start activity ...\"). It returned void. onReceive then called startActivityForResult(new Intent(restrictionsIntent), ...), a copy of the original untrusted Intent. The launched Intent was not limited to the resolved and vetted ActivityInfo, so the component the system actually started for Settings was not guaranteed to be the one that passed the check.",
     "fix_analysis": [
      "AppRestrictionsFragment.java:RestrictionsResultReceiver.assertSafeToStartCustomActivity now returns an Intent. After the existing checks, it builds `new Intent(intent)`, calls setComponent(activityInfo.getComponentName()) and setPackage(activityInfo.packageName), and returns that copy. The diff does not change the vetting code itself.",
      "AppRestrictionsFragment.java:RestrictionsResultReceiver.onReceive stores the return value in `final Intent vettedIntent` and passes vettedIntent to AppRestrictionsFragment.this.startActivityForResult(vettedIntent, requestCode). Before the fix it passed new Intent(restrictionsIntent). The catch of ActivityNotFoundException | SecurityException and the early return with EventLog entry 200688991 are unchanged."
     ],
     "completeness": [
      "Covers the single launch site in RestrictionsResultReceiver.onReceive. The launched Intent is now explicit, with its component and package set to the resolved ActivityInfo.",
      "The copy is made with new Intent(intent), so the other fields of the untrusted Intent (action, data, flags, extras, etc.) are kept. Only component and package are overridden.",
      "The vetting criterion itself is not changed by the diff. The fix makes the launch consistent with the check; it does not strengthen the check.",
      "No other caller of assertSafeToStartCustomActivity appears in the material. It is a private method of the inner receiver class."
     ],
     "behavior_changes": [
      "The custom restrictions activity is now always started with an explicit component Intent, with component and package taken from the resolved ActivityInfo, even if the app supplied an implicit Intent.",
      "No new error paths. The same ActivityNotFoundException / SecurityException handling and EventLog entries remain."
     ],
     "preconditions": [
      "RestrictionsResultReceiver.onReceive runs with invokeIfCustom true while AppRestrictionsFragment is resumed.",
      "The broadcast result extras contain Intent.EXTRA_RESTRICTIONS_INTENT.",
      "The supplied Intent passes assertSafeToStartCustomActivity (resolveActivity with MATCH_DEFAULT_ONLY plus a package-based check whose exact condition is not shown)."
     ],
     "per_major": {
      "14": "same fix: commit 33a1cbf7bd0e in android-security-14.0.0_r22. The hunks are identical, at different line offsets. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit e8a17485771e in android-security-15.0.0_r10, identical diff to the Android 16 commit. 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit e12156e2d805 in android-security-16.0.0_r1 (cherry-pick of d3e34060803c). 8/8 patched 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": [
      "Single self-contained change to one file. The 14/15/16 commits are identical apart from line offsets. If a fork has modified assertSafeToStartCustomActivity, keep the resolved activityInfo in scope so that its getComponentName() and packageName can be used to build the returned Intent.",
      "The bulletin lists Android 13 as affected, but the material includes no Android 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the exact mismatch, reported in bugs 353680402 and 365739560, between the resolveActivity(MATCH_DEFAULT_ONLY) result and the activity actually launched.",
      "The exact package condition inside assertSafeToStartCustomActivity is not shown.",
      "The material does not show whether a corresponding fix landed for Android 13 (listed as affected).",
      "At android-security-16.0.0_r8, the code shows `new Intent(intent)` created directly after the EventLog line in assertSafeToStartCustomActivity. This differs from the fix commit's layout and may indicate a later change to that method. Its content beyond line 668 is not in 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-2025-32331": {
     "cve": "CVE-2025-32331",
     "summary": "KeyguardService.showDismissibleKeyguard() now returns early when ActivityManager reports that lock task mode is active. Folding a foldable with the \"Swipe up to continue\" setting therefore no longer replaces a pinned app with a dismissible keyguard.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardService IKeyguardService binder, app pinning / lock task mode)",
     "weakness": "Missing state check: lock task mode is not considered before a dismissible keyguard is shown, which changes the pinned app's security state",
     "root_cause": "The IKeyguardService.showDismissibleKeyguard() implementation in packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java ran trace() and checkPermission() and then went on to show the dismissible keyguard. On Android 16 it calls mKeyguardInteractor.showDismissibleKeyguard() when FoldGracePeriodProvider is enabled. On Android 15 it calls mKeyguardViewMediator.showDismissibleKeyguard() plus the SceneContainerFlag path. It never checked whether an app was pinned (lock task mode). On foldables with \"Continue using apps on fold\" set to \"Swipe up to continue\", folding the device therefore showed a dismissible keyguard over a pinned app. Per the commit message, this changes the security state the app was in while unfolded. The bulletin rates the issue as High severity EoP.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java: the @Inject constructor gets a new ActivityManager parameter, stored in the new field mActivityManager.",
      "KeyguardService.java:IKeyguardService.showDismissibleKeyguard(): after trace() and checkPermission(), the fix adds `if (mActivityManager.getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE) return;`. On Android 16 this comes before the mFoldGracePeriodProvider.get().isEnabled() check that calls mKeyguardInteractor.showDismissibleKeyguard(). On Android 15 it comes before mKeyguardViewMediator.showDismissibleKeyguard() and the SceneContainerFlag path.",
      "Result: while any lock task mode is active (the check compares against LOCK_TASK_MODE_NONE), the request to show a dismissible keyguard is ignored and the pinned app stays on screen with the screen on."
     ],
     "completeness": [
      "The gate is applied only at the IKeyguardService.showDismissibleKeyguard() binder entry point in KeyguardService. The underlying KeyguardViewMediator.showDismissibleKeyguard() (per index search) and KeyguardInteractor.showDismissibleKeyguard() are not changed, so any other caller of those would not be gated. The material does not show any such callers.",
      "The check queries ActivityManager.getLockTaskModeState() once, at the time of the call.",
      "Every lock task state other than LOCK_TASK_MODE_NONE is rejected, so the fix is not limited to user app pinning.",
      "The commit includes no automated test. It states manual testing only."
     ],
     "behavior_changes": [
      "When an app is pinned (or the device is otherwise in lock task mode) and the fold grace period setting is active, folding the device keeps the screen on with the pinned app shown instead of a dismissible keyguard.",
      "In lock task mode, showDismissibleKeyguard() returns silently after the permission check, with no error or log from the new branch.",
      "KeyguardService's Dagger constructor gains an ActivityManager dependency."
     ],
     "preconditions": [
      "Foldable device with \"Continue using apps on fold\" set to \"Swipe up to continue\" (FoldGracePeriodProvider enabled), per the commit message.",
      "An app is pinned, or lock task mode is otherwise active.",
      "The device is folded, which leads to a call to IKeyguardService.showDismissibleKeyguard(). The caller must pass checkPermission(); the material does not show who the caller is."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. android-security-14.0.0_r29 has only 4/8 lines of the patched region, so the code differs. The material does not show whether the vulnerable path exists there.",
      "15": "Adapted fix, commit 7f5cc94e82fc in android-security-15.0.0_r10. It uses the same ActivityManager injection and lock task check, but the constructor has fewer parameters and the check sits before mKeyguardViewMediator.showDismissibleKeyguard() and the SceneContainerFlag path. 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix, commit 705d322dc8b6 in android-security-16.0.0_r1 (cherry-pick of 14f874decfdd). 8/8 lines are present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). The patched region is fully present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit fix. The method body differs between 15 and 16 (KeyguardViewMediator path vs KeyguardInteractor path), so place the lock task check right after checkPermission() instead of relying on the surrounding context. ActivityManager must be injectable in SystemUI's Dagger graph; the Android 15 and 16 commits both inject it the same way."
     ],
     "confidence": "high",
     "unknowns": [
      "Which system component calls IKeyguardService.showDismissibleKeyguard() on fold, and whether other paths can show a dismissible keyguard while an app is pinned.",
      "The concrete security consequence of the dismissible keyguard appearing over a pinned app, beyond the commit's statement about security state and the bulletin's EoP rating.",
      "Why Android 14 is not listed, and whether the fold grace period feature exists there in a different form (4/8 lines present).",
      "The rest of the showDismissibleKeyguard() body after the lines shown in the diff context."
     ],
     "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-32345": {
     "cve": "CVE-2025-32345",
     "summary": "Settings' ContentProtectionTogglePreferenceController now disables the Content Protection main switch for every non-admin user (via !UserManager.isAdminUser()) instead of only for guest users (UserManager.isGuestUser()).",
     "component": "platform/packages/apps/Settings, security settings (Content Protection toggle, com.android.settings.security)",
     "weakness": "Missing/insufficient authorization check (user-type check too narrow: guest-only instead of non-admin)",
     "root_cause": "In ContentProtectionTogglePreferenceController.updateState(), when the enforced-admin branch did not apply, the switch bar was only disabled when userManager.isGuestUser() was true. Any other non-admin user (e.g. a secondary user) therefore got an enabled Content Protection toggle. The robolectric test reads and writes KEY_CONTENT_PROTECTION_PREFERENCE through Settings.Global, which suggests the toggle backs a global setting. The commit title states that the toggle should not be enabled for non-admin users. The bulletin rates this High / EoP.",
     "fix_analysis": [
      "src/com/android/settings/security/ContentProtectionTogglePreferenceController.java:updateState(): the condition `userManager != null && userManager.isGuestUser() && mSwitchBar != null` becomes `userManager != null && !userManager.isAdminUser() && mSwitchBar != null`; when it is true, mSwitchBar.setEnabled(false) is called. Guest users are still covered because they are not admin users, and other non-admin users are now covered too.",
      "The enforced-admin branch above it is unchanged: if mSwitchBar and mEnforcedAdmin are set and the policy is not CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY, the switch is disabled by admin and the method returns before the user-type check.",
      "tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java: setUp() now calls mShadowUserManager.setIsAdminUser(true) instead of setGuestUser(false). The guest test is renamed updateState_flagEnabled_noEnforcedAdmin_nonAdminUser_switchBarDisabled and uses setIsAdminUser(false). The enabled-case test is renamed updateState_flagEnabled_noEnforcedAdmin_adminUser_switchBarEnabled, explicitly calls setIsAdminUser(true), and asserts that setEnabled(false) is never called."
     ],
     "completeness": [
      "Covers: in updateState(), the UI switch bar is now disabled for all non-admin users (guest and others), not only for guests.",
      "Leaves out (from the code shown): the restriction only sets the enabled state of the SettingsMainSwitchPreference widget, and it only runs when mSwitchBar != null. onCheckedChanged() has no user-type check of its own. setChecked() and the write path to the setting do not appear in the material. The material does not show whether the backing setting is also protected elsewhere (e.g. in the framework).",
      "The check runs inside updateState(); the diff adds nothing that re-evaluates it at other times."
     ],
     "behavior_changes": [
      "Non-admin, non-guest users now see the Content Protection main switch disabled when the enforced-admin branch does not apply. Before the fix the switch was interactive for them.",
      "Admin users and the enforced-admin (setDisabledByAdmin) behavior are unchanged.",
      "The Robolectric test names and shadow setup changed (setIsAdminUser instead of setGuestUser)."
     ],
     "preconditions": [
      "The device has a non-admin user (other than guest) who can open the Settings Content Protection page, and the controller's updateState() runs with mSwitchBar bound.",
      "The enforced-admin branch does not apply (otherwise setDisabledByAdmin is called and the method returns first).",
      "Per the bulletin, Android 15, 16 and 16-next are affected. The patched files are absent on Android 14."
     ],
     "per_major": {
      "14": "not affected: the bulletin does not list it, and the patched files are absent at android-security-14.0.0_r29.",
      "15": "same fix: platform/packages/apps/Settings 6c9a5944dd04 in android-security-15.0.0_r10, identical diff (same blob hashes 69ac6b100be..61987cdd8ad). 6/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: dc41d27f1bd9 in android-security-16.0.0_r1 (cherry-pick of ef801e1a8ec3). At android-security-16.0.0_r8 the code has `!userManager.isAdminUser()` in updateState().",
      "17": "not affected: the bulletin does not list it. 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The source change is a single condition swap (isGuestUser() -> !isAdminUser()) in updateState(). The test change uses ShadowUserManager.setIsAdminUser()."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show whether the underlying content protection setting is also enforced against non-admin users outside this Settings UI (e.g. in the framework).",
      "setChecked() and the exact write path of the toggle are not shown.",
      "The commit carrying the fix on Android 17 / main is not in the material (only the cherry-pick source hash ef801e1a8ec3 is referenced).",
      "The material does not explain why only 6/8 lines of the patched region match at the latest 15/16 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-32346": {
     "cve": "CVE-2025-32346",
     "summary": "In VoicemailSettingsActivity.onActivityResult, the contact URI returned by a picker activity is now validated with ComponentCaller.checkContentUriPermission(FLAG_GRANT_READ_URI_PERMISSION) against the component that delivered the result, instead of only comparing the user ID encoded in the URI with the Phone app's own user ID; denial now logs an EventLog entry and throws SecurityException.",
     "component": "platform/packages/services/Telephony, Phone app voicemail settings (com.android.phone.settings.VoicemailSettingsActivity)",
     "weakness": "Insufficient access control on externally supplied content URI (confused deputy: privileged app queries a URI chosen by another component after only a user-ID comparison)",
     "root_cause": "VoicemailSettingsActivity.onActivityResult receives an Intent result containing a contact URI and queries it with getContentResolver().query(...) using the Phone app's own privileges. The only validation was `UserHandle.getUserId(Process.myUid()) != ContentProvider.getUserIdFromUri(data.getData(), currentUser)`, i.e. it checked whether the user ID embedded in the URI matched the current user. This does not verify that the component returning the result actually holds read access to that URI, so the Phone app could be made to read contact data (the commit's test steps reference a work profile contact) on behalf of a caller that has no such access.",
     "fix_analysis": [
      "src/com/android/phone/settings/VoicemailSettingsActivity.java:onActivityResult: replaces the user-ID comparison with a caller permission check. It obtains `ComponentCaller currentCaller = getCurrentCaller()`; if null, logs an error and returns without querying.",
      "Same function: extracts `Uri contactUri = (data != null) ? data.getData() : null` and returns early with a warning if the Intent or URI is null (new explicit null handling).",
      "Same function: calls `currentCaller.checkContentUriPermission(contactUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)`; on PackageManager.PERMISSION_DENIED it writes EventLog 0x534e4554 with tag 337785563 and the caller UID, then throws a SecurityException naming the caller uid/package and URI. Only when the check passes does the existing `getContentResolver().query(data.getData(), ...)` run.",
      "Imports updated: adds android.app.ComponentCaller, android.content.pm.PackageManager, android.net.Uri, android.util.EventLog; removes android.content.ContentProvider and android.os.Process."
     ],
     "completeness": [
      "Covers the contact-URI query in the patched hunk of VoicemailSettingsActivity.onActivityResult; the subsequent query still uses data.getData(), which is the same value as the validated contactUri.",
      "The check relies on getCurrentCaller() identifying the component that delivered the activity result; when it returns null the code logs an error and returns without querying (fail-closed).",
      "The check is for FLAG_GRANT_READ_URI_PERMISSION only, which matches the read-only query performed.",
      "No other onActivityResult or URI-handling paths in the Telephony project are touched by this commit; the material does not show whether similar patterns exist elsewhere."
     ],
     "behavior_changes": [
      "A result whose caller lacks read permission on the returned URI now causes a SecurityException to be thrown from onActivityResult (previously a user-ID mismatch caused a silent return), plus an EventLog security event (0x534e4554 / 337785563).",
      "A null Intent or null URI now returns early with a warning log.",
      "Legitimate results from a picker that grants read URI permission are unaffected; results that merely carry a same-user URI without a permission grant are now rejected."
     ],
     "preconditions": [
      "VoicemailSettingsActivity must be in a state where it started an activity for result (the contact picker flow) and receives a result Intent containing a content URI, from the diff context.",
      "The component delivering the result must not hold read access to the URI; the commit's test steps mention a work profile contact and a PoC app, indicating a multi-user/work-profile scenario.",
      "Index search results (Telecomm tests, Dialer TelecomUtil.placeCall) are not related to this code path and give no additional reachability information."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin); at android-security-14.0.0_r29 the patched region is present (8/8 lines).",
      "15": "Not affected (not listed by the bulletin); at android-security-15.0.0_r17 the patched region is present (8/8 lines).",
      "16": "Resolved by commit ccfb4f0691ca in android-security-16.0.0_r1 (cherry-pick of 9eb57535ecb1 / 76467ce5606c); patched region present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin); at android-security-17.0.0_r1 the patched region is present (8/8 lines)."
     },
     "backport_notes": [
      "Single-commit fix, self-contained in VoicemailSettingsActivity.java. It depends on android.app.ComponentCaller, Activity.getCurrentCaller() and ComponentCaller.checkContentUriPermission(); a branch without these APIs needs an alternative way to identify the result's sender and check its URI grant.",
      "No fork comparison was provided in the material, so no per-file fork verdict can be reported."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the surrounding onActivityResult code (request code handling, how the picker is launched, any earlier null checks), so the exact entry conditions are inferred from the diff only.",
      "Whether the 8/8 presence reported for 14, 15 and 17 reflects an identical or an adapted commit, and which API levels provide ComponentCaller/getCurrentCaller on those branches, is not shown.",
      "The precise impact of the original flaw is not described in the material beyond the bulletin's EoP/High rating and the test steps mentioning a work profile contact."
     ],
     "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-32347": {
     "cve": "CVE-2025-32347",
     "summary": "BiometricEnrollIntroduction now removes the MultiBiometricEnrollHelper extras EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT from its launch intent in onStart() whenever the calling package is not Settings itself.",
     "component": "platform/packages/apps/Settings, biometrics enrollment (BiometricEnrollIntroduction, base of FaceEnrollIntroduction and FingerprintEnrollIntroduction)",
     "weakness": "Missing validation of caller-supplied intent extras (PendingIntent extras accepted from untrusted callers)",
     "root_cause": "BiometricEnrollIntroduction subclasses could be launched with the MultiBiometricEnrollHelper extras EXTRA_ENROLL_AFTER_FACE and EXTRA_ENROLL_AFTER_FINGERPRINT (PendingIntents, per the tests) and EXTRA_SKIP_PENDING_ENROLL, and the code did not drop these extras when the caller was a package other than Settings. The commit title states the intent: \"Drop PendingIntent extras from external packages during enrollment\". The code that consumes these PendingIntents is not shown in the material.",
     "fix_analysis": [
      "src/com/android/settings/biometrics/BiometricEnrollIntroduction.java:onStart (new override): after super.onStart(), if getPackageName() does not equal getCallingPackage(), iterate over List.of(EXTRA_SKIP_PENDING_ENROLL, EXTRA_ENROLL_AFTER_FACE, EXTRA_ENROLL_AFTER_FINGERPRINT) and call getIntent().removeExtra(key) for each. As the base class is shared, face and fingerprint introduction activities inherit this.",
      "src/com/android/settings/biometrics/BiometricEnrollIntroduction.java: removeEnrollNextBiometricIfSkipEnroll(Intent) and handleBiometricResultSkipOrFinished(int, Intent) are changed from protected to private.",
      "tests/robotests/.../FaceEnrollIntroductionTest.java and .../FingerprintEnrollIntroductionTest.java: new drops_pendingIntents tests launch the activity with mock PendingIntent extras and EXTRA_SKIP_PENDING_ENROLL, call mController.start(), and assert all three extras are absent from getIntent()."
     ],
     "completeness": [
      "Covers exactly three extras on the intent of BiometricEnrollIntroduction subclasses; any other activity that reads these MultiBiometricEnrollHelper extras is not touched, and the material does not show whether such activities exist.",
      "The trust decision is a string comparison of getCallingPackage() with Settings' own package name. getCallingPackage() is only populated when the activity is started for result (general platform knowledge); if null, the comparison fails and the extras are dropped.",
      "Removal happens in onStart(), after onCreate(); the material does not show whether onCreate of the subclasses reads these extras.",
      "The visibility change of the two helper methods to private does not by itself remove any input path."
     ],
     "behavior_changes": [
      "When a face or fingerprint enrollment introduction activity is launched by a package other than Settings (or without a calling package), the three extras are removed at onStart(); chaining via these extras only works when Settings itself is the caller.",
      "removeEnrollNextBiometricIfSkipEnroll and handleBiometricResultSkipOrFinished are no longer accessible from subclasses (source-level change inside Settings)."
     ],
     "preconditions": [
      "A package other than Settings starts a BiometricEnrollIntroduction subclass with an intent carrying the MultiBiometricEnrollHelper PendingIntent or skip extras.",
      "Bulletin rates this as High severity EoP."
     ],
     "per_major": {
      "14": "same source change: commit 214b82f7097b in android-security-14.0.0_r22 contains the same BiometricEnrollIntroduction.java change (onStart extra stripping, methods made private) at different line offsets; only that file is shown in the material. Patched region present (7/8 lines) at android-security-14.0.0_r29.",
      "15": "same fix: commit 25cfacbe5ac2 in android-security-15.0.0_r10 with identical source and test changes; patched region present (7/8 lines) at android-security-15.0.0_r17.",
      "16": "same fix: commit d50de07930ee in android-security-16.0.0_r1 (cherry-pick of 4ccdeee849d5); patched region present (7/8 lines) at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); patched region present (7/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit in packages/apps/Settings touching BiometricEnrollIntroduction.java (plus two robolectric tests); it relies on MultiBiometricEnrollHelper defining the three EXTRA_* constants and adds a new onStart() override to BiometricEnrollIntroduction."
     ],
     "confidence": "medium",
     "unknowns": [
      "Where the EXTRA_ENROLL_AFTER_FACE / EXTRA_ENROLL_AFTER_FINGERPRINT PendingIntents are sent, and thus what action is performed with an externally supplied PendingIntent, is not shown.",
      "Whether the subclasses' onCreate() reads any of these extras before onStart() removes them is not shown.",
      "Whether the enrollment introduction activities are reachable by third-party apps (export status, hardware requirements) is not shown.",
      "Whether the Android 14 commit also included the robolectric test changes is not shown.",
      "Why the per-major check reports 7/8 rather than 8/8 lines of the patched region 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-32349": {
     "cve": "CVE-2025-32349",
     "summary": "WindowManager now ignores app-supplied window animations for untrusted TYPE_TOAST windows (forcing R.style.Animation_Toast) and uses a fixed short alpha fade-out when forcibly hiding non-system overlay windows, so an app-chosen exit animation no longer controls how long such windows stay visible after the system asks for them to be hidden.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm: DisplayPolicy, WindowState, WindowStateAnimator)",
     "weakness": "Untrusted client-controlled parameter (WindowManager.LayoutParams.windowAnimations) honored by the system when forcibly hiding overlay/toast windows; improper enforcement of a hide policy (window remains visible for an app-chosen animation duration)",
     "root_cause": "Legacy apps and deprecated custom-view toasts add TYPE_TOAST windows from their own process, and non-system apps can add system-alert-type overlay windows. Both carry an app-controlled windowAnimations style. DisplayPolicy.adjustWindowParamsLw clamped the toast timeout and made toasts non-touchable, but left attrs.windowAnimations untouched. When WindowState.setForceHideNonSystemOverlayWindowIfNeeded(true) hid such a window via hide(true /* doAnimation */, ...), the window's own specified exit animation could be used. That let the app's chosen animation and its duration decide how long the window stayed on screen after the system asked for it to be hidden. The commit messages call this an \"unexpected animation duration\" / \"customized duration\" for untrusted toasts and an inconsistent duration for forcibly hidden overlays.",
     "fix_analysis": [
      "Commit 3a76075721bb, services/core/java/com/android/server/wm/DisplayPolicy.java:adjustWindowParamsLw, case TYPE_TOAST: after setting FLAG_NOT_TOUCHABLE, if !win.mSession.mCanAddInternalSystemWindow, it overwrites attrs.windowAnimations with R.style.Animation_Toast. Untrusted sessions therefore always get the default toast animation style. SystemUI-rendered text toasts, whose session has the internal system window permission, keep their own style. Per the commit message, the untrusted toast can then be hidden in a short time (config_longAnimTime=500ms).",
      "Commit ab5874fecde6, services/core/java/com/android/server/wm/WindowState.java: adds a package-private getter isForceHiddenNonSystemOverlayWindow() that returns the existing mForceHideNonSystemOverlayWindow field. setForceHideNonSystemOverlayWindowIfNeeded sets that field only for windows whose session lacks mCanAddInternalSystemWindow and whose base type is a system alert window type or TYPE_TOAST. System application overlays with mCanCreateSystemApplicationOverlay are excluded.",
      "Commit ab5874fecde6, services/core/java/com/android/server/wm/WindowStateAnimator.java: in the window animation loading path, adds a new branch `else if (!isEntrance && mWin.isForceHiddenNonSystemOverlayWindow())` that uses `new AlphaAnimation(1f, 0f)` with duration com.android.internal.R.integer.config_shortAnimTime, and imports android.view.animation.AlphaAnimation. The branch comes before the switch(transit) fallback that would otherwise select the style-based exit animation. It comes after an earlier branch that loads an animation from an 'anim' id, whose condition the material does not show."
     ],
     "completeness": [
      "The DisplayPolicy change only covers attrs.type == TYPE_TOAST. Other non-system window types (system alert window types) keep their app-supplied windowAnimations for normal enter/exit. For them, only the forced-hide exit is normalized, by the WindowStateAnimator change.",
      "The WindowStateAnimator change applies only to exit animations (!isEntrance) and only while mForceHideNonSystemOverlayWindow is set. It therefore has the same eligibility as setForceHideNonSystemOverlayWindowIfNeeded: non-internal session, system alert window type or TYPE_TOAST, and not a permitted system application overlay.",
      "The new branch is an `else if` after an earlier branch that loads an animation from an `anim` id. The material does not show that branch's condition, so it cannot tell whether that branch can take precedence over the new one.",
      "WindowState.setHiddenWhileSuspended, a sibling path with the same type gating, is not modified. Its body is cut off in the material, so it is not shown whether it animates with the window's exit animation."
     ],
     "behavior_changes": [
      "TYPE_TOAST windows added by sessions without mCanAddInternalSystemWindow always use R.style.Animation_Toast. Any windowAnimations value they set is discarded for both enter and exit.",
      "Windows marked force-hidden as non-system overlays fade out with a plain alpha 1->0 animation of config_shortAnimTime instead of their own exit animation (commit message: avoids jump cuts and inconsistent durations).",
      "No new error codes or exceptions appear in the diffs; the changes affect only visuals and timing."
     ],
     "preconditions": [
      "A window is added by a session where mSession.mCanAddInternalSystemWindow is false (a regular, non-system app).",
      "For the DisplayPolicy change: the window type is TYPE_TOAST and the app adds it directly (a legacy app or a deprecated custom-view toast shown in the app process, per the commit message), so it reaches DisplayPolicy.adjustWindowParamsLw.",
      "For the WindowStateAnimator change: the window's base type is a system alert window type or TYPE_TOAST (and not a permitted system application overlay), and the system has called WindowState.setForceHideNonSystemOverlayWindowIfNeeded(true), which calls hide(true, true) and leads to exit animation selection."
     ],
     "per_major": {
      "14": "Same fix for the WindowState/WindowStateAnimator part: commit b8401310e97f in android-security-14.0.0_r22 has the same content at different line offsets; 8/8 lines of the patched region are present at android-security-14.0.0_r29. The material does not show the DisplayPolicy TYPE_TOAST change on 14.",
      "15": "Same fix for the DisplayPolicy part: commit 394acf2aa1da in android-security-15.0.0_r10 adds the untrusted-toast animation override; 4/4 lines are present at android-security-15.0.0_r17. The material does not show the WindowStateAnimator change on 15.",
      "16": "Both commits (3a76075721bb and ab5874fecde6) are in android-security-16.0.0_r1, linked by bug 408215749; 4/4 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin); the patched region is present (4/4 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The two commits share no symbols in the diffs, so they can be applied independently; apply both. The WindowStateAnimator change relies on the pre-existing WindowState.mForceHideNonSystemOverlayWindow field and config_shortAnimTime. The DisplayPolicy change relies on R.style.Animation_Toast and Session.mCanAddInternalSystemWindow.",
      "The material shows only one of the two commits for Android 14 (animator) and one for Android 15 (DisplayPolicy). Check that the other commit is present on the target branch before treating the backport as complete."
     ],
     "confidence": "high",
     "unknowns": [
      "Which system event triggers setForceHideNonSystemOverlayWindowIfNeeded in the reported scenario. The animator commit's test step mentions launching Settings, but the caller is not in the material.",
      "Whether both commits were applied on Android 14 and Android 15; the material shows only one commit per branch.",
      "The condition of the branch just before the new `else if` in WindowStateAnimator, and therefore whether any animation source can still bypass the forced alpha fade.",
      "The exact effective hide duration before the fix (bounded only by the app-chosen animation)."
     ],
     "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-32350": {
     "cve": "CVE-2025-32350",
     "summary": "Removes android:exported=\"true\" from SystemUI's ControlsActivity manifest entry and adds SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS to the window of the dialog built in ControlsSettingsDialogManager.kt, so other packages can no longer launch the activity and non-system overlay windows can no longer cover the lockscreen-controls settings dialog.",
     "component": "platform/frameworks/base, SystemUI (packages/SystemUI), device controls / ControlsSettingsDialogManager",
     "weakness": "Improperly exported activity (no permission or intent-filter shown) combined with a system dialog that toggles Secure settings without hiding non-system overlay windows (overlay/tapjacking exposure)",
     "root_cause": "In packages/SystemUI/AndroidManifest.xml, the ControlsActivity entry (launchMode singleInstance, visibleToInstantApps true, self-closing tag with no intent-filter in the hunk) had android:exported=\"true\", so any package could launch it. Separately, ControlsSettingsDialogManagerImpl in ControlsSettingsDialogManager.kt builds an AlertDialog that prompts the user to enable Settings.Secure.LOCKSCREEN_SHOW_CONTROLS / LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS (per the class doc and the test's SETTING_SHOW / SETTING_ACTION). Its window did not set SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, so non-system overlay windows could be drawn on top of it while it was shown.",
     "fix_analysis": [
      "packages/SystemUI/AndroidManifest.xml (ControlsActivity <activity> element): the android:exported=\"true\" attribute is deleted. The element has no intent-filter in the shown hunk, so the activity defaults to not exported and other apps can no longer start it.",
      "packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt (ControlsSettingsDialogManagerImpl, dialog construction before SystemUIDialog.registerDismissListener): adds an import of android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS and calls d.window?.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) on the built dialog, before setDialogSize/setShowForAllUsers.",
      "packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt: the new test dialogHidesNonSystemOverlayWindows sets attempts to 0 and both settings to false, calls underTest.maybeShowDialog(context, completedRunnable), then asserts that dialog.window.attributes.privateFlags contains SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS."
     ],
     "completeness": [
      "Covers the single ControlsActivity manifest entry and the single dialog built in ControlsSettingsDialogManagerImpl. Every dialog created through that path gets the flag, because it is applied right after construction.",
      "The flag is added with the safe call d.window?.addSystemFlags, so if the dialog had no window the flag would silently not be set. The test checks only the normal case.",
      "The commit touches no other SystemUI activities or dialogs. The material does not show whether other callers of ControlsActivity needed an exported entry; the commit removes the attribute unconditionally."
     ],
     "behavior_changes": [
      "ControlsActivity is no longer exported: explicit intents to it from other packages (including instant apps, even though visibleToInstantApps stays true) are refused.",
      "While the controls settings dialog is shown, non-system app windows that would overlay it are hidden (SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS is set in the window's privateFlags).",
      "No functional change to the dialog's buttons, the attempt counter or the settings writes."
     ],
     "preconditions": [
      "Before the fix, any package could start ControlsActivity with an explicit intent, because it was exported and the shown hunk has no permission attribute.",
      "The dialog is only shown when at least one of LOCKSCREEN_SHOW_CONTROLS / LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS is false and the user has not seen the dialog too many times (as defined by MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG), per the ControlsSettingsDialogManager doc and the test setup.",
      "For the overlay part: a non-system app able to draw overlay windows on top of SystemUI's dialog."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/base b08dcc947008 in android-security-14.0.0_r22 (matched by subject). The manifest and Kotlin hunks are identical, except that the file imports com.android.systemui.R instead of com.android.systemui.res.R and the test also adds an androidx AndroidJUnit4 import. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix: platform/frameworks/base 47424521c49b in android-security-15.0.0_r10 (matched by Change-Id). The manifest hunk is at a different line offset (903); the Kotlin and test hunks are identical. 8/8 present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/frameworks/base d6b86833ee06 in android-security-16.0.0_r1 (matched by bug id), a cherry-pick of 0042369de2a6. 8/8 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: one manifest attribute, one addSystemFlags call and one test. The only per-branch drift is the SystemUI R import path (com.android.systemui.R on 14 vs com.android.systemui.res.R on 15/16) and the manifest line offset. Window.addSystemFlags and SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS are hidden platform APIs that SystemUI can use as a platform-signed app built against the framework (general platform knowledge).",
      "Before dropping android:exported, check that no downstream customization starts ControlsActivity from outside the SystemUI package, since the commit removes the attribute unconditionally."
     ],
     "confidence": "high",
     "unknowns": [
      "The manifest hunk does not show the activity name, its fully qualified component name, or whether a permission attribute existed. The identification as ControlsActivity comes from the commit title.",
      "The material does not show what ControlsActivity does when launched externally, or how the two parts of the fix relate to the bulletin's EoP 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-48522": {
     "cve": "CVE-2025-48522",
     "summary": "Adds a 1024-character upper bound on the display name accepted by AssociationRequest.Builder.setDisplayName in the Companion Device Manager API, throwing IllegalArgumentException for longer values.",
     "component": "platform/frameworks/base, Companion Device Manager (core/java/android/companion/AssociationRequest.java)",
     "weakness": "Missing input length validation (unbounded caller-supplied string)",
     "root_cause": "AssociationRequest.Builder.setDisplayName accepted any non-null CharSequence, no matter how long. Per the constructor javadoc in the context, the display name is \"shown in the CDM confirmation UI\" and must be non-null for self-managed associations, so an application could supply an unbounded string for use in the association confirmation. The shown code had no length check. The material does not show the concrete downstream effect that the bulletin rates as High EoP.",
     "fix_analysis": [
      "core/java/android/companion/AssociationRequest.java: adds a private constant DISPLAY_NAME_LENGTH_LIMIT = 1024 to the AssociationRequest class.",
      "core/java/android/companion/AssociationRequest.java:Builder.setDisplayName: after checkNotUsed() and the existing requireNonNull assignment to mDisplayName, throws IllegalArgumentException(\"Length of the display name must be at most 1024 characters\") when displayName.length() > DISPLAY_NAME_LENGTH_LIMIT.",
      "Single-commit fix that touches one file. It does not change the service side, the Parcel constructor or the build() method."
     ],
     "completeness": [
      "Covers only the public Builder.setDisplayName entry point. This is a client-side check in the framework API class.",
      "The check runs after the assignment mDisplayName = requireNonNull(displayName). If a caller catches the IllegalArgumentException, the Builder instance still holds the oversized value. The material does not show whether build() can then succeed.",
      "This commit does not touch the Parcelable path of AssociationRequest (constructor from Parcel / CREATOR) or the receiving side in the system service. The material does not show whether either one validates the length.",
      "The limit is counted in UTF-16 code units (CharSequence.length()), not in rendered width or bytes."
     ],
     "behavior_changes": [
      "AssociationRequest.Builder.setDisplayName now throws IllegalArgumentException for display names longer than 1024 characters. Before the fix, any length was accepted.",
      "Apps that pass a longer display name now get the exception from the setDisplayName call, before they can build the request or enter the CDM association flow."
     ],
     "preconditions": [
      "The caller builds an AssociationRequest with AssociationRequest.Builder and calls setDisplayName with a very long CharSequence.",
      "Per the javadoc in the context, the display name is shown in the CDM confirmation UI and must be non-null for self-managed associations."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base aa93bf9db684 in android-security-14.0.0_r22. Same hunks (the constant and the setDisplayName check) at different line offsets. 4/4 patched lines are present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base bdad29b85a6c in android-security-15.0.0_r10. Its diff is identical to the 14 commit (same blob ids 2e969f83f836..10d47559cf73). 4/4 patched lines are present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base eb3cf182f929 in android-security-16.0.0_r1 (cherry-pick of internal commit 4234c17b14d6). 4/4 patched lines are 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 already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change is self-contained: one constant plus a 4-line check inside Builder.setDisplayName, with no dependency on other commits shown. The 14, 15 and 16 commits carry the same hunk content at different line offsets. In the 16 file, the mDeviceIcon field follows the constant.",
      "The bulletin lists Android 13 as affected, but the material includes no 13 commit. The hunk targets Builder.setDisplayName where checkNotUsed() is followed by mDisplayName = requireNonNull(displayName)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the concrete effect of an oversized display name in the system/CDM UI that led to the High EoP rating.",
      "Whether CompanionDeviceManagerService or the AssociationRequest Parcel constructor validates the display name length on its own.",
      "Whether build() can succeed after the exception is caught, since mDisplayName is assigned before the length check.",
      "The material does not include the Android 13 fix commit."
     ],
     "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-48523": {
     "cve": "CVE-2025-48523",
     "summary": "Removes the branch in Contacts' SelectAccountActivity.onCreate that, when exactly one writable account existed, immediately returned that account as RESULT_OK and finished without showing the account selection dialog, so the vCard import target account must now be selected by the user in the dialog.",
     "component": "platform/packages/apps/Contacts, vCard import (src/com/android/contacts/vcard/SelectAccountActivity.java)",
     "weakness": "Missing user confirmation step: an activity auto-completed the import target account selection without user interaction when only one candidate existed",
     "root_cause": "SelectAccountActivity.onCreate obtained the writable account list via AccountTypeManager.blockForWritableAccounts() and, in the `accountList.size() == 1` case, built an Intent with ACCOUNT_NAME, ACCOUNT_TYPE and DATA_SET of the sole account, called setResult(RESULT_OK, intent) and finish() with no dialog. Per the commit message this allowed auto-importing when a single account was present; the security team recommended always showing the selection dialog (b/388032224#comment4).",
     "fix_analysis": [
      "src/com/android/contacts/vcard/SelectAccountActivity.java:onCreate: deletes the entire `else if (accountList.size() == 1)` block (9 lines) that auto-selected the single account, set RESULT_OK and finished.",
      "As a result, after the size()==0 early-finish check, control always falls through to constructing AccountSelectionUtil.AccountSelectedListener and calling showDialog(R.string.import_from_vcf_file); the result Intent is only produced from the dialog's onClick (lines 77-86 in the r8 context), i.e. after an explicit user selection.",
      "A CancelListener class that calls finish() on click/cancel exists in the activity; whether onCreateDialog wires it to this dialog is not visible in the truncated context."
     ],
     "completeness": [
      "Covers the single-account auto-selection path only; the zero-account path (finish with no result) and the multi-account dialog path are unchanged.",
      "The fix is entirely within SelectAccountActivity; no change is made to the caller side of the vCard import flow or to how the activity is launched, so any other route that supplies an account without this activity is not addressed by this diff (none is shown in the material).",
      "The explanatory comment at lines 57-60 of the r8 code still says 'just one account -> use the account without asking the user', which is now stale but has no functional effect.",
      "The r8 context shows getWindow().setHideOverlayWindows(true) in onCreate; it is not part of this diff and its origin is not shown."
     ],
     "behavior_changes": [
      "With exactly one writable account, SelectAccountActivity now shows the 'import from vcf file' account selection dialog instead of returning immediately; the user must select the account to get a RESULT_OK.",
      "In the single-account case the activity no longer returns RESULT_OK unless the user selects the account in the dialog.",
      "Log line 'The number of available accounts: N' is now emitted for N==1 as well."
     ],
     "preconditions": [
      "SelectAccountActivity in the Contacts app is started (part of the vCard import flow, per the R.string.import_from_vcf_file resource used for the dialog).",
      "AccountTypeManager.blockForWritableAccounts() returns exactly one writable account on the device.",
      "How the activity is launched (exported status, launching component, intent) is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: identical diff (commit 6104890ea916, same blob hashes 8ead5fab3..797fc8670) landed in android-security-14.0.0_r22; at android-security-14.0.0_r29, 7 of 8 lines of the patched region are present.",
      "15": "same fix: identical diff (commit 7c8dbcffbc93) landed in android-security-15.0.0_r10; at android-security-15.0.0_r17, 7 of 8 lines of the patched region are present.",
      "16": "same fix: commit c314edb9bde8 in android-security-16.0.0_r1; at android-security-16.0.0_r8, 7 of 8 lines of the patched region are present (the r8 context shows no single-account branch).",
      "17": "not affected per the bulletin; at android-security-17.0.0_r1, 7 of 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-commit, deletion-only change (9 lines) with identical pre/post blob hashes on 14, 15 and 16, so it should apply cleanly to any branch whose SelectAccountActivity.onCreate still contains the `else if (accountList.size() == 1)` block; after applying, verify that showDialog(resId) is reached whenever accountList.size() >= 1."
     ],
     "confidence": "high",
     "unknowns": [
      "How SelectAccountActivity is reached (launching component, whether it is exported, what intent/data triggers the import) is not in the material.",
      "The exact privilege gained (bulletin says EoP, High) and the attack scenario in b/388032224 are not shown.",
      "How the caller handles a cancelled or non-OK result, and whether CancelListener is attached to this dialog, are not shown.",
      "Whether getWindow().setHideOverlayWindows(true) seen in the r8 context is related to this CVE or a separate change.",
      "Which line of the 8-line patched region is missing in each latest tag is not stated."
     ],
     "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-48524": {
     "cve": "CVE-2025-48524",
     "summary": "WifiPermissionsUtil.isSystem() now returns false early when the calling uid is an SDK sandbox uid (on T and later), so an SDK sandbox caller is no longer classified as a system app from the package name's ApplicationInfo.",
     "component": "platform/packages/modules/Wifi, Wifi service permission checks (WifiPermissionsUtil)",
     "weakness": "Improper privilege/identity check (missing exclusion of SDK sandbox uids in a system-app check)",
     "root_cause": "WifiPermissionsUtil.isSystem(packageName, uid) decided system-app status only by looking up ApplicationInfo for the supplied packageName, under the user derived from uid, and testing APP_INFO_FLAGS_SYSTEM_APP. It did not check whether the uid belonged to an SDK sandbox, so a sandbox caller could be treated as a privileged (system) app, per the commit title \"Do not treat SdkSandBox as privileges App\". The bulletin classifies the impact as DoS (High).",
     "fix_analysis": [
      "service/java/com/android/server/wifi/util/WifiPermissionsUtil.java:isSystem adds `if (SdkLevel.isAtLeastT() && Process.isSdkSandboxUid(uid)) return false;` inside the try block, after Binder.clearCallingIdentity() and before the PackageManager.getApplicationInfoAsUser lookup. SDK sandbox uids therefore never reach the APP_INFO_FLAGS_SYSTEM_APP test.",
      "The check is gated on SdkLevel.isAtLeastT(), so behavior is unchanged on pre-T builds. On the new early-return path the finally block still restores the calling identity."
     ],
     "completeness": [
      "Covers only WifiPermissionsUtil.isSystem. Every caller of isSystem gets the stricter result.",
      "Other WifiPermissionsUtil checks visible in the context (isDeviceOwner, isProfileOwnerOfOrganizationOwnedDevice, isOrganizationOwnedDeviceAdmin) are not touched by this commit. The material does not show whether they have a similar SDK sandbox concern.",
      "Because of the SdkLevel.isAtLeastT() gate, the new check does nothing on builds below T."
     ],
     "behavior_changes": [
      "On T and later, isSystem returns false for SDK sandbox uids, so any Wifi service logic that depends on isSystem treats those callers as non-system.",
      "isSystem itself adds no new error codes or exceptions."
     ],
     "preconditions": [
      "Device runs T or later (SdkLevel.isAtLeastT()).",
      "A caller running under an SDK sandbox uid reaches a Wifi service path that calls WifiPermissionsUtil.isSystem with a package name whose ApplicationInfo has the system-app flag.",
      "The material does not show which Wifi service APIs call isSystem."
     ],
     "per_major": {
      "14": "same fix: platform/packages/modules/Wifi 980d442c5926 in android-security-14.0.0_r22, identical 3-line hunk (at line 1138); 5/5 lines present at android-security-14.0.0_r29",
      "15": "same fix: platform/packages/modules/Wifi 298745e0cb23 in android-security-15.0.0_r10, identical hunk at line 1126; 5/5 lines present at android-security-15.0.0_r17",
      "16": "same fix: platform/packages/modules/Wifi c47809aec031 in android-security-16.0.0_r1 (cherry-picked from b8feb0cc861f / 68262dc34af9); 5/5 lines present at android-security-16.0.0_r8",
      "17": "not affected (not listed by the bulletin); the patched lines are present 5/5 at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single self-contained 3-line hunk. It depends on Process.isSdkSandboxUid and SdkLevel.isAtLeastT() being available in the tree.",
      "Android 13 is listed as affected, but the material includes no Android 13 commit. The same hunk should apply to isSystem there if the method has the same shape."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which Wifi service API(s) reach isSystem, and how the DoS shows up, is not shown.",
      "Exactly how an SDK sandbox caller came to be classified as a system app (for example, which package name it supplied) is not shown.",
      "Whether an Android 13 Wifi module fix exists (the bulletin lists 13 as affected, but no commit is provided).",
      "Whether other WifiPermissionsUtil privilege checks have a similar SDK sandbox issue."
     ],
     "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-48526": {
     "cve": "CVE-2025-48526",
     "summary": "ChooserActivity now hands cross-profile tabs a sanitized copy of the payload intents (package and component cleared on each intent and its selector) instead of the caller's original, possibly explicit, intents.",
     "component": "platform/packages/modules/IntentResolver, ChooserActivity (share sheet) cross-profile target resolution",
     "weakness": "Missing sanitization of caller-controlled intents before cross-profile resolution (explicit package/component targeting allowed to bypass cross-profile sharing policy)",
     "root_cause": "In ChooserActivity.createMultiProfilePagerAdapter the same request.getPayloadIntents() list (targetIntent plus additionalTargets, built in ChooserRequest.kt) was passed to createChooserGridAdapter for every profile tab, including profiles other than the one the chooser was launched as. A caller could therefore supply an explicit intent (with setPackage/setComponent, or a selector carrying them) and have it resolved in another profile. The KDoc added in ChooserRequest.kt states that such explicit intents \"could otherwise be used to bypass the device's cross-profile sharing policy settings\". Only the initialIntents were already restricted to the launched-as profile; payload intents were not.",
     "fix_analysis": [
      "java/src/com/android/intentresolver/util/IntentUtils.kt (new): sanitizePayloadIntents(List<Intent>) copies each Intent, calls setPackage(null) and setComponent(null) on the copy, and if a selector is present replaces it with a copy that also has package and component cleared. Originals are not mutated.",
      "java/src/com/android/intentresolver/data/model/ChooserRequest.kt: adds `val crossProfilePayloadIntents by lazy { sanitizePayloadIntents(payloadIntents) }`, a lazily computed sanitized view of payloadIntents.",
      "java/src/com/android/intentresolver/ChooserActivity.java:createMultiProfilePagerAdapter: computes `isCrossProfile = !profile.equals(launchedAs)` per ProfileRecord and passes request.getCrossProfilePayloadIntents() to createChooserGridAdapter for cross-profile tabs and request.getPayloadIntents() for the launched-as profile; initial intents remain only for the launched-as profile. The `List<Intent> initialIntents` parameter is removed and initialIntentArray is now derived from request.getInitialIntents(); both call sites in ChooserActivity are updated accordingly.",
      "java/src/com/android/intentresolver/shared/model/Profile.kt: formatting-only changes (unused import removed, trailing commas); no behavior change.",
      "tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt (new): verifies package/component are null on sanitized intents and their selectors."
     ],
     "completeness": [
      "Sanitization is applied only on the ChooserActivity path, where the adapter for each non-launched-as profile is built; it is keyed on profile.equals(launchedAs), so every cross-profile tab gets sanitized intents.",
      "Only Intent.getPackage and Intent.getComponent are cleared, on the intent and one level of selector; other intent fields (action, data, type, categories, extras, flags) are passed through unchanged.",
      "The launched-as profile continues to receive the original, unsanitized payload intents and the initial intents.",
      "The material does not show whether the ResolverActivity (non-chooser) path or the intent actually launched after the user selects a cross-profile target goes through the same sanitized intents; that is outside the diff.",
      "The lazy property means the sanitized list is computed once per ChooserRequest instance; per index search, ChooserActivity.onChooserRequestChanged replaces mRequest and recreates the pager adapter only when shouldUpdateAdapters returns true, in which case the new request's sanitized list is used."
     ],
     "behavior_changes": [
      "Cross-profile tabs in the share sheet resolve targets against implicit copies of the payload intents; explicit package/component targeting supplied by the caller no longer narrows or forces results in other profiles.",
      "createMultiProfilePagerAdapter signature changes (initialIntents parameter removed); callers inside ChooserActivity updated.",
      "New public Kotlin top-level function sanitizePayloadIntents in IntentUtils (JvmName IntentUtils) and new ChooserRequest.crossProfilePayloadIntents property.",
      "No change for the launched-as profile tab."
     ],
     "preconditions": [
      "Device has at least one additional profile shown as a tab in the chooser (profile different from the launched-as profile).",
      "A caller starts the chooser (ACTION_CHOOSER with EXTRA_INTENT and optionally EXTRA_ALTERNATE_INTENTS, read by readChooserRequest in ui/viewmodel/ChooserRequestReader.kt per index search) where the target or alternate intent, or its selector, carries an explicit package or component."
     ],
     "per_major": {
      "14": "adapted fix (commit 4ffdecbbef8a in android-security-14.0.0_r22). ChooserActivity extends ResolverActivity there: sanitizePayloadIntents(mIntents) is computed in the adapter-building method and passed to the personal adapter when selectedProfile != PROFILE_PERSONAL and to the work adapter when selectedProfile != PROFILE_WORK. The IntentUtils.kt variant accepts List<Intent?> and passes nulls through. The diff shown has no Profile.kt change and no unit test file. At android-security-14.0.0_r29 the patched region is fully present (8/8 lines).",
      "15": "same fix (commit 923a5673ac9d in android-security-15.0.0_r10): identical IntentUtils.kt, ChooserRequest.kt, Profile.kt and test changes; the ChooserActivity hunk differs only in that the loop iterates profileHelper.getProfiles() with an availability check instead of ProfileRecords. At android-security-15.0.0_r17 6/8 lines of the patched region are present (surrounding code evolved).",
      "16": "fix commit 247af9e3e487 in android-security-16.0.0_r1 (this diff). At android-security-16.0.0_r8 6/8 lines are present; the context shows IntentUtils.kt has since grown (prepareCrossProfileIntents added) and ChooserRequest.kt still imports sanitizePayloadIntents, so the fix is present with later refactoring around it.",
      "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": [
      "For 15/16-style trees the fix depends on the ChooserRequest data model and ProfileHelper.getLaunchedAsProfile(); for 14-style trees (ChooserActivity extends ResolverActivity, mIntents/selectedProfile) use the android-security-14.0.0_r22 shape and the nullable-list IntentUtils.kt variant.",
      "IntentUtils.kt is created by this commit; on branches where java/src/com/android/intentresolver/util/IntentUtils.kt already exists (e.g. with prepareCrossProfileIntents), add sanitizePayloadIntents to the existing file rather than taking the new-file hunk."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the intent launched when a cross-profile target is selected also derives from the sanitized list (launch path not in the material).",
      "Whether the ResolverActivity (non-chooser) cross-profile path needed or received equivalent handling.",
      "The exact policy bypass scenario (which cross-profile sharing setting) that motivated the bug; the material only gives the KDoc statement.",
      "Why Android 13 is listed as affected by the bulletin while no Android 13 fix commit is in the material.",
      "The names of the methods enclosing the two updated createMultiProfilePagerAdapter call sites."
     ],
     "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-48527": {
     "cve": "CVE-2025-48527",
     "summary": "Settings' Notification history screen now shows the generic \"notification_hidden_text\" string, with no title, in place of the title and text of historical, snoozed and dismissed notifications. This applies to any profile of the current user that has a secure lock, is currently locked, and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS disabled.",
     "component": "platform/packages/apps/Settings, notification history UI (com.android.settings.notification.history)",
     "weakness": "Missing privacy check before displaying sensitive data (information disclosure). Notification content of a locked profile was shown without checking the profile's lock-screen \"hide sensitive content\" setting or its lock state.",
     "root_cause": "NotificationHistoryActivity fills a per-package history list (NotificationHistoryAdapter over HistoricalNotification) and snoozed and dismissed lists (NotificationSbnAdapter over StatusBarNotification). Both adapters' onBindViewHolder always called holder.setTitle(...) and holder.setSummary(...) with the notification's real title and text. This included entries from a profile of the current user (per the commit test note, a work profile with a different PIN) that was locked and had chosen to hide sensitive content when locked, so that content was shown on the history screen.",
     "fix_analysis": [
      "NotificationHistoryActivity.java:onCreate: after getting mUm, the code loops over mUm.getProfiles(getUserId()). A profile is added to the new field mContentRestrictedUsers (ArrayList<Integer>) when Settings.Secure.getIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, default 0, user.id) == 0 and LockPatternUtils.isSecure(user.id) && KeyguardManager.isDeviceLocked(user.id).",
      "NotificationHistoryActivity.java: passes NotificationHistoryActivity.this and mContentRestrictedUsers to the NotificationHistoryAdapter constructor. It also passes mContentRestrictedUsers to both NotificationSbnAdapter constructions (snoozed, isSnoozed=true; dismissed, isSnoozed=false).",
      "NotificationHistoryAdapter.java: the constructor gains Context and ArrayList<Integer> contentRestrictedUsers parameters, stored in mContext and mContentRestrictedUsers. In onBindViewHolder, if mContentRestrictedUsers.contains(hn.getUserId()), only holder.setSummary(mContext.getString(com.android.internal.R.string.notification_hidden_text)) is called. Otherwise the original setTitle(hn.getTitle())/setSummary(hn.getText()) path runs. Posted time and the click handler are unchanged.",
      "NotificationSbnAdapter.java: the constructor gains ArrayList<Integer> contentRestrictedUsers, stored in mContentRestrictedUsers. In onBindViewHolder, if mContentRestrictedUsers.contains(sbn.getNormalizedUserId()), only setSummary(notification_hidden_text) is called. Otherwise the original setTitle(getTitleString(...))/setSummary(getTextString(...)) path runs. Icon, icon background, package label, posted time and user badge handling are unchanged."
     ],
     "completeness": [
      "Covers the three adapter instantiations touched by the diff: the per-package history list (NotificationHistoryAdapter) and the snoozed and dismissed lists (NotificationSbnAdapter). The material does not show the full activity, so it cannot confirm there are no other rendering paths.",
      "Redaction depends only on the owning user id (hn.getUserId() / sbn.getNormalizedUserId()). Per-notification visibility is not considered.",
      "mContentRestrictedUsers is computed only in onCreate, and the diff adds no recomputation elsewhere. A lock-state or setting change while the activity is alive would therefore not be reflected by this code. The Android 15 variant adds clear() calls in onCreate and onDestroy but still computes the list only in onCreate.",
      "In the restricted branch neither adapter calls holder.setTitle(...), so the title view is not explicitly reset. Whether a recycled view holder could keep a prior item's title depends on the view holder implementation, which is not in the material.",
      "For restricted entries, the package label, icon, posted time and user badge are still shown, and the NotificationHistoryAdapter onClick handler is unchanged. Only title and text are hidden.",
      "The condition checks only LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS plus the secure and locked state. Other lock-screen notification settings and policies are not consulted."
     ],
     "behavior_changes": [
      "For profiles that are secure, currently locked and have LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS == 0 (the code's default when the setting is unset), history rows show the framework string notification_hidden_text as the summary and no title.",
      "Constructor signatures change: NotificationHistoryAdapter adds Context first and ArrayList<Integer> last; NotificationSbnAdapter adds ArrayList<Integer> last.",
      "onCreate does extra work: it enumerates profiles, reads one Settings.Secure value per profile, and calls LockPatternUtils.isSecure and KeyguardManager.isDeviceLocked for each candidate profile."
     ],
     "preconditions": [
      "NotificationHistoryActivity in Settings is opened by a user that has profiles.",
      "A profile of the current user (per the commit message, for example a work profile with a different PIN) has a secure lock, is currently locked, and has LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS disabled or unset.",
      "Notifications from that profile appear in the history, snoozed or dismissed lists.",
      "The bulletin rates this High severity, type ID (information disclosure). Affected versions: 13, 14, 15, 16, 16-next."
     ],
     "per_major": {
      "14": "adapted fix: commit cee46cf86cd9 in android-security-14.0.0_r22. Same logic in the same three files, with context differences (View.GONE, setDividerVisible, OnMainSwitchChangeListener import). 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "adapted fix: commit d8b1de7a18fc in android-security-15.0.0_r10. Same logic plus explanatory comments and mContentRestrictedUsers.clear() calls in onCreate (twice) and onDestroy. 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit 504a34c4d4a3 in android-security-16.0.0_r1 (cherry-picked from 9df37c3f8be2). 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": [
      "The change touches only three Settings files. It uses com.android.internal.widget.LockPatternUtils, KeyguardManager.isDeviceLocked(int), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS and com.android.internal.R.string.notification_hidden_text, all of which Settings can access (general platform knowledge). Any fork code or tests that construct NotificationHistoryAdapter or NotificationSbnAdapter directly must be updated to the new constructor signatures.",
      "The 14 and 16 variants are logically identical. The 15 variant also adds clear() calls on mContentRestrictedUsers in onCreate and onDestroy, plus comments."
     ],
     "confidence": "high",
     "unknowns": [
      "Contents of bug 378088320, and whether other Settings or SystemUI surfaces were handled in separate commits.",
      "Implementation of NotificationHistoryViewHolder and NotificationSbnViewHolder is not in the material, so it is not shown whether title views are reset on recycle when setTitle is skipped.",
      "Whether Android 13 (listed as affected) received a corresponding commit. No Android 13 fix is in the material.",
      "Whether the snoozed and dismissed lists are shown when notification history is disabled, and how the activity retrieves them.",
      "The rest of NotificationHistoryActivity's lifecycle (for example whether onResume rebuilds the adapters)."
     ],
     "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-48528": {
     "cve": "CVE-2025-48528",
     "summary": "Changes the BiometricPrompt window (AuthContainerView) back to window type TYPE_KEYGUARD_DIALOG from TYPE_APPLICATION_OVERLAY. On Android 16 it also binds the UDFPS touch overlay for REASON_AUTH_BP to a new PromptUdfpsTouchOverlayViewModel that always handles touches.",
     "component": "platform/frameworks/base, SystemUI biometrics (BiometricPrompt / UDFPS overlay)",
     "weakness": "Incorrect window type for a system-security UI window (BiometricPrompt created as TYPE_APPLICATION_OVERLAY rather than TYPE_KEYGUARD_DIALOG)",
     "root_cause": "AuthContainerView.getLayoutParams() created the BiometricPrompt window with WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY. The Android 15 fix reverts a change titled \"Fix biometric prompt appearing above shade\". That shows the type had been switched from TYPE_KEYGUARD_DIALOG to TYPE_APPLICATION_OVERLAY. On 15, the same change had also moved mPanelInteractionDetector.enable() under the !constraintBp() condition. On Android 16, UdfpsControllerOverlay.show() had no dedicated REASON_AUTH_BP branch, so BiometricPrompt requests were handled by the existing branches. The commit adds a BP-specific binding. The bulletin rates this as a High EoP; the material does not show the concrete security consequence.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java:getLayoutParams(): window type changed from WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY to WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG. The flags (FLAG_SECURE, FLAG_SHOW_WHEN_LOCKED, FLAG_DIM_BEHIND, SYSTEM_FLAG_SHOW_FOR_ALL_USERS), the title \"BiometricPrompt\" and the token are unchanged.",
      "packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptUdfpsTouchOverlayViewModel.kt (new): an UdfpsTouchOverlayViewModel implementation with an @Inject constructor. Its shouldHandleTouches is a MutableStateFlow(true) (\"Biometric Prompt should always handle touches\").",
      "packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt:show(): a new `REASON_AUTH_BP ->` branch, placed before the `else` branch, calls UdfpsTouchOverlayBinder.bind(view, promptUdfpsTouchOverlayViewModel.get(), udfpsOverlayInteractor). show(controller, params) becomes show(params), and shouldRotate(animation) becomes shouldRotate().",
      "packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt:constructor: adds Lazy<PromptUdfpsTouchOverlayViewModel>. Removes these parameters: Context, StatusBarStateController, StatusBarKeyguardViewManager, SystemUIDialogManager, DumpManager, ConfigurationController, UnlockedScreenOffAnimationController, ActivityTransitionAnimator, PrimaryBouncerInteractor, AlternateBouncerInteractor, isDebuggable, SelectedUserInteractor and ShadeInteractor. Also removes @JvmOverloads, SETTING_REMOVE_ENROLLMENT_UI (\"udfps_overlay_remove_enrollment_ui\") and shouldRemoveEnrollmentUi().",
      "packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java: the constructor takes and stores Lazy<PromptUdfpsTouchOverlayViewModel> mPromptUdfpsTouchOverlayViewModel and passes it when constructing UdfpsControllerOverlay. showUdfpsOverlay() now calls overlay.show(mOverlayParams).",
      "Tests: UdfpsControllerTest.java and UdfpsControllerOverlayTest.kt are updated to the new constructor and show() signatures and the new view model mock.",
      "Android 15 (ca71b9a63c40, Revert \"Fix biometric prompt appearing above shade\"): AuthContainerView.getLayoutParams() changes TYPE_APPLICATION_OVERLAY to TYPE_KEYGUARD_DIALOG. mPanelInteractionDetector.enable(() -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)) moves back after mWakefulnessLifecycle.addObserver(this) and runs unconditionally, no longer only when !constraintBp()."
     ],
     "completeness": [
      "The window type change in AuthContainerView.getLayoutParams() is applied. The Android 16 code context at android-security-16.0.0_r8 shows TYPE_KEYGUARD_DIALOG there. The material does not show whether any other code path sets the prompt window type.",
      "On Android 16 the UDFPS touch overlay gets a dedicated REASON_AUTH_BP binding. The other branches of show() are not modified by the diff.",
      "The Android 16 commit does not touch mPanelInteractionDetector in AuthContainerView, while the Android 15 revert restores unconditional mPanelInteractionDetector.enable(). The material does not show whether Android 16 still carries the conditional variant.",
      "Much of the Android 16 diff removes constructor parameters, a debug setting and test scaffolding."
     ],
     "behavior_changes": [
      "The BiometricPrompt window is TYPE_KEYGUARD_DIALOG again instead of TYPE_APPLICATION_OVERLAY. The title of the reverted commit suggests the prompt may again appear above the notification shade.",
      "Android 16: the UDFPS touch overlay for BiometricPrompt (REASON_AUTH_BP) is bound to PromptUdfpsTouchOverlayViewModel, whose shouldHandleTouches is always true.",
      "Android 16: the debug Settings.Global key udfps_overlay_remove_enrollment_ui (SETTING_REMOVE_ENROLLMENT_UI) is removed and no longer consulted.",
      "Android 16: the UdfpsControllerOverlay constructor and show() signatures change, so callers and tests must be updated.",
      "Android 15: mPanelInteractionDetector.enable(() -> animateAway(DISMISSED_USER_CANCELED)) runs regardless of constraintBp()."
     ],
     "preconditions": [
      "SystemUI is showing a BiometricPrompt (AuthContainerView attached via WindowManager).",
      "For the UDFPS overlay part: the device has an under-display fingerprint sensor, and the overlay is requested with REASON_AUTH_BP (UdfpsController showUdfpsOverlay -> UdfpsControllerOverlay.show()).",
      "The exact conditions under which the TYPE_APPLICATION_OVERLAY prompt leads to the EoP are not shown in the material."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29 the patched region is present (8/8 lines).",
      "15": "Adapted fix: platform/frameworks/base ca71b9a63c40 in android-security-15.0.0_r10. It reverts \"Fix biometric prompt appearing above shade\": the window type goes back to TYPE_KEYGUARD_DIALOG and mPanelInteractionDetector.enable() runs unconditionally again. It has no UDFPS view model changes. Present at android-security-15.0.0_r17 (8/8 lines).",
      "16": "Fix commit c2774dc575e9 in android-security-16.0.0_r1 (cherry-pick of 47fd897138eb, Change-Id Ifce421be7997eca4323a2e0412abca5753b6b787). It combines the window type change with PromptUdfpsTouchOverlayViewModel and the UdfpsControllerOverlay refactor. Present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "Not listed by the bulletin. At android-security-17.0.0_r1 the patched region is present (8/8 lines)."
     },
     "backport_notes": [
      "The window type change in AuthContainerView.getLayoutParams() is common to both versions. Google used a plain revert on 15 and a larger refactor on 16. When taking the 16 version: PromptUdfpsTouchOverlayViewModel has an @Inject constructor and is injected as Lazy<> into UdfpsController. The UdfpsControllerOverlay constructor and show() signature changes require matching updates in UdfpsController and in any downstream callers or tests.",
      "Check whether the target branch also has mPanelInteractionDetector.enable() moved under !constraintBp(). The 15 revert undoes that move; the 16 commit does not touch it."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete security consequence of BiometricPrompt being a TYPE_APPLICATION_OVERLAY window (what the EoP enables) is not shown in the material.",
      "The material does not show what DefaultUdfpsTouchOverlayViewModel and DeviceEntryUdfpsTouchOverlayViewModel do with shouldHandleTouches, or exactly which branch handled REASON_AUTH_BP before the fix. The diff cuts off the else branch body.",
      "Whether the Android 16 branch still contains the conditional mPanelInteractionDetector.enable() that the Android 15 revert undid.",
      "The original regressing commit (\"Fix biometric prompt appearing above shade\") and when it landed on each branch are not provided.",
      "Whether any other code path sets the BiometricPrompt window type."
     ],
     "meta": {
      "generated": "2026-10-04",
      "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-48529": {
     "cve": "CVE-2025-48529",
     "summary": "Removes the shared-preference storage, read-back and channel migration of the voicemail ringtone URI in frameworks/opt/telephony. As a result, a URI supplied through TelephonyManager#setVoicemailRingtoneUri is no longer persisted by the phone process, migrated onto the voicemail NotificationChannel, or returned by getVoicemailRingtoneUri.",
     "component": "platform/frameworks/opt/telephony, voicemail notification settings (VoicemailNotificationSettingsUtil, NotificationChannelController)",
     "weakness": "Unvalidated caller-supplied URI persisted and later applied or returned by a privileged process (cross-profile issue per the commit message, bulletin type ID). The fix adds no validation; it removes the storage path instead.",
     "root_cause": "VoicemailNotificationSettingsUtil.setRingtoneUri wrote any URI passed to TelephonyManager#setVoicemailRingtoneUri into the phone process default SharedPreferences. The setter is reached via PhoneInterfaceManager.setVoicemailRingtoneUri (per index search). The stored value was then used in two places: (a) getRingTonePreference read it back, and getRingtoneUri used that as a fallback when the voicemail channel was absent; (b) NotificationChannelController.migrateVoicemailNotificationSettings applied it as the sound of the \"voiceMail\" NotificationChannel. Per the commit message, setting the pref this way let it be migrated onto the notification channel, 'causing a cross-profile exploit'. The shared-preference path was a leftover from before Android P and was no longer the source of truth.",
     "fix_analysis": [
      "VoicemailNotificationSettingsUtil.java:setRingtoneUri: the body is replaced with a no-op comment, so the URI is no longer written to SharedPreferences (the editor.putString of getVoicemailRingtoneSharedPrefsKey is removed).",
      "VoicemailNotificationSettingsUtil.java:getRingtoneUri: now returns channel.getSound() if the voiceMail channel exists, else null. The fallback to getRingTonePreference(context) is removed, with an explicit comment never to consult shared preferences.",
      "VoicemailNotificationSettingsUtil.java:getRingTonePreference: the method is deleted entirely. It read the pref, called migrateVoicemailRingtoneSettingsIfNeeded and parsed the stored string into a Uri. The now-unused Settings and TextUtils imports are dropped.",
      "NotificationChannelController.java:migrateVoicemailNotificationSettings: the block that fetched getRingTonePreference and called voiceMailChannel.setSound(sound or DEFAULT_NOTIFICATION_URI, USAGE_NOTIFICATION) before createNotificationChannel is removed. The Uri import and the javadoc reference are also removed. Vibration migration (getVibrationPreference) is kept."
     ],
     "completeness": [
      "Covers the three uses of the ringtone shared preference shown in the diff: the setter, the getter fallback and the channel migration.",
      "The private helper migrateVoicemailRingtoneSettingsIfNeeded and the OLD_VOICEMAIL_*RINGTONE* key constants are not touched by the diff (the constants are still present at android-security-16.0.0_r8). Their only shown caller, getRingTonePreference, is removed, so they appear to be dead code, but the material does not confirm this.",
      "The vibration preference path (setVibrationEnabled, getVibrationPreference) still uses SharedPreferences and is still migrated to the channel. It stores a boolean, not a URI, and is unchanged.",
      "PhoneInterfaceManager.setVoicemailRingtoneUri in packages/services/Telephony (per index search) is unchanged: it still runs its permission checks and then calls the setter, which is now a no-op."
     ],
     "behavior_changes": [
      "TelephonyManager#setVoicemailRingtoneUri no longer persists the value. The caller-side permission checks in PhoneInterfaceManager still apply (per index search).",
      "TelephonyManager#getVoicemailRingtoneUri returns null when the voiceMail NotificationChannel does not exist. Before the fix it fell back to the stored pref, to DEFAULT_NOTIFICATION_URI if nothing was stored, or to null if the stored string was empty.",
      "The voiceMail channel created by migrateVoicemailNotificationSettings no longer has setSound called on it, so it keeps the NotificationChannel default sound instead of a migrated pref value."
     ],
     "preconditions": [
      "The caller reaches PhoneInterfaceManager.setVoicemailRingtoneUri (TelephonyManager#setVoicemailRingtoneUri) either as the default dialer or by passing enforceCallingOrSelfModifyPermissionOrCarrierPrivilege for the subscription, on a device with FEATURE_TELEPHONY_CALLING (per index search).",
      "Pre-fix code only: the voiceMail NotificationChannel is created or recreated so that migrateVoicemailNotificationSettings runs, or the channel is absent so that getRingtoneUri falls back to the shared preference."
     ],
     "per_major": {
      "14": "same fix: 122251ced157 in android-security-14.0.0_r22, diff identical to the 16 commit. 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: e5cdca27526f in android-security-15.0.0_r10, diff identical. 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: f33f9ef6467b in android-security-16.0.0_r1 (cherry-pick of 8e47af093625). 7/8 lines of the patched region are 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": [
      "A single self-contained commit with an identical diff on 14, 15 and 16. It deletes the public static getRingTonePreference(Context), so any fork code that calls it must be updated. The SharedPreferences and PreferenceManager imports must stay because the vibration methods still use them."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact cross-profile mechanism (which component in which profile consumed the migrated URI) is described only at a high level in the commit message and is not shown in code.",
      "Whether migrateVoicemailRingtoneSettingsIfNeeded, getVoicemailRingtoneSharedPrefsKey and the old ringtone key constants still read or write the ringtone pref elsewhere, or were removed in a follow-up; the diff leaves them in place.",
      "Whether any code outside the diffed files calls getRingTonePreference.",
      "What the one patched-region line missing from the latest tag of each major is."
     ],
     "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-48531": {
     "cve": "CVE-2025-48531",
     "summary": "Settings' CredentialStorage now identifies its caller via ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) instead of Activity.getCallingPackage() when deciding whether the caller is com.android.certinstaller.",
     "component": "platform/packages/apps/Settings, security/CredentialStorage (credential install activity)",
     "weakness": "Incorrect caller identification leading to an authorization check that can be satisfied by a package other than the intended one (improper access control)",
     "root_cause": "CredentialStorage.checkCallerIsCertInstallerOrSelfInProfile() in src/com/android/settings/security/CredentialStorage.java compared the result of Activity.getCallingPackage() against \"com.android.certinstaller\" and then passed that same value to PackageManager.checkSignatures(). The commit message says Activity.getCallingPackage() does not always return the package name of the app that actually called the activity. As a result, the identity being checked (and signature-matched against Settings) was not reliably the real caller.",
     "fix_analysis": [
      "src/com/android/settings/security/CredentialStorage.java: adds a private helper getCallingPackageName() that calls ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) and returns null on RemoteException.",
      "src/com/android/settings/security/CredentialStorage.java:checkCallerIsCertInstallerOrSelfInProfile(): reads the package once into a local callingPackage via getCallingPackageName(), compares it to \"com.android.certinstaller\", and passes the same local to getPackageManager().checkSignatures(callingPackage, getPackageName()). Both the name comparison and the signature check now use the launched-from package instead of Activity.getCallingPackage().",
      "Adds import android.app.ActivityManager; android.os.RemoteException was already imported."
     ],
     "completeness": [
      "Covers the only use of getCallingPackage() shown in the diff, inside checkCallerIsCertInstallerOrSelfInProfile(). Both occurrences (the equality test and the signature check) are replaced and now use a single value read once.",
      "This commit does not change the 'self in profile' branch of the same method (the launchedFromUserId logic after the hunk). The material does not show how that branch is implemented.",
      "The material does not show whether other activities in Settings rely on Activity.getCallingPackage() for authorization decisions. This commit only touches CredentialStorage."
     ],
     "behavior_changes": [
      "Caller identity for the certinstaller check now comes from ActivityManager's getLaunchedFromPackage() for the activity token rather than from Activity.getCallingPackage().",
      "If the call to ActivityManager throws RemoteException, the package is treated as null. The certinstaller branch is then not taken and evaluation falls through to the rest of the method.",
      "The diff introduces no new user-visible error or message."
     ],
     "preconditions": [
      "An app launches the Settings CredentialStorage activity such that checkCallerIsCertInstallerOrSelfInProfile() is evaluated. The material does not show the exact intent actions or the paths that call this check.",
      "The weak path required getCallingPackage() to report com.android.certinstaller while the real caller was a different package. The material does not explain how that condition arises."
     ],
     "per_major": {
      "14": "same fix: da4efc0a0615 in android-security-14.0.0_r22. The hunks are identical apart from the line offset (@@ -331). The patched region is present at android-security-14.0.0_r29 (7/8 lines).",
      "15": "same fix: cc1b1b5e493a in android-security-15.0.0_r10. The hunks are identical (@@ -332). The patched region is present at android-security-15.0.0_r17 (7/8 lines).",
      "16": "same fix: 0f61e586873a in android-security-16.0.0_r1 (cherry-pick of 70bd3efe0674). The patched region is present at android-security-16.0.0_r8 (7/8 lines).",
      "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 that relies on ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) being available. The same diff appears on 14, 15 and 16, differing only in line offsets."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not describe the specific scenario in which Activity.getCallingPackage() returned a misleading package, beyond what the commit message says.",
      "The material does not show which intent actions or flows in CredentialStorage call checkCallerIsCertInstallerOrSelfInProfile(), how launchedFromUserId is obtained, or what the profile branch of the method does.",
      "The material does not show why one of the 8 patched lines differs in the latest security tags (7/8 present)."
     ],
     "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-48532": {
     "cve": "CVE-2025-48532",
     "summary": "MediaProvider.markMediaAsFavorite no longer applies a caller-supplied ContentValues from the call extras. It now reads only a boolean IS_FAVORITE extra and builds the ContentValues itself, and the MediaStore client helper now sends that boolean instead of EXTRA_CONTENT_VALUES.",
     "component": "platform/packages/providers/MediaProvider, the MediaProvider.call() handler for MARK_MEDIA_AS_FAVORITE and its MediaStore client helper (apex/framework)",
     "weakness": "Improper input validation / confused deputy: caller-controlled ContentValues applied to media rows under the provider's own identity",
     "root_cause": "In src/com/android/providers/media/MediaProvider.java, markMediaAsFavorite(Bundle extras) took the ContentValues directly from extras.getParcelable(MediaStore.EXTRA_CONTENT_VALUES), which is controlled by whoever invoked resolver.call(AUTHORITY, MARK_MEDIA_AS_FAVORITE, ...). It then called clearLocalCallingIdentity() and ran update(uri, values, null) for every URI in the ClipData. For callers other than the package manager, the only check was AccessChecker.hasAccessToCollection(..., forWrite=false), i.e. read access to the collection. The values were not limited to the IS_FAVORITE column, so columns other than is_favorite could be written to media rows under the provider's cleared identity. The commit message says: \"The content values retrieved from extras in the MediaProvider can have values preset by the calling app and can affect other fields set.\"",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:markMediaAsFavorite: no longer reads MediaStore.EXTRA_CONTENT_VALUES from the extras. Instead it reads `final boolean areFavorites = extras.getBoolean(MediaColumns.IS_FAVORITE)`. After the existing per-URI access check it builds a fresh ContentValues that contains only MediaColumns.IS_FAVORITE, set to 1 or 0. That provider-built ContentValues is what goes to update(uri, values, null) inside the clearLocalCallingIdentity()/restoreLocalCallingIdentity(token) block, so no caller-chosen column can reach update().",
      "apex/framework/java/android/provider/MediaStore.java (the helper that issues MARK_MEDIA_AS_FAVORITE, around line 1710): no longer builds the ContentValues, and replaces `extras.putParcelable(EXTRA_CONTENT_VALUES, values)` with `extras.putBoolean(MediaColumns.IS_FAVORITE, areFavorites)` to match the new provider-side contract. ClipData handling (EXTRA_CLIP_DATA) is unchanged."
     ],
     "completeness": [
      "Covers the MARK_MEDIA_AS_FAVORITE call path: the provider ignores any EXTRA_CONTENT_VALUES a caller still puts in the extras, and only is_favorite can be written through this method.",
      "The existing access check in markMediaAsFavorite is unchanged. Callers other than the package manager still only need to pass AccessChecker.hasAccessToCollection(..., forWrite=false) (read access) before the provider performs the update with its own identity. The fix narrows what is written, not who can trigger it.",
      "In the Android 16 code, MediaStore.createRequest(resolver, method, uris, values) still forwards caller-supplied ContentValues as EXTRA_CONTENT_VALUES to resolver.call(AUTHORITY, method, ...), where MediaProvider.getResultForCreateOperationsRequest passes them to createRequest(method, extras). That path returns a PendingIntent, and how it processes the values is not shown in the material, so whether it has a similar issue cannot be judged from the material."
     ],
     "behavior_changes": [
      "The wire format of the MARK_MEDIA_AS_FAVORITE call changed: the provider now expects a boolean extra keyed by MediaColumns.IS_FAVORITE and no longer reads EXTRA_CONTENT_VALUES. A client that sends only EXTRA_CONTENT_VALUES will have its items marked not favorite (getBoolean defaults to false) instead of having its values applied.",
      "Only the is_favorite column can be changed through this method. Any other columns in a supplied ContentValues are silently dropped and no new error is raised."
     ],
     "preconditions": [
      "The caller can invoke ContentResolver.call on MediaStore.AUTHORITY with the MARK_MEDIA_AS_FAVORITE method and supply a Bundle with EXTRA_CLIP_DATA and (before the fix) EXTRA_CONTENT_VALUES.",
      "Unless the caller is the package manager (isCallingPackageManager()), it must have read access to the collection matched by each URI (AccessChecker.hasAccessToCollection with forWrite=false). Otherwise UnsupportedOperationException is thrown before any update.",
      "The material does not show whether arbitrary third-party apps can reach MARK_MEDIA_AS_FAVORITE. The explicit branch for callers other than the package manager suggests other callers are expected."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). At android-security-14.0.0_r29, 7/8 lines of the patched region are present. The exact state of that branch is not shown.",
      "15": "Not affected per the bulletin (not listed). At android-security-15.0.0_r17, 7/8 lines of the patched region are present. The exact state of that branch is not shown.",
      "16": "Same fix: commit a6fcb52734f2 in android-security-16.0.0_r1 (cherry-pick of the internal change, Change-Id I0227139b0a04efcb7456557fbf2d47996c017bad). 8/8 lines of the patched region are 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": [
      "A single commit touches both halves of the module: apex/framework MediaStore.java (client) and MediaProvider.java (provider). Apply both together, because the extras contract changes from EXTRA_CONTENT_VALUES to a boolean MediaColumns.IS_FAVORITE extra. MediaProvider is a mainline module, so both halves normally ship together (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which columns or fields a caller could affect through the old path, and the impact beyond the bulletin's High EoP rating.",
      "Whether arbitrary third-party apps can call MARK_MEDIA_AS_FAVORITE, or whether it is gated elsewhere in MediaProvider.call().",
      "Why Android 14 and 15 are listed as not affected even though the markMediaAsFavorite region exists there (7/8 lines present). The material does not show the state of those branches before the fix.",
      "How MediaProvider.createRequest(method, extras) handles caller-supplied EXTRA_CONTENT_VALUES on the createRequest path.",
      "Whether the patch applies cleanly to customized or older trees."
     ],
     "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-48535": {
     "cve": "CVE-2025-48535",
     "summary": "In Settings' AppRestrictionsFragment, the custom restrictions Intent returned by an app is now copied before vetting, and the copy is pinned to the exact component and package that passed the resolveActivity/package check. That pinned copy, not a fresh copy of the caller-supplied Intent, is what startActivityForResult launches.",
     "component": "platform/packages/apps/Settings, users / app restrictions (AppRestrictionsFragment.RestrictionsResultReceiver)",
     "weakness": "Insufficient validation of an externally supplied Intent before launching it from a privileged app (the vetted object differs from the launched object, and the component is not pinned to the vetted one)",
     "root_cause": "RestrictionsResultReceiver.onReceive takes restrictionsIntent from the broadcast result extras (Intent.EXTRA_RESTRICTIONS_INTENT). It calls assertSafeToStartCustomActivity to check that mPackageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY) resolves to an activity in the expected packageName, then launched `new Intent(restrictionsIntent)`. The vetting function returned nothing, so the launched Intent kept whatever targeting the app supplied. Only the result of one implicit resolution was checked, and the Intent actually started was not constrained to the component that passed the check. After the first commit, the code still resolved the original Intent object and copied it only after the check, so the object that was resolved and the object that was copied and launched were different instances.",
     "fix_analysis": [
      "Commit e12156e2d805 (\"AppRestrictions - use vetted component\"), src/com/android/settings/users/AppRestrictionsFragment.java: assertSafeToStartCustomActivity now returns an Intent. After the resolveActivity and packageName equality checks pass, it builds `vettedIntent = new Intent(intent)` and calls vettedIntent.setComponent(activityInfo.getComponentName()) and vettedIntent.setPackage(activityInfo.packageName). This forces the launch target to the exact ActivityInfo that was vetted.",
      "Same commit, RestrictionsResultReceiver.onReceive: stores the returned vettedIntent and passes it to AppRestrictionsFragment.this.startActivityForResult(vettedIntent, requestCode) instead of `new Intent(restrictionsIntent)`. The ActivityNotFoundException/SecurityException catch path (log, EventLog 200688991, return) is unchanged.",
      "Commit 25ed933c4cf5 (\"startActivityForResult with earlier new Intent\"), assertSafeToStartCustomActivity: moves `final Intent vettedIntent = new Intent(intent)` to the top of the method, before resolveActivity, and resolves vettedIntent instead of the caller-supplied intent. The component/package pinning is then applied to the same object that was resolved. After the copy is taken, the caller-supplied Intent is referenced only in exception messages."
     ],
     "completeness": [
      "Covers the single launch site in RestrictionsResultReceiver.onReceive: the launched Intent is now a private copy whose component and package are set from the vetted ResolveInfo.activityInfo.",
      "The check is still a package-name equality test (packageName.equals(activityInfo.packageName)) plus MATCH_DEFAULT_ONLY resolution. The fix does not change the policy; it only makes sure the launched target matches what was checked.",
      "The shown commits do not strip URI-grant flags, data or ClipData from the Intent. The android-security-16.0.0_r8 context shows extra lines in assertSafeToStartCustomActivity (vettedIntent.removeFlags(FLAG_GRANT_*_URI_PERMISSION...), setData(null), setClipData(null)) that are not part of either diff provided here. Their origin is not in the material.",
      "Extras carried in the Intent are kept in the copy (new Intent(intent) copies them); the shown commits do not filter them."
     ],
     "behavior_changes": [
      "The custom restrictions activity is now always started explicitly (component and package set to the resolved ActivityInfo), even if the app supplied an implicit Intent.",
      "No new error paths: failures still raise ActivityNotFoundException/SecurityException, which is caught and logged, and the launch is skipped."
     ],
     "preconditions": [
      "Per the surrounding code, Settings must be showing AppRestrictionsFragment, and RestrictionsResultReceiver.onReceive must run with result extras that contain Intent.EXTRA_RESTRICTIONS_INTENT (a non-null restrictionsIntent).",
      "invokeIfCustom must be true and AppRestrictionsFragment.this.isResumed() must be true.",
      "The supplied Intent must resolve (MATCH_DEFAULT_ONLY) to an activity whose package equals the receiver's packageName, otherwise the launch is skipped."
     ],
     "per_major": {
      "14": "same fix (first commit shown): commit 33a1cbf7bd0e (\"AppRestrictions - use vetted component\") in android-security-14.0.0_r22, with the same hunks at slightly different line offsets. At android-security-14.0.0_r29, 8/8 lines of the patched region are present. The material does not show the \"earlier new Intent\" commit for 14.",
      "15": "same fix: commit 1e4423730f87 (\"startActivityForResult with earlier new Intent\") in android-security-15.0.0_r10, with the identical diff (blob c4a01797d0e -> 5b2a86fff42). The preimage blob matches the post-image of the first commit, so the first commit was already present. At android-security-15.0.0_r17, 8/8 lines are present.",
      "16": "same fix: commits e12156e2d805 and 25ed933c4cf5 in android-security-16.0.0_r1. At android-security-16.0.0_r8, 8/8 lines are present, plus extra URI-grant/data/ClipData stripping lines that do not come from these commits.",
      "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": [
      "Apply the two commits in order. First the \"use vetted component\" change (return type change of assertSafeToStartCustomActivity and the onReceive call site), then the \"earlier new Intent\" change, which moves the copy above resolveActivity. Both touch only src/com/android/settings/users/AppRestrictionsFragment.java."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show exactly how the originally launched Intent could differ from the vetted resolution. The commit message only says \"See bug\" (bug 353680402; the first commit also lists 365739560).",
      "Which commit introduced the removeFlags/setData(null)/setClipData(null) lines visible at android-security-16.0.0_r8, and whether they are tied to this CVE.",
      "Whether and in which tag the \"earlier new Intent\" commit was applied on Android 14 (only the first commit is shown for 14).",
      "Which tag on Android 15 received the first commit (only the second commit is shown for 15)."
     ],
     "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-48537": {
     "cve": "CVE-2025-48537",
     "summary": "Makes ZenModeConditions track CPS subscriptions by (condition Uri, ComponentName) instead of Uri alone. It also restricts matching against built-in system ConditionProviderServices (schedule, event, etc.) to system-owned rules and the manual rule, and rejects non-system rules whose component lives in the \"android\" package.",
     "component": "platform/frameworks/base, NotificationManagerService / Zen mode (ZenModeConditions, ConditionProviders)",
     "weakness": "Improper ownership/authorization check and key collision. A condition Uri was treated as a unique identifier across different ConditionProviderServices, and nothing checked that a rule binding to a system CPS belongs to the system.",
     "root_cause": "In services/core/java/com/android/server/notification/ZenModeConditions.java, mSubscriptions was an ArrayMap<Uri, ComponentName> and evaluateConfig built a 'current' ArraySet<Uri>. Two rules from different CPSes sharing a condition Uri therefore collided: one map entry overwrote the other, and the unsubscribe loop compared Uris only, so the wrong provider could be unsubscribed or kept subscribed. Separately, evaluateRule iterated mConditionProviders.getSystemProviders() for every rule. If sp.isValidConditionId(id) matched, it called ensureRecordExists for the system component and rewrote rule.component to the system CPS, regardless of which package owned the rule. A non-system rule whose conditionId had a system-provider format (e.g. a schedule condition Uri) was therefore bound to a system CPS and shared its condition records. Non-system rules with a component in the \"android\" package were not rejected.",
     "fix_analysis": [
      "ZenModeConditions.java: mSubscriptions changed from ArrayMap<Uri, ComponentName> to ArraySet<Pair<Uri, ComponentName>>. evaluateConfig builds 'current' as ArraySet<Pair<Uri, ComponentName>>, and the cleanup loop now checks current.contains(subscription) on the pair and calls unsubscribeIfNecessary(subscription.second, subscription.first), so subscriptions are keyed per provider.",
      "ZenModeConditions.java:evaluateRule: computes isSystemRule = isManual || ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getPkg()). If the rule is not a system rule but rule.component is non-null and its package is SYSTEM_AUTHORITY (\"android\"), it logs a Slog.w and returns without creating records or subscribing.",
      "ZenModeConditions.java:evaluateRule: the loop over getSystemProviders() (which calls ensureRecordExists and overwrites rule.component with the system CPS component) now runs only when isSystemRule is true. Non-system rules always fall through to findConditionProvider(rule.component) using their own component.",
      "ZenModeConditions.java:evaluateRule: adds the rule to 'current' and mSubscriptions as new Pair<>(id, rule.component) after the component has been resolved.",
      "ConditionProviders.java: adds @VisibleForTesting getRecord(Uri, ComponentName), which wraps getRecordLocked(id, component, false), and makes ConditionRecord package-private. Both changes are for tests only, with no runtime behaviour change.",
      "Tests: the new ZenModeConditionsTest covers three cases: a system rule with the system CPS (subscribed), a package rule with the system CPS (ignored, no record), and a package rule with a package CPS but a system-like Uri (record under the package CPS only). ZenModeHelperTest.testRulesWithSameUri was rewritten to use a registered package CPS."
     ],
     "completeness": [
      "Both issues named in the commit message are addressed in evaluateRule and evaluateConfig: per-provider subscription keys and system-only matching of system providers.",
      "Ownership is derived from rule.getPkg(). Correctness depends on pkg being set truthfully when rules are created or restored, which is outside this diff.",
      "Rules with configurationActivity != null, and automatic rules with component == null, are skipped before reaching the new check. These are pre-existing early exits and are unchanged.",
      "The check is only in ZenModeConditions.evaluateRule. ConditionProviders.notifyConditions and getRecordLocked already key records by (id, component) and were not changed."
     ],
     "behavior_changes": [
      "Non-system automatic rules whose component is in package \"android\" are skipped with a warning log and are never subscribed or evaluated.",
      "Non-system rules with a system-format condition Uri are no longer rebound to the built-in system CPS. They are resolved through their own component, and if that CPS is not found the existing handling for a missing CPS applies.",
      "Subscription bookkeeping and unsubscription are now per (Uri, ComponentName), so two providers sharing a Uri no longer interfere with each other's subscriptions."
     ],
     "preconditions": [
      "A ZenRule in the ZenModeConfig whose pkg is not \"android\" and which has either a conditionId accepted by SystemConditionProviderService.isValidConditionId (e.g. a schedule condition Uri) or a component in the \"android\" package.",
      "ZenModeConditions.evaluateConfig running over that config. The updated testRulesWithSameUri suggests this path is reached when rules are added via ZenModeHelper.addAutomaticZenRule; exact trigger points are not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit 49078c4bfdce in android-security-14.0.0_r22. Same logic, but it also adds the isManual parameter to evaluateRule (absent on this branch) and adapts ZenModeHelperTest to the older addAutomaticZenRule signature. 7/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 63aab59ce138 in android-security-15.0.0_r10. The core diff is identical, and the test is adapted to the UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI API. 7/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "same fix: commit 57451c039840 in android-security-16.0.0_r1 (cherry-pick of 5cb0ae9c43e2). 7/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed). 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "On branches where evaluateRule lacks the isManual parameter (as on 14), the fix must also pass isManual through from evaluateConfig (true for config.manualRule, false for automatic rules), because isSystemRule depends on it.",
      "The ConditionProviders changes (getRecord, package-private ConditionRecord) are only needed for the new ZenModeConditionsTest. The runtime fix is entirely in ZenModeConditions.java."
     ],
     "confidence": "high",
     "unknowns": [
      "How a non-system package could get a rule with a system-format conditionId or an \"android\" component into the config (validation in addAutomaticZenRule is not shown).",
      "What specific information was disclosed through the shared system CPS condition records (the bulletin only rates it ID / High).",
      "Which line of the patched region is missing at each latest tag (7/8 lines 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-48538": {
     "cve": "CVE-2025-48538",
     "summary": "PackageManagerService's package-hiding path now refuses to hide the SystemUI package (resolved via PackageManagerInternal.getSystemUiServiceComponent()) in addition to the \"android\" package, returning false and logging a warning.",
     "component": "platform/frameworks/base, PackageManagerService (package hiding)",
     "weakness": "Missing validation of a protected package name in the hide-package path (insufficient denylist), leading to DoS",
     "root_cause": "In services/core/java/com/android/server/pm/PackageManagerService.java, the package-hiding path (the block that logs \"Cannot hide package\") only rejected the \"android\" package. Any other package, including the SystemUI package, could be hidden. Per the commit message, hiding SystemUI makes the phone impossible to use, even when done inside a managed profile (the commit was tested manually with a custom DPC).",
     "fix_analysis": [
      "PackageManagerService.java, hide-package path (hunk at line ~5957 on 16): the existing guard `\"android\".equals(packageName)` is extended with `|| LocalServices.getService(PackageManagerInternal.class).getSystemUiServiceComponent().getPackageName().equals(packageName)`.",
      "When either condition matches, the code logs `Slog.w(TAG, \"Cannot hide package: \" + packageName)` and returns false at this guard, so the hide request is rejected.",
      "Single commit, 5 insertions / 3 deletions, no other files touched."
     ],
     "completeness": [
      "Covers only the hide-package path in PackageManagerService where the \"android\" check already lived. The SystemUI package is identified dynamically via getSystemUiServiceComponent() rather than by a hardcoded name.",
      "The check is on package name only and applies regardless of which user/profile the request targets, which matches the commit message's managed-profile case.",
      "Other mechanisms that could disable or suspend a package (e.g. enabled-setting or suspension paths) are not touched by this diff. The material does not show whether they already protect SystemUI.",
      "The new code dereferences getSystemUiServiceComponent() without a null check. The material does not show whether that component can be null."
     ],
     "behavior_changes": [
      "Requests to hide the SystemUI package now fail (return false) with a warning log \"Cannot hide package: <packageName>\" instead of succeeding.",
      "The warning log for the \"android\" case now includes the package name rather than the literal \"android\"."
     ],
     "preconditions": [
      "A caller able to reach the PackageManagerService hide-package path, for example a device policy controller (the commit was tested with a custom DPC), including from within a managed profile.",
      "The target package name equals the SystemUI package."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 36348592fd27 in android-security-14.0.0_r22, identical hunk at line ~5765.",
      "15": "same fix: platform/frameworks/base bd7578b738a0 in android-security-15.0.0_r10, identical hunk at line ~5867.",
      "16": "same fix: platform/frameworks/base 4e1be9d5eb9b in android-security-16.0.0_r1, hunk at line ~5957.",
      "17": "not affected per the bulletin (not listed). 2/3 lines of the patched region are present at android-security-17.0.0_r1. Whether the fix is present there, possibly in modified form, cannot be verified from the material."
     },
     "backport_notes": [
      "The hunk is identical across 14/15/16 (only line offsets differ) and depends on PackageManagerInternal.getSystemUiServiceComponent() and LocalServices being available in the target branch.",
      "The per-major facts report only 1/3 of the patched lines present at the latest 14/15/16 security tags even though the fix is resolved. Check the current form of the check in the target branch before assuming it is missing (the region may have been reworded or moved later)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the enclosing method is not shown in the diff hunk header (only the class is).",
      "Why the latest 14/15/16 security tags show only 1/3 of the patched lines present (later refactor vs. partial presence) is not shown.",
      "Whether getSystemUiServiceComponent() can return null on any configuration, and whether other disable/suspend paths protect SystemUI.",
      "Whether any state is modified earlier in the method, before this guard."
     ],
     "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-2025-48539": {
     "cve": "CVE-2025-48539",
     "summary": "In AclArbiter::SendPacketToPeer (system/stack/arbiter/acl_arbiter.cc), the warning log on the failure path now reports buffer.size() from the rust::Vec the function still owns. Before, it read p_buf->len from a BT_HDR that L2CA_SendFixedChnlData may already have freed.",
     "component": "platform/packages/modules/Bluetooth, system/stack/arbiter (the GATT/ATT ACL arbiter between Rust GATT and L2CAP)",
     "weakness": "use after free (read of a freed heap buffer in a log statement)",
     "root_cause": "AclArbiter::SendPacketToPeer allocates a BT_HDR p_buf with osi_malloc, copies the incoming rust::Vec<uint8_t> buffer into it and passes it to stack::l2cap::get_interface().L2CA_SendFixedChnlData(L2CAP_ATT_CID, p_tcb->peer_bda, p_buf). The commit message says this call may free the buffer. When the call returned anything other than tL2CAP_DW_RESULT::SUCCESS, the code logged p_buf->len, which dereferences the possibly freed BT_HDR.",
     "fix_analysis": [
      "system/stack/arbiter/acl_arbiter.cc:AclArbiter::SendPacketToPeer: the log::warn on the L2CA_SendFixedChnlData failure path now prints buffer.size() instead of p_buf->len. buffer is the rust::Vec parameter owned by this function, and its size is the same value stored in p_buf->len at line 114. As a result, p_buf is no longer touched after it is handed to L2CAP.",
      "No other logic changes: the allocation, the copy, the offset/len setup and the L2CA_SendFixedChnlData call are unchanged. The only edit is the argument of the warning log."
     ],
     "completeness": [
      "The fix covers the single dereference of p_buf after the call in SendPacketToPeer. In the fixed function context (lines 101-123), p_buf is not used again after L2CA_SendFixedChnlData.",
      "The fix does not change the ownership contract of L2CA_SendFixedChnlData and adds no handling of p_buf on failure. It only removes the stale read. The material does not show when that call frees p_buf.",
      "The other arbiter entry points in the shown context (OnIncomingMtuResp, OnIncomingMtuReq) do not allocate or pass BT_HDR buffers, so this pattern does not apply to them."
     ],
     "behavior_changes": [
      "No functional change. The warning prints the same length value, because buffer.size() equals the value assigned to p_buf->len, but now reads it from live memory. Only the failure-path log is affected."
     ],
     "preconditions": [
      "A non-FLOSS build (the function body returns immediately under TARGET_FLOSS).",
      "AclArbiter::SendPacketToPeer is reached through the free function SendPacketToPeer, which posts it to the main thread with do_in_main_thread (context lines 137-140). tcb_idx must be valid, so that gatt_get_tcb_by_idx returns a non-null tGATT_TCB.",
      "L2CA_SendFixedChnlData(L2CAP_ATT_CID, ...) returns a result other than tL2CAP_DW_RESULT::SUCCESS, and the buffer has been freed by that call (the commit message says it may free the buffer). The exact conditions inside L2CAP are not shown in the material."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. 0/5 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Adapted fix: commit c881220f499e in android-security-15.0.0_r10 makes the same one-line change (p_buf->len to buffer.size()) against the older call form, a direct L2CA_SendFixedChnlData call compared with L2CAP_DW_SUCCESS. 4/5 lines are present at android-security-15.0.0_r17 (fixed state).",
      "16": "Same fix: commit 33be5d45cb27 in android-security-16.0.0_r1 (a cherry-pick of 243d7484e597). 4/5 lines are present at android-security-16.0.0_r8 (fixed state).",
      "17": "Not affected per the bulletin (not listed). 4/5 lines of the patched region are present at android-security-17.0.0_r1, matching the fixed state."
     },
     "backport_notes": [
      "A single one-line change with no dependencies. On trees with the older call style (L2CA_SendFixedChnlData(...) != L2CAP_DW_SUCCESS), apply the Android 15 variant c881220f499e. Otherwise apply 33be5d45cb27."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the L2CA_SendFixedChnlData implementation. It is therefore unknown which results free p_buf, and under what conditions a remote peer could cause them.",
      "The researcher POC mentioned in the commit message is not included, and the material does not show the concrete trigger path from the Rust GATT layer."
     ],
     "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-48540": {
     "cve": "CVE-2025-48540",
     "summary": "Three libbinder RPC Binder commits change RpcState.cpp so that it ends the RPC session (session->shutdownAndWait(false)) on error returns that previously left the session open: CommandData allocation failure (NO_MEMORY) in waitForReply and processTransact, object-table span failure (BAD_VALUE) in processTransactInternal, and RPC_COMMAND_TRANSACT received under a restricted CommandType (BAD_TYPE) in processCommand.",
     "component": "platform/frameworks/native, libs/binder RPC Binder (RpcState.cpp)",
     "weakness": "Improper error handling: unrecoverable wire-protocol errors returned an error code but left the RPC session open, so the RPC stream, which is not self-synchronizing, could continue in a desynchronized state",
     "root_cause": "RpcState.cpp reads a RpcWireHeader and then a body of command.bodySize bytes from the connection. Several error paths returned early without ending the session. In RpcState::waitForReply and RpcState::processTransact, when the CommandData allocation for the body failed, the code returned NO_MEMORY with the body still unread. In processTransactInternal, when the object table span could not be formed, it returned BAD_VALUE even though the log message said 'Terminating!'. In RpcState::processCommand, an RPC_COMMAND_TRANSACT arriving while type != CommandType::ANY returned BAD_TYPE with the body unread. The commit messages say that in the out-of-memory case \"the wire protocol gets messed up\" and that the SPAN error \"is not recoverable\". The existing comment in processCommand notes that the RPC wire protocol is not self synchronizing, so after such an early return the session cannot tell where the next command begins. The fix commits make these errors end the session instead of treating them as recoverable.",
     "fix_analysis": [
      "Commit 300a057414d7 (RPC Binder: shutdown on ENOMEM), libs/binder/RpcState.cpp:RpcState::waitForReply: when CommandData data(command.bodySize - rpcReplyWireSize) is not valid(), call (void)session->shutdownAndWait(false) before returning NO_MEMORY.",
      "Same commit, libs/binder/RpcState.cpp:RpcState::processTransact: when CommandData transactionData(command.bodySize) is not valid(), call (void)session->shutdownAndWait(false) before returning NO_MEMORY. The added comment says a graceful recovery would require reading all the transaction data and still sending a reply. The fix does not do that.",
      "Commit 91aec28d0f59 (RPC Binder: shutdown on SPAN error), libs/binder/RpcState.cpp, processTransactInternalTailCall section of processTransactInternal: after the existing ALOGE (ending ' objectTableBytesSize=%zu. Terminating!') on the maybeSpan failure path, add (void)session->shutdownAndWait(false) before returning BAD_VALUE.",
      "Commit a9e6d5a3c679 (RPC Binder: clearer errors for wrong transact type), libs/binder/RpcState.cpp:RpcState::processCommand: in case RPC_COMMAND_TRANSACT, if type != CommandType::ANY, log ALOGE('CommandType %d, but got RPC command %d.'), call (void)session->shutdownAndWait(false), then return BAD_TYPE. Before, this was a bare return BAD_TYPE. The commit message says this path is separate from the reported bug but was found while investigating it."
     ],
     "completeness": [
      "Covers both allocation-failure sites that read a command body (the waitForReply reply body and the processTransact transaction body), the object-table span failure in processTransactInternal, and the wrong-CommandType path in processCommand.",
      "The fix does not drain the unread body or send a reply. It only ends the session. The commit message says 'More work needs to be done to ignore transactions in out of memory conditions', so graceful recovery is explicitly out of scope.",
      "In the android-security-16.0.0_r8 context, RpcState::waitForReply still has a similar object-table reinterpret failure path (ALOGE 'Bad object table size inferred from RpcWireReply ... Terminating!' followed by return BAD_VALUE, lines 722-728) with no shutdownAndWait call. Only the processTransactInternal counterpart received the SPAN fix. On that reply path rpcRec has already read the whole body, so the material does not show whether it has the same consequence.",
      "Other error returns in these functions (for example rpcRec failures and rpcReply.status != OK) are unchanged. The material does not show how rpcRec failures are handled."
     ],
     "behavior_changes": [
      "On CommandData allocation failure in waitForReply or processTransact, the RPC session is now shut down (shutdownAndWait(false)) instead of only returning NO_MEMORY. The error code is unchanged.",
      "On object-table span failure in processTransactInternal, the session is now shut down before BAD_VALUE is returned.",
      "In processCommand, an RPC_COMMAND_TRANSACT received when type != CommandType::ANY now logs an ALOGE and shuts down the session before returning BAD_TYPE. Before, it returned without logging and the session stayed open.",
      "Peers that trigger any of these conditions now lose the session, which is stricter than before."
     ],
     "preconditions": [
      "The process must use RPC Binder (libbinder RpcSession/RpcServer), with a peer that can send RPC wire commands.",
      "ENOMEM paths: the CommandData allocation for the command body must fail, either in waitForReply (a client waiting for a reply) or in processTransact (receiving a transaction).",
      "SPAN path: a transaction whose object table bytes cannot be turned into a span of uint32_t entries in processTransactInternal. The material does not show the full path that reaches this check.",
      "BAD_TYPE path: an RPC_COMMAND_TRANSACT delivered while processCommand is called with a CommandType other than ANY. The callers that do this are not shown in the material."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin. The material contains no fix commit or code for Android 13, so how the fix was applied there is unknown.",
      "14": "Same change as the 16 SPAN commit: commit 995eb96a80a2 (RPC Binder: shutdown on SPAN error) in android-security-14.0.0_r22 adds the same line at a different offset (hunk at line 946). The material shows only this commit for 14, so it does not show whether the ENOMEM and wrong-transact-type commits were also applied. The resolver reports 5/5 patched lines present at android-security-14.0.0_r29.",
      "15": "Same change as the 16 transact-type commit: commit 7fb4755c9d93 (RPC Binder: clearer errors for wrong transact type) in android-security-15.0.0_r10, with an identical hunk at line 824. The material shows only this commit for 15. The resolver reports 5/6 patched lines present at android-security-15.0.0_r17.",
      "16": "Same fix: three commits, 300a057414d7, 91aec28d0f59 and a9e6d5a3c679, in android-security-16.0.0_r1, applied in that order (each diff's index line starts where the previous one ends). The code at android-security-16.0.0_r8 shows the waitForReply ENOMEM change and the processCommand change in place. The resolver reports 5/6 patched lines present.",
      "17": "Not affected (not listed by the bulletin). The resolver reports 7/8 patched-region lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The commits on 16 are consecutive (300a057414d7 (ENOMEM), then 91aec28d0f59 (SPAN), then a9e6d5a3c679 (transact type)). All of them touch only libs/binder/RpcState.cpp and add a few lines around existing return statements. Offsets differ between branches: the SPAN hunk is at line 946 on 14 and line 987 on 16.",
      "For 13/14/15 branches, check that all three changes are present, because the material shows only one of the three commits for each of 14 and 15. A tree has all three changes when shutdownAndWait is called after the object-table span ALOGE in processTransactInternal, after both CommandData valid() checks, and in the RPC_COMMAND_TRANSACT/BAD_TYPE branch."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which of the three changes matches the reported issue (bug 404210068 / 414720799 / 416734088), and what consequence of the desynchronized session led to the EoP rating.",
      "Whether the ENOMEM and wrong-transact-type commits were applied to Android 14, whether the ENOMEM and SPAN commits were applied to Android 15, and whether anything was applied to Android 13.",
      "Which callers invoke processCommand with a CommandType other than ANY.",
      "How rpcRec failures and other early returns in the same functions relate to session shutdown.",
      "What shutdownAndWait(false) does to the individual connections of a session.",
      "What can cause CommandData allocation to fail (for example, how bodySize is limited)."
     ],
     "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-48541": {
     "cve": "CVE-2025-48541",
     "summary": "FaceSettings now reads the hw_auth_token, sensor id, challenge and EXTRA_USER_ID intent extras only when the SettingsActivity's initial calling package is Settings itself. For any other caller, or when no calling package is known, it ignores them and uses the current user.",
     "component": "platform/packages/apps/Settings, biometrics/face (FaceSettings fragment)",
     "weakness": "Missing caller validation: the screen trusted intent extras supplied from outside (EXTRA_USER_ID and the auth token, sensor id and challenge extras were accepted from any caller that launched the face settings screen)",
     "root_cause": "In src/com/android/settings/biometrics/face/FaceSettings.java, the fragment read KEY_TOKEN (\"hw_auth_token\"), BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, BiometricEnrollBase.EXTRA_KEY_CHALLENGE and Intent.EXTRA_USER_ID from the launching Intent without any condition. According to the fix's comment, these extras are meant only for internal calls by Settings. However, the screen can be started with the android.settings.FACE_SETTINGS action, so another caller could supply them. The commit's test case launches the screen from a non-zero user with EXTRA_USER_ID=0, which would make the face settings screen use another user's id. The bulletin rates the issue as a High EoP.",
     "fix_analysis": [
      "FaceSettings.java (field declarations): mSensorId is now initialised to -1 and mChallenge to 0, so they keep the old defaults for a missing extra when the extras are not read. mToken stays null.",
      "FaceSettings.java (the initialisation method that obtains UserManager/FaceManager/DevicePolicyManager; the hunk header shows only the class): the method gets the hosting SettingsActivity via requireActivity() and calls activity.getInitialCallingPackage(). If that value is null or differs from activity.getPackageName(), all four extras are ignored and mUserId = UserHandle.myUserId(). Only when the initial calling package equals Settings' own package does it read KEY_TOKEN, EXTRA_KEY_SENSOR_ID, EXTRA_KEY_CHALLENGE and Intent.EXTRA_USER_ID from getIntent(), as before.",
      "The following mUserManager.getUserInfo(mUserId).isManagedProfile() check is unchanged and now sees the restricted mUserId."
     ],
     "completeness": [
      "One check covers all four sensitive extras in FaceSettings (token, sensor id, challenge, user id). When in doubt it refuses: the extras are ignored both for a foreign calling package and for a null calling package.",
      "The check relies on SettingsActivity.getInitialCallingPackage() being accurate. The material does not show how that value is derived.",
      "Only FaceSettings is changed. The material does not show whether sibling biometric settings fragments (for example fingerprint) read the same extras.",
      "External callers now get mToken == null and mChallenge == 0. The material does not show how the controllers that use these values handle a missing token."
     ],
     "behavior_changes": [
      "When FACE_SETTINGS is launched by any package other than Settings, or with no identifiable calling package, EXTRA_USER_ID is ignored and the screen uses UserHandle.myUserId().",
      "For such callers the hw_auth_token, sensor id and challenge extras are ignored, so the fields keep token null, sensorId -1 and challenge 0.",
      "Intent extras keep working unchanged when Settings itself is the initial calling package."
     ],
     "preconditions": [
      "A caller other than Settings (in the commit's test, running as a non-zero user) launches android.settings.FACE_SETTINGS with Intent.EXTRA_USER_ID set, and optionally the token, sensor or challenge extras."
     ],
     "per_major": {
      "14": "same fix: f3cc1073e51e in android-security-14.0.0_r22, identical logic. Only the surrounding context differs (FeatureFactory.getFactory(getContext()) instead of getFeatureFactory()). 7/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: aba2077a6c03 in android-security-15.0.0_r10, same diff. 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "same fix: 2aa27461a40b in android-security-16.0.0_r1 (Change-Id I06193e421a140a90568251fc25baa7fc81c12d78, cherry-picked from internal commits 486947205e05 and 0baf462586f1). 7/8 lines of the patched region are 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 diff touches only FaceSettings.java and applies with small context adjustments across 14 to 16. It depends on SettingsActivity.getInitialCallingPackage() and on the fragment being hosted in a SettingsActivity, because the cast will throw otherwise. Check that both hold on the target branch. Android 13 is listed as affected, but the material contains no 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "How SettingsActivity.getInitialCallingPackage() is computed and whether it stays reliable when the activity is recreated.",
      "What the FaceSettings controllers do when mToken is null and mChallenge is 0 for an external caller.",
      "Whether other biometric settings fragments read the same extras without a caller check.",
      "The exact name of the patched method (the hunk header shows only the class).",
      "Whether the screen can be reached on devices without a face sensor."
     ],
     "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-48542": {
     "cve": "CVE-2025-48542",
     "summary": "Adds per-account storage accounting to AccountManagerService. New user-data and auth-token writes are refused once an account has used its share of a 30,000,000-char per-user budget, and passwords longer than 1,000,000 chars are rejected when an account is added or a password is set.",
     "component": "platform/frameworks/base, system_server AccountManagerService (services/core/java/com/android/server/accounts/AccountManagerService.java)",
     "weakness": "Missing size/quota bound on caller-supplied data persisted to the accounts database (uncontrolled resource consumption)",
     "root_cause": "AccountManagerService saved caller-supplied account user data (setUserdataInternal), auth tokens (the insertAuthToken path) and passwords (the add-account and set-password paths) into the per-user accounts database. User data and tokens also went into the in-memory caches (userDataCache, authTokenCache). None of this had a limit on the size or total amount of data per account. The diff context shows an existing 200-char check on the account name. An account owner could therefore grow the AccountManager DB and the caches without bound. The bulletin classifies the issue as a High-severity DoS.",
     "fix_analysis": [
      "AccountManagerService.java: adds the constants MAXIMUM_PASSWORD_LENGTH = 1000*1000 and STORAGE_LIMIT_PER_USER = 30*1000*1000. Adds a new UserAccounts field mCacheSizeForAccount (Map<Account,Integer>, protected by cacheLock) that holds an estimated storage size for each account.",
      "AccountManagerService.computeEntrySize: the size of one key/value entry is key.length() + value.length() + 20, with a null key or value counting as 1. Sizes are in Java chars, not bytes.",
      "AccountManagerService.recomputeCacheSizeForAccountLocked: recomputes the account's size by summing computeEntrySize over the entries currently in accounts.userDataCache.get(account) and accounts.authTokenCache.get(account), then stores it in mCacheSizeForAccount. Either map may be null, in which case it contributes 0.",
      "AccountManagerService.shouldBlockDatabaseWrite (caller holds cacheLock): adds computeEntrySize(key,value) to the account's running estimate and stores the result. If the estimate is below STORAGE_LIMIT_PER_USER/100, it returns false (allow). Otherwise it counts the accounts in accounts.accountCache (minimum 1) and allows the write if the estimate is below STORAGE_LIMIT_PER_USER/numberOfAccounts. If not, it calls recomputeCacheSizeForAccountLocked for a precise figure, adds the new entry again and allows the write if it is below the per-account share. Otherwise it logs 'uses too much storage' and returns true (block).",
      "AccountManagerService.setUserdataInternal: under dbLock and then cacheLock, if value != null and shouldBlockDatabaseWrite(...) returns true, it logs and returns before beginTransaction, so the write is silently dropped. Deletions (value == null) are never blocked.",
      "Auth-token save path (the method under dbLock that calls deleteAuthtokensByAccountIdAndType and insertAuthToken; its name is not shown in the hunk): computes shouldBlockWrite under cacheLock before beginTransaction. After the old token of that type is deleted, if authToken != null and shouldBlockWrite is true, it logs and returns false before setTransactionSuccessful, so insertAuthToken is skipped. Token removal (authToken == null) is never blocked.",
      "Add-account path (method name not shown in the hunk): right after the existing 200-char name check, the add is rejected (returns false with a log) if password != null and password.length() > MAXIMUM_PASSWORD_LENGTH.",
      "Set-password path (method name not shown in the hunk): returns early with a log if the new password is longer than MAXIMUM_PASSWORD_LENGTH.",
      "Cache maintenance: recomputeCacheSizeForAccountLocked runs when userDataCache or authTokenCache is first loaded from the DB in writeUserDataIntoCacheLocked, writeAuthTokenIntoCacheLocked, readAuthTokenInternal and the user-data read path. It also runs after a rename, for renamedAccount. mCacheSizeForAccount.remove(account) is added next to the existing authTokenCache/visibilityCache removals in two places: the account-validation removal branch and the method that removes an account from the caches."
     ],
     "completeness": [
      "Quota accounting covers only user data (userDataCache) and auth tokens (authTokenCache). Passwords have their own length limit (1,000,000 chars) but do not count toward the per-account estimate. Other per-account rows (visibility, previous name) are not counted either.",
      "The precise recomputation in shouldBlockDatabaseWrite sums only the caches currently loaded for the account. For example, if authTokenCache.get(account) is null, the tokens stored in the DB count as 0. The effective bound therefore applies to the loaded caches, not to the on-disk DB.",
      "The running estimate only ever goes up: overwriting an existing key or removing one does not lower it. The auth-token path also adds an entry size when authToken is null. The estimate is corrected only by a recompute (at cache load, at rename, or when a threshold is crossed). The new entry's size is recorded even when the write is then blocked. Compared with the loaded caches this leads to over-estimation; compared with the DB, the estimate can still be too low (see previous point).",
      "The per-account share is STORAGE_LIMIT_PER_USER / numberOfAccounts, with a fast-path floor of STORAGE_LIMIT_PER_USER/100 (300,000 chars). This change does not limit the number of accounts. The code comment calls 100 the upper bound, but the material does not show where that is enforced.",
      "Sizes are measured as String.length() (UTF-16 units), not database bytes.",
      "The checks sit in the internal write methods. The material does not show which Binder entry points call them. Per index search (may be incomplete), no other chunks mentioning writeUserDataIntoCacheLocked or writeAuthTokenIntoCacheLocked came up among the top results."
     ],
     "behavior_changes": [
      "setUserData writes for an account over its storage share are silently dropped: only a Log.w is written, with no exception or error returned to the caller.",
      "Auth-token writes for an over-quota account fail silently: the save method returns false before insertAuthToken and logs 'Too much storage is used - block token update'.",
      "Adding an account fails (returns false) when the password is longer than 1,000,000 chars. Setting such a password is ignored, with a Log.w.",
      "Extra per-account size bookkeeping under cacheLock on cache load, on rename, and on each user-data or auth-token write. Near the limit this includes a full recompute over the account's cached entries.",
      "New Log.w messages: 'Account of type=... uses too much storage', 'Account cannot be added - password is too long', 'New password is too long for accountType=...'."
     ],
     "preconditions": [
      "The caller must be able to reach the AccountManager write APIs for an account (setUserData, setAuthToken, setPassword, or adding an account with a password). AccountManagerService normally restricts these to the UID of the account type's authenticator (general platform knowledge).",
      "For the add-account path, the user must be unlocked (the existing isLocalUnlockedUser check is visible in the diff context)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 9a4d7193f893 in android-security-14.0.0_r22, with identical logic. Only the context differs: setUserdataInternal has no callingUid parameter, and cancelNotification takes UserHandle.of(accounts.userId). 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 7ed50e5b5d56 in android-security-15.0.0_r10, identical content; setUserdataInternal also has no callingUid parameter. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "fixed: platform/frameworks/base 50ae729881da in android-security-16.0.0_r1 (cherry-pick of 6b8138ca5279, Change-Id I88a0fef8e2e7bc232768bd5f7aa3f4bf87cb1c2c). 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 that touches only AccountManagerService.java and adds no dependencies. On older trees, adjust for the two context differences seen in the 14/15 versions: setUserdataInternal without callingUid, and cancelNotification(..., UserHandle.of(accounts.userId)). The hunk guarding insertAuthToken must stay inside the existing transaction, before setTransactionSuccessful, so the preceding delete is not committed.",
      "The material provides no comparison against a fork, so no per-file fork verdict can be given."
     ],
     "confidence": "high",
     "unknowns": [
      "The bulletin lists Android 13 as affected, but the material contains no Android 13 fix commit or tag status.",
      "The material does not describe the exact trigger or the concrete DoS effect (DB growth, system_server memory, or boot/unlock failure); only the bulletin's 'DoS' type is known.",
      "The hunk headers do not show the names of the methods holding the auth-token insert, add-account password and set-password password hunks, or the first cache-removal hunk.",
      "The material does not show whether the 100-account cap per user (assumed by the comment in shouldBlockDatabaseWrite) is enforced anywhere.",
      "The material does not show the full set of Binder entry points that reach the guarded internal methods."
     ],
     "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-48543": {
     "cve": "CVE-2025-48543",
     "summary": "Adds an IsInstantiable() check to the ART JNI AllocObject/NewObjectV/NewObjectA implementations, so that allocating an abstract class or interface throws java.lang.InstantiationException, and relaxes the CheckJNI wrapper so it aborts only for array classes.",
     "component": "platform/art, runtime JNI (runtime/jni/jni_internal.cc, runtime/jni/check_jni.cc)",
     "weakness": "Missing validation of class instantiability in JNI object allocation (abstract class or interface instances could be allocated)",
     "root_cause": "In runtime/jni/jni_internal.cc, the JNI allocation entry points (AllocObject, NewObjectV and NewObjectA, matching the three CheckJNI wrappers) returned early only on a null class. They then went to allocation (the String special case or the generic path) without checking whether the class is instantiable. Before the fix, only the CheckJNI layer (ScopedCheck::CheckInstantiableNonArray in check_jni.cc) rejected non-instantiable classes. The regression test test/863-serialization shows the issue being reached through ObjectInputStream deserialization of a stream that names the abstract class java.text.DateFormat. Its info.txt says: 'we forgot to check if a class is instantiable'.",
     "fix_analysis": [
      "runtime/jni/jni_internal.cc, class JNI, first hunk (~line 922, the path using gc::AllocatorType for the String case, likely AllocObject): after the `c == nullptr` early return, adds `if (UNLIKELY(!c->IsInstantiable()))`. This calls soa.Self()->ThrowNewExceptionF(\"Ljava/lang/InstantiationException;\", \"Can't instantiate %s %s\", interface-or-abstract-class, c->PrettyDescriptor()) and returns nullptr before any allocation.",
      "runtime/jni/jni_internal.cc, class JNI, hunks at ~line 956 and ~line 989 (the NewObjectV/NewObjectA paths, which have the String.<init> to StringFactory rewrite): the same IsInstantiable() check is inserted at the same point, before the String rewrite.",
      "runtime/jni/check_jni.cc: ScopedCheck::CheckInstantiableNonArray is renamed to CheckNonArray, and its condition changes from `!c->IsInstantiableNonArray()` to `c->IsArrayClass()`. The three CheckJNI callers (AllocObject, NewObjectV, NewObjectA) now call CheckNonArray. As a result, abstract classes and interfaces are no longer a CheckJNI abort and are handled by the new runtime exception, while array classes still abort under CheckJNI.",
      "test/863-serialization is added: it deserializes a crafted stream (ConcurrentHashMap and java.text.DateFormat) and expects an InvalidClassException whose cause is InstantiationException."
     ],
     "completeness": [
      "All three allocation hunks in jni_internal.cc receive the runtime check, and the CheckJNI side is adjusted consistently for AllocObject, NewObjectV and NewObjectA.",
      "The check is placed after the null-class early return and before the IsStringClass branch, so neither the String path nor the generic path can allocate a non-instantiable class.",
      "The fix is scoped to JNI entry points. The material does not show whether other allocation paths in ART (reflection, interpreter, compiled code) perform an equivalent check.",
      "The material does not show whether IsInstantiable() rejects array classes on the runtime (non-CheckJNI) path."
     ],
     "behavior_changes": [
      "JNI AllocObject, NewObjectV and NewObjectA now return null with a pending java.lang.InstantiationException (\"Can't instantiate interface X\" / \"Can't instantiate abstract class X\") when the class is abstract or an interface.",
      "Under CheckJNI, passing an abstract class or interface to these functions no longer aborts the process; the InstantiationException is raised instead. Array classes still trigger the CheckJNI abort, with the same message.",
      "Java deserialization of the test's stream, which names the abstract class java.text.DateFormat, now fails with InvalidClassException caused by InstantiationException (per the regression test)."
     ],
     "preconditions": [
      "A caller must reach JNI AllocObject/NewObjectV/NewObjectA with a jclass that is abstract or an interface.",
      "The regression test shows one path that reaches it: java.io.ObjectInputStream.readObject() on crafted serialized bytes that name an abstract serializable class (java.text.DateFormat in the test)."
     ],
     "per_major": {
      "14": "same fix: platform/art caeef036e31b in android-security-14.0.0_r22, same change content with line offsets differing (jni_internal.cc hunks at 921/948/974, check_jni.cc caller hunks at 2196/2212/2238). At android-security-14.0.0_r29, 6/8 lines of the patched region are present.",
      "15": "same fix: platform/art 444fc40dfb04 in android-security-15.0.0_r10, same content (jni_internal.cc hunks at 924/951/977). At android-security-15.0.0_r17, 6/8 lines of the patched region are present.",
      "16": "same fix: platform/art 3eb64751e5a7 in android-security-16.0.0_r1 (cherry-pick of internal a4826745b63b / a5889a1a8511, Change-Id I4ccf22f85b4ae0325e9f8e29503149bbda533e86). At android-security-16.0.0_r8, 6/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, the same as the fixed 14/15/16 tags."
     },
     "backport_notes": [
      "The change is self-contained (two runtime files plus a test) and was applied with the same content on 14, 15 and 16, with only line-number drift. It uses mirror::Class::IsInstantiable(), IsInterface() and IsArrayClass(). The material indicates no dependency commit.",
      "ART ships as a mainline module, so the fix may also reach devices through an ART module update and not only through a platform security tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not describe the concrete consequence of holding an instance of an abstract class or interface; it gives only the EoP/High rating.",
      "The material does not show whether the bulletin-listed Android 13 received the same patch (no 13 fix commit is included).",
      "The material does not show whether IsInstantiable() returns false for array classes.",
      "The material does not show which libcore native code calls JNI NewObject on the deserialization path; only the end-to-end test behavior is shown.",
      "The jni_internal.cc hunk headers do not name the functions. The mapping to AllocObject/NewObjectV/NewObjectA is inferred from the code context and the CheckJNI callers.",
      "The material does not show whether the varargs JNI NewObject is covered through delegation.",
      "The material does not explain why 2 of the 8 lines of the patched region are missing at each latest tag."
     ],
     "meta": {
      "generated": "2026-10-02",
      "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-48545": {
     "cve": "CVE-2025-48545",
     "summary": "AccountManagerService.isSystemUid() now returns false immediately when the calling UID is an SDK sandbox UID, so sandbox callers can no longer be classified as system callers via the package-flag lookup.",
     "component": "platform/frameworks/base, AccountManagerService (services/core/java/com/android/server/accounts)",
     "weakness": "Improper caller identity check (missing exclusion of SDK sandbox UIDs in a privilege check), leading to information disclosure per the bulletin",
     "root_cause": "isSystemUid(int callingUid) in AccountManagerService.java decides whether a caller is \"system\" by calling mPackageManager.getPackagesForUid(callingUid) with calling identity cleared. It returns true if any returned package has ApplicationInfo.FLAG_SYSTEM set. It did not exclude SDK sandbox UIDs. The commit title ('Don't allow SdkSandbox to bypass systemUid check') implies that a caller with a sandbox UID could pass this check. The material does not show exactly what the package lookup returned for such UIDs, or which account-manager paths the check guards.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountManagerService.java:isSystemUid: adds an early check `if (Process.isSdkSandboxUid(callingUid)) return false;` before the package lookup. Any SDK sandbox UID is now treated as non-system, whatever getPackagesForUid/getPackageInfo report.",
      "The rest of the logic is unchanged (clearCallingIdentity, getPackagesForUid, the FLAG_SYSTEM test on each package, restoreCallingIdentity). The fix is a single 3-line guard that adds no new imports: android.os.Process is already used in the file (Process.SYSTEM_UID)."
     ],
     "completeness": [
      "Covers every caller of isSystemUid in AccountManagerService, because the guard is inside the helper itself.",
      "Only the FLAG_SYSTEM-based helper isSystemUid is changed. The nearby check at line 6130 (`UserHandle.getAppId(callerUid) == Process.SYSTEM_UID`) is a different, exact-UID comparison and is left untouched.",
      "The commit does not address other code in this file or elsewhere that might infer privilege from a UID's packages without excluding sandbox UIDs. The material shows no such code."
     ],
     "behavior_changes": [
      "isSystemUid no longer treats callers with an SDK sandbox UID as system. Any AccountManagerService operation that relied on that check now takes its non-system path for sandbox callers (the concrete effect is not shown).",
      "No new log messages, exceptions or API changes. Behavior is unchanged for callers that are not in an SDK sandbox."
     ],
     "preconditions": [
      "The caller runs with an SDK sandbox UID (one for which Process.isSdkSandboxUid returns true) and invokes an AccountManagerService entry point that consults isSystemUid.",
      "Which AccountManagerService API entry points call isSystemUid is not shown in the material or the index results."
     ],
     "per_major": {
      "14": "same fix: commit 30dc386d4a57 in android-security-14.0.0_r22, the same 3-line guard in isSystemUid (hunk at line 5985); all 3 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 66ac17909252 in android-security-15.0.0_r10, the same 3-line guard (hunk at line 6080); all 3 lines present at android-security-15.0.0_r17.",
      "16": "same fix: commit c21f79312d3f in android-security-16.0.0_r1, the same 3-line guard (hunk at line 6146); all 3 lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the 3 guard lines are already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained hunk. It applies wherever isSystemUid exists and Process.isSdkSandboxUid is available. Only the line offset differs between 14, 15 and 16."
     ],
     "confidence": "high",
     "unknowns": [
      "Which AccountManagerService Binder methods call isSystemUid, and therefore what account data was reachable (neither the material nor the index search shows any callers).",
      "What getPackagesForUid actually returns for an SDK sandbox UID on the affected builds.",
      "Whether an Android 13 fix commit exists, and whether Process.isSdkSandboxUid is available there. The bulletin lists 13 as affected, but the material includes no 13 commit."
     ],
     "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-48546": {
     "cve": "CVE-2025-48546",
     "summary": "SafeActivityOptions now throws a SecurityException when the supplied ActivityOptions carry setLaunchWindowingMode(WINDOWING_MODE_PINNED), so an activity can no longer be started directly into the pinned (picture-in-picture) windowing mode through that option.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (services/core/java/com/android/server/wm/SafeActivityOptions.java)",
     "weakness": "Missing authorization check on a caller-controlled launch option (ActivityOptions launch windowing mode PINNED was not rejected by SafeActivityOptions)",
     "root_cause": "The method in SafeActivityOptions that performs the 'Permission Denial' checks validated several ActivityOptions fields (for example the launch activity type) but did not reject a launch windowing mode of WINDOWING_MODE_PINNED. The removed tests testKeepsPictureInPictureLaunchModeInOptions and testKeepsPictureInPictureLaunchModeWithBoundsInOptions in TaskLaunchParamsModifierTests show that TaskLaunchParamsModifier kept that option and produced a PINNED windowing mode for the launched task. A caller able to pass such ActivityOptions to an activity start could therefore request a launch directly into the pinned windowing mode instead of using the public path ActivityOptions#makeLaunchIntoPip. The bulletin rates the issue as a High severity EoP.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/SafeActivityOptions.java, in the method that performs the existing 'Permission Denial' checks (the hunk header does not show its name): after an existing SecurityException check and before the launch activity type check, a new block reads options.getLaunchWindowingMode(). If the value equals WINDOWING_MODE_PINNED, it logs a Slog.w with the intent, callerApp, callingPid and callingUid, then throws SecurityException. The check is unconditional: it is not gated on isSystemOrSystemUI or on any permission. The code comment directs callers to ActivityOptions#makeLaunchIntoPip.",
      "A static import of android.app.WindowConfiguration.WINDOWING_MODE_PINNED is added to SafeActivityOptions.java.",
      "services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java: adds a case that builds ActivityOptions.makeBasic() with setLaunchWindowingMode(WINDOWING_MODE_PINNED) and checks through verifySecureExceptionThrown(activityOptions, taskSupervisor) that a SecurityException is thrown (Android 15 and 16 commits only).",
      "services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java: removes testKeepsPictureInPictureLaunchModeInOptions and testKeepsPictureInPictureLaunchModeWithBoundsInOptions, which asserted that a PINNED launch windowing mode from the options was preserved."
     ],
     "completeness": [
      "Covers exactly one value: options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED. Other windowing modes passed through setLaunchWindowingMode are not touched by this change.",
      "The rejection is absolute (no system/SystemUI exemption), unlike the neighbouring activity-type check, which applies only to non-system callers.",
      "The check lives in the SafeActivityOptions permission check, so it only protects activity starts that send their ActivityOptions through that method. The material does not show whether any start path uses ActivityOptions without going through SafeActivityOptions.",
      "TaskLaunchParamsModifier itself is not changed. Only its tests for keeping PINNED were removed.",
      "The ActivityOptions#makeLaunchIntoPip path is not modified."
     ],
     "behavior_changes": [
      "Any activity start whose ActivityOptions have setLaunchWindowingMode(WINDOWING_MODE_PINNED) and that goes through this check now fails with SecurityException ('Permission Denial: ... with setLaunchWindowingMode=PINNED') and logs a warning under TAG, whatever the caller's uid or permissions.",
      "Callers that relied on setLaunchWindowingMode(PINNED) need to use ActivityOptions#makeLaunchIntoPip instead, as the code comment says.",
      "Two wmtests cases asserting that the PINNED launch mode was kept were deleted. SafeActivityOptionsTest gained a negative test (Android 15/16 commits)."
     ],
     "preconditions": [
      "The caller can start an activity while supplying ActivityOptions that reach the SafeActivityOptions permission check in system_server.",
      "The ActivityOptions have the launch windowing mode set to WINDOWING_MODE_PINNED through setLaunchWindowingMode.",
      "Before the fix, SafeActivityOptions had no PINNED-specific check at this point. The material does not show whether other gating existed."
     ],
     "per_major": {
      "14": "Same source fix (commit 41bdf43266da in android-security-14.0.0_r22, RESTRICT AUTOMERGE; hunk at about line 407 instead of 372). The test changes differ: only the TaskLaunchParamsModifierTests removals are included, and the SafeActivityOptionsTest negative test is not added. 8/8 patched lines are present at android-security-14.0.0_r29.",
      "15": "Same fix (commit 8b660e887005 in android-security-15.0.0_r10, RESTRICT AUTOMERGE; hunk at about line 391), including the SafeActivityOptionsTest addition and the TaskLaunchParamsModifierTests removals. 8/8 patched lines are present at android-security-15.0.0_r17.",
      "16": "Fix commit 3668e1d4bbec in android-security-16.0.0_r1 (RESTRICT AUTOMERGE cherry-pick of b49d5d4d03dd), covering the source file and both test files. 8/8 patched lines are 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": [
      "This is a single self-contained commit: one import plus one if-block inserted before the getLaunchActivityType check in SafeActivityOptions. Only the line offset differs across 14/15/16 (about 407/391/372). Vendor or system components that launch into PiP through setLaunchWindowingMode(PINNED) will get a SecurityException.",
      "The bulletin lists Android 13 as affected, but the material includes no Android 13 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the patched method is not shown by the diff hunk header (only the class is shown).",
      "The material does not describe the concrete privilege or UI effect gained by launching directly into PINNED mode.",
      "Whether any activity start path uses ActivityOptions without passing through the SafeActivityOptions permission check.",
      "The Android 13 fix commit is not included in the material, even though 13 is listed as affected."
     ],
     "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-48547": {
     "cve": "CVE-2025-48547",
     "summary": "PermissionController now calls PermissionManager.stopOneTimePermissionSession(packageName) after a revocation only when no granted permission of the package still carries FLAG_PERMISSION_ONE_TIME. This applies to both the legacy AppPermissionGroup.java revoke path and the KotlinUtils.kt revoke path. Before the fix, the package-wide one-time session was stopped whenever a single one-time group was revoked.",
     "component": "platform/packages/modules/Permission, PermissionController (one-time permission session handling in AppPermissionGroup.java and KotlinUtils.kt)",
     "weakness": "Logic error in session state handling: the per-package one-time permission session was ended while other one-time grants in the same package were still active. The Kotlin helper also had an incorrect granted/one-time flag check.",
     "root_cause": "Per the commit message, each package has a single one-time permission session that covers all of its one-time grants. In AppPermissionGroup.java, the else branch always called mContext.getSystemService(PermissionManager.class).stopOneTimePermissionSession(packageName) when a one-time group was revoked. So revoking one one-time permission ended the only session tracking the package's remaining one-time grants. In KotlinUtils.kt, the guard anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup) was also unreliable in three ways. First, it skipped every permission belonging to the group being examined. Second, its per-permission test masked the flags with FLAG_PERMISSION_ONE_TIME before testing FLAG_PERMISSION_REVOKED_COMPAT, so that test was always true. Third, it compared the requestedPermissionsFlags bitfield with PackageManager.PERMISSION_GRANTED (value 0) instead of testing the REQUESTED_PERMISSION_GRANTED bit (general platform knowledge on those constants). As a result, granted one-time permissions were not correctly detected, and the session could be stopped while one-time grants remained.",
     "fix_analysis": [
      "AppPermissionGroup.java (the method containing the stopOneTimePermissionSession call; the hunk does not show its name): the plain `else` becomes `else if (!anyPermsOfPackageOneTimeGranted(getApp()))`. The session is now stopped only when no permission in the package is still granted as one time.",
      "AppPermissionGroup.java: a new private helper, anyPermsOfPackageOneTimeGranted(PackageInfo), returns false if requestedPermissions or requestedPermissionsFlags is null. Otherwise it iterates over requestedPermissions and skips entries whose requestedPermissionsFlags lack PackageInfo.REQUESTED_PERMISSION_GRANTED. For each remaining entry it queries mPackageManager.getPermissionFlags(perm, packageName, getUser()) and returns true if FLAG_PERMISSION_REVOKED_COMPAT is clear and FLAG_PERMISSION_ONE_TIME is set. The commit adds imports for FLAG_PERMISSION_ONE_TIME, FLAG_PERMISSION_REVOKED_COMPAT and android.annotation.SuppressLint, and annotates the helper with @SuppressLint(\"MissingPermission\").",
      "KotlinUtils.kt (revoke path): the call site becomes `if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo))`, dropping the `group` argument.",
      "KotlinUtils.kt anyPermsOfPackageOneTimeGranted: the commit removes the `group: LightAppPermGroup?` parameter, the `if (group?.isOneTime == true) return true` shortcut and the `permName in group?.permissions` skip. The user is now always UserHandle.getUserHandleForUid(packageInfo.uid). Permissions whose requestedPermissionsFlags lack PackageInfo.REQUESTED_PERMISSION_GRANTED are skipped. The full getPermissionFlags value, no longer pre-masked, is tested for REVOKED_COMPAT clear and ONE_TIME set. The commit adds an import for android.content.pm.PackageInfo."
     ],
     "completeness": [
      "The fix covers both PermissionController revocation paths in the diff that call stopOneTimePermissionSession: the legacy AppPermissionGroup.java model and the KotlinUtils.kt LightAppPermGroup path. Both now use the same per-package check. Only the Java helper adds null checks on the arrays.",
      "The Java helper takes the REQUESTED_PERMISSION_GRANTED bits from the PackageInfo returned by getApp(), and the Kotlin helper takes them from newGroup.packageInfo. Flags are fetched live through getPermissionFlags. The material does not show whether those PackageInfo objects already reflect the revocation just performed.",
      "The fix does not change how sessions are started or any framework-side session logic. It only gates the stop call in PermissionController.",
      "The material shows no other callers of stopOneTimePermissionSession. Whether any exist outside these two paths is unknown."
     ],
     "behavior_changes": [
      "stopOneTimePermissionSession is no longer called while the package still has another granted permission flagged FLAG_PERMISSION_ONE_TIME (and not FLAG_PERMISSION_REVOKED_COMPAT). The one-time session therefore keeps running for the remaining one-time grants.",
      "On the Java path, revoking a one-time group now runs one getPermissionFlags query per granted requested permission of the package before deciding whether to stop the session.",
      "On the Kotlin path, the group-specific shortcut (treat the group as one-time if newGroup.isOneTime) is removed. The decision is now based only on package-wide permission flags."
     ],
     "preconditions": [
      "A package holds more than one permission granted as one-time, so a single per-package one-time session covers them.",
      "One of those one-time permissions or groups is revoked through a PermissionController path that reaches the stopOneTimePermissionSession call, either in AppPermissionGroup.java or in the KotlinUtils.kt revoke path with wasOneTime true.",
      "The bulletin rates the issue High severity EoP. The material does not show the specific trigger beyond the conditions above."
     ],
     "per_major": {
      "14": "Adapted fix: commit 122e39ced57a in android-security-14.0.0_r22. The AppPermissionGroup.java change is the same. The KotlinUtils.kt hunk is adapted to the older code: the call site uses app.getSystemService(PermissionManager::class.java) directly with no ContextCompat.createDeviceContext/deviceId, the helper has no @Suppress(\"MissingPermission\"), and line offsets differ. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 5dca0ccb26f2 in android-security-15.0.0_r10. AppPermissionGroup.java is identical to the Android 16 commit. KotlinUtils.kt differs only in line offsets and in a trailing comma on the removed parameter line; the resulting code is the same. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Same fix: commit 78fc913decb3 in android-security-16.0.0_r1. 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 commit, two files. Port the new guard condition and the rewritten anyPermsOfPackageOneTimeGranted helper, and drop the `group` parameter at all call sites. The Android 14 variant shows the KotlinUtils.kt call site without the deviceId/ContextCompat.createDeviceContext wrapper. The material does not include an Android 13 fix.",
      "PermissionController ships as part of the Permission mainline module, so the fix may also arrive through a module update rather than a platform build (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The name of the AppPermissionGroup.java method that contains the modified else branch (the hunk header does not show it).",
      "Whether the PackageInfo from getApp() and newGroup.packageInfo reflects the revocation just performed, and whether the framework clears FLAG_PERMISSION_ONE_TIME on revoke. Both affect the outcome of the new check for the revoked permission itself.",
      "The exact framework-side effect of stopping the session on the remaining one-time grants. The material only states that the session is the only one for all of the package's one-time grants.",
      "Whether other PermissionController code paths call stopOneTimePermissionSession.",
      "How the fix was applied on Android 13, which the bulletin lists as affected."
     ],
     "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-48550": {
     "cve": "CVE-2025-48550",
     "summary": "Adds package-name validation (FrameworkParsingPackageUtils.validateName) to both PkgUser constructors in SlicePermissionManager, so a malformed package name is rejected with IllegalArgumentException instead of being stored.",
     "component": "platform/frameworks/base, system_server slice permission management (services/core/java/com/android/server/slice/SlicePermissionManager.java)",
     "weakness": "Missing input validation of a package name string (bulletin classifies the impact as DoS)",
     "root_cause": "SlicePermissionManager.PkgUser stored whatever string it was given as mPkg. This applied to PkgUser(String pkg, int userId) and to PkgUser(String pkgUserStr), which parses a \"pkg@userId\" string. Nothing checked that the string was a well-formed package name or usable as a filename. Per the commit title, this concerns granting slice permissions. The bulletin classifies the result as a High-severity DoS; the material does not show the exact failure mode.",
     "fix_analysis": [
      "SlicePermissionManager.java: a new private method PkgUser.enforceValidPackage() calls FrameworkParsingPackageUtils.validateName(mPkg, false /* requireSeparator */, true /* requireFilename */). If the returned error string is non-empty, it throws IllegalArgumentException(error). With requireSeparator=false a single-segment name is accepted; requireFilename=true also requires the name to be usable as a filename (general platform knowledge about validateName).",
      "SlicePermissionManager.java: PkgUser(String pkg, int userId) now calls enforceValidPackage() after assigning mPkg and mUserId.",
      "SlicePermissionManager.java: PkgUser(String pkgUserStr) now calls enforceValidPackage() after parsing the pkg@userId string. This constructor already declared IllegalArgumentException for parse failures.",
      "Imports added: android.content.pm.parsing.FrameworkParsingPackageUtils and android.text.TextUtils."
     ],
     "completeness": [
      "Both PkgUser constructors are covered, so every PkgUser constructed in SlicePermissionManager now carries a validated package name. The material does not list the code paths that construct PkgUser.",
      "Validation is syntactic only: it checks that the string is a well-formed name and filename, not that the package exists or belongs to the caller. The diff adds no existence or ownership check, and the material does not show whether one exists elsewhere.",
      "The diff touches only PkgUser. No other string fields in SlicePermissionManager or SliceProviderPermissions are shown being validated. The material gives no evidence of a problem there, so this is a scope observation, not a known gap."
     ],
     "behavior_changes": [
      "Constructing a PkgUser with a package name that fails FrameworkParsingPackageUtils.validateName now throws IllegalArgumentException. The material does not show how this surfaces to callers of the slice grant APIs.",
      "Strings in pkg@userId form whose package name is invalid now throw IllegalArgumentException from PkgUser(String). The material does not show how callers of that constructor handle the exception."
     ],
     "preconditions": [
      "A caller must reach a SlicePermissionManager path that constructs a PkgUser from a supplied package name. Per the commit title, this is the slice permission grant path. Per index search, TvSettings SliceManagerCompat calls SliceManager.grantPermissionFromUser, but the material does not show that this call leads to PkgUser.",
      "The exact binder entry point and any permission check needed to reach it are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 54158e2925dc in android-security-14.0.0_r22, same content as the Android 16 commit (hunk line offsets differ); 8/8 lines present at android-security-14.0.0_r29",
      "15": "same fix: platform/frameworks/base 354820f6ec38 in android-security-15.0.0_r10, same content; 8/8 lines present at android-security-15.0.0_r17",
      "16": "same fix: platform/frameworks/base bae8ab23b03d in android-security-16.0.0_r1 (cherry-pick of internal 53dc72fedf79); 8/8 lines present at android-security-16.0.0_r8",
      "17": "not affected per bulletin; the patched region is already present (8/8 lines) at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single self-contained commit. It depends only on FrameworkParsingPackageUtils.validateName(String, boolean, boolean) and TextUtils, both already present on the 14, 15 and 16 branches where it applied. Android 13 is listed as affected, but the material includes no Android 13 commit, so check that the helper is available there before backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete DoS failure mode is not shown, including how an invalid package name stored in PkgUser (for example, through persistence) leads to denial of service. Only the bulletin's DoS classification and the commit title are available.",
      "The binder entry point and permission checks guarding the grant path in SliceManagerService are not in the material, and neither is the link from SliceManager.grantPermissionFromUser to PkgUser construction.",
      "How callers of PkgUser(String pkgUserStr) handle the newly thrown IllegalArgumentException for already-persisted invalid entries is not shown.",
      "Whether an Android 13 security branch received the fix is not covered by the per-major facts provided."
     ],
     "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-48551": {
     "cve": "CVE-2025-48551",
     "summary": "The Sharesheet \"Edit\" image action in IntentResolver is changed to start the image editor as the user that launched the sharesheet (mUserInteractor.getLaunchedAs(), or UserHandle.of(UserHandle.myUserId()) on Android 14) instead of always as the personal profile user.",
     "component": "platform/packages/modules/IntentResolver, Sharesheet (ChooserActivity / ChooserActionFactory image edit action)",
     "weakness": "Wrong user handle for cross-profile activity launch (editor always started as personal profile user regardless of launching profile)",
     "root_cause": "In ChooserActivity.createChooserActionFactory, the ActionActivityStarter passed to ChooserActionFactory implemented safelyStartActivityAsPersonalProfileUser and safelyStartActivityAsPersonalProfileUserWithSharedElementTransition by calling safelyStartActivityAsUser(targetInfo, mProfiles.getPersonalHandle(), ...) (getPersonalProfileUserHandle() on Android 14). ChooserActionFactory's edit action (comment: \"Action bar is user-independent; always start as primary.\") invoked these for the editSharingTarget. As a result, when the sharesheet was launched from a non-personal profile, the image editor target was started in the personal profile rather than in the profile that launched the sharesheet. The bulletin classifies this as High severity information disclosure.",
     "fix_analysis": [
      "ChooserActivity.java:createChooserActionFactory (anonymous ActionActivityStarter): safelyStartActivityAsLaunchingUser now calls safelyStartActivityAsUser(targetInfo, mUserInteractor.getLaunchedAs()) instead of mProfiles.getPersonalHandle(), so the editor is launched as the user that launched the sharesheet.",
      "ChooserActivity.java:createChooserActionFactory: safelyStartActivityAsLaunchingUserWithSharedElementTransition likewise passes mUserInteractor.getLaunchedAs() together with the scene transition ActivityOptions bundle.",
      "ChooserActionFactory.java:ActionActivityStarter interface: methods renamed from safelyStartActivityAsPersonalProfileUser / ...WithSharedElementTransition to safelyStartActivityAsLaunchingUser / safelyStartActivityAsLaunchingUserWithSharedElementTransition; the default implementation and the edit action lambda (around the firstImageView visibility check) are updated to the new names. No logic change in this file beyond the rename.",
      "Per the commit message, this commit is the counterpart of ag/34038177 and covers only the case where the use_preferred_image_editor flag is off or does not exist; the flag-on path is fixed by that separate change, which is not in the material."
     ],
     "completeness": [
      "Covers both launch paths used by the edit action in ChooserActionFactory (plain launch and shared-element-transition launch), since both go through the single ActionActivityStarter implementation in ChooserActivity.",
      "Covers only the flag-off / flag-absent code path for use_preferred_image_editor; the flag-on path is handled by a separate change (ag/34038177) that is not shown, so its presence on a given branch cannot be verified from this material.",
      "The comment in ChooserActionFactory.java ('Action bar is user-independent; always start as primary.') and the Log.d message in ChooserActivity ('safelyStartActivityAsPersonalProfileUser(...)') are left stale; cosmetic only.",
      "No test changes are included in the commit (Test: manual repro plus existing atest IntentResolver-tests-*)."
     ],
     "behavior_changes": [
      "The image editor started from the Sharesheet edit action now runs in the profile/user that launched the sharesheet instead of always in the personal profile.",
      "Interface method names on ChooserActionFactory.ActionActivityStarter changed (safelyStartActivityAsPersonalProfileUser* -> safelyStartActivityAsLaunchingUser*); any other implementor of this interface must be renamed."
     ],
     "preconditions": [
      "Sharesheet (ChooserActivity) launched from a user/profile other than the personal profile, with image content and a configured image editor (mImageEditor) so the Edit action is offered.",
      "User selects the Edit action in the content preview action row (ChooserActionFactory edit action lambda).",
      "use_preferred_image_editor flag off or not present (per commit message; the flag-on path is a separate change)."
     ],
     "per_major": {
      "14": "adapted fix (75f41ba2e89d, android-security-14.0.0_r22): same rename; in this branch ChooserActivity extends ResolverActivity, and the launching user is computed as UserHandle.of(UserHandle.myUserId()) in place of getPersonalProfileUserHandle(); the shared-element path calls startFinishAnimation(). 6/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix (13c30b464d04, android-security-15.0.0_r10): identical to the 16 change, mProfiles.getPersonalHandle() -> mUserInteractor.getLaunchedAs(). 6/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "same fix (9810de0fcb5a, android-security-16.0.0_r1). 6/8 lines of the patched region 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": [
      "On Android 14 style branches (ChooserActivity extending ResolverActivity) Google used UserHandle.of(UserHandle.myUserId()); on 15+ it used mUserInteractor.getLaunchedAs(). Also confirm the companion flag-on change (ag/34038177, use_preferred_image_editor path) is present if that flag exists on the branch."
     ],
     "confidence": "high",
     "unknowns": [
      "Contents of the companion change ag/34038177 (flag-on use_preferred_image_editor path) and whether it is present on each branch.",
      "Exact disclosure consequence beyond what the diff shows (editor launched in personal profile for content shared from another profile); the bulletin only gives type ID, High.",
      "Whether the edit target intent carries URI grant flags.",
      "Why Android 17 is not listed by the bulletin (6/8 lines of the patched region are present there), and which 2 lines differ in each latest tag."
     ],
     "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-48552": {
     "cve": "CVE-2025-48552",
     "summary": "Wraps the three Settings.Global writes in DevicePolicyManagerService.saveGlobalProxyLocked in a try/catch so that an exception thrown by SettingsProvider while applying a device admin's global proxy no longer propagates and breaks the calling operation (for example removal of an ActiveAdmin).",
     "component": "platform/frameworks/base, DevicePolicyManagerService (services/devicepolicy)",
     "weakness": "Improper handling of exceptional conditions: an unhandled exception from a settings write breaks a security-sensitive caller (admin removal)",
     "root_cause": "saveGlobalProxyLocked parses an admin-supplied proxySpec and exclusionList and validates them only through ProxyInfo.isValid(). It then writes GLOBAL_HTTP_PROXY_HOST, GLOBAL_HTTP_PROXY_PORT and GLOBAL_HTTP_PROXY_EXCLUSION_LIST via mInjector.settingsGlobalPutString/PutInt with no exception handling. The context shows it is called from the global proxy reset path, which iterates active admins and re-applies (or clears) the global proxy. Per the commit message, an exception thrown by SettingsProvider during these writes could propagate and break callers that perform potentially security-sensitive operations, such as removing an ActiveAdmin.",
     "fix_analysis": [
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:saveGlobalProxyLocked: the three mInjector.settingsGlobalPutString/settingsGlobalPutInt calls are now inside try { ... } catch (Exception e). The catch logs Slogf.w(LOG_TAG, \"Fail to save proxy\", e) and the method returns normally. Callers (the admin iteration that calls saveGlobalProxyLocked(ap.globalProxySpec, ap.globalProxyExclusionList) and the saveGlobalProxyLocked(null, null) reset) therefore continue even when SettingsProvider fails.",
      "The existing ProxyInfo.isValid() check and its early return with Slogf.e are unchanged. The fix adds a second line of defense for values that pass isValid() but are rejected at write time."
     ],
     "completeness": [
      "Covers only the three Settings.Global writes inside saveGlobalProxyLocked. It catches Exception (not Error) and adds no new validation of the proxy host, port or exclusion list beyond the existing ProxyInfo.isValid() check.",
      "The admin's stored globalProxySpec/globalProxyExclusionList are not sanitized or cleared on failure. The failing write is skipped, not corrected.",
      "The three writes are not atomic: if the second or third write throws, the earlier writes in the same call stay applied, and the fix does not roll them back.",
      "Other places in DevicePolicyManagerService that may call settingsGlobal* with admin-controlled values are not touched by this commit. The material does not show whether they have the same exposure."
     ],
     "behavior_changes": [
      "Exceptions raised by SettingsProvider while saving the global proxy are swallowed and logged at warning level (\"Fail to save proxy\") instead of propagating to the caller.",
      "Operations that trigger a global proxy reset (such as admin removal) now continue even when the proxy settings cannot be saved. In that case the global proxy may be partially updated or not updated at all."
     ],
     "preconditions": [
      "An active device admin whose ActiveAdmin has specifiesGlobalProxy set, with a globalProxySpec/exclusion list that passes ProxyInfo.isValid() but makes SettingsProvider throw when it is written to Settings.Global (from the diff context and commit message).",
      "A code path that runs the global proxy reset, which iterates admins and calls saveGlobalProxyLocked. Per the commit message, this includes removing an ActiveAdmin."
     ],
     "per_major": {
      "14": "Same fix, 58e0b9a15338 in android-security-14.0.0_r22: the same try/catch around the three writes. The removed lines use Settings.Global.* and the added lines use Global.*. Per-major facts report only 1/4 patched-region lines present at android-security-14.0.0_r29.",
      "15": "Same fix, bb6d2f17243d in android-security-15.0.0_r10: the same hunk as on Android 14 (Settings.Global.* replaced by Global.*). Per-major facts report only 1/4 patched-region lines present at android-security-15.0.0_r17.",
      "16": "Same fix, d5fee58e64ae in android-security-16.0.0_r1. The code already used Global.*, so the hunk only adds the try/catch. 4/4 patched-region lines present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). The patched code (try/catch) is present at android-security-17.0.0_r1 (4/4 lines)."
     },
     "backport_notes": [
      "The 14/15 hunks change the qualifier from Settings.Global.* to Global.*. When backporting, make sure Global resolves in the target file, or keep the Settings.Global.* spelling.",
      "Single commit with no other commits in the fix. On trees older than 16, the removed lines use Settings.Global.* rather than Global.*."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which proxy value passes ProxyInfo.isValid() but makes SettingsProvider throw.",
      "The exact call chain from ActiveAdmin removal to saveGlobalProxyLocked is described only in the commit message. The material shows the body of the admin-iterating reset function but not its name or its callers.",
      "What exactly happens to an admin removal when the exception propagates (partial completion, crash, etc.) is not shown.",
      "Whether the skipped write is retried later is not shown.",
      "The material does not explain why the per-major facts report only 1/4 patched-region lines present at android-security-14.0.0_r29 and android-security-15.0.0_r17, although the fix is resolved at earlier tags on those branches (later modification vs. measurement artifact)."
     ],
     "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-48553": {
     "cve": "CVE-2025-48553",
     "summary": "DevicePolicyManagerService.handlePackagesChanged now re-parses the device admin metadata of a changed DPC package (new isActiveAdminPackageValid) and drops the active admin when the package, receiver, or its admin metadata is missing or malformed; DeviceAdminInfo additionally converts OutOfMemoryError during metadata parsing into an XmlPullParserException.",
     "component": "platform/frameworks/base, DevicePolicyManagerService / DeviceAdminInfo (device admin / DPC handling)",
     "weakness": "Missing validation of updated device admin package metadata (stale ActiveAdmin retained after package update); unhandled OutOfMemoryError during metadata parsing",
     "root_cause": "In DevicePolicyManagerService.handlePackagesChanged, when a package changed, the code only verified that the admin package still existed (getPackageInfo != null) and that the receiver component still existed (getReceiverInfo without GET_META_DATA != null). It never re-read or re-validated the device admin metadata of the updated package, so an ActiveAdmin entry, with its previously parsed DeviceAdminInfo, remained registered in policy.mAdminList/mAdminMap even if the updated package no longer carried valid device admin metadata. Separately, the DeviceAdminInfo(Context, ActivityInfo) metadata parsing only translated NameNotFoundException into XmlPullParserException; an OutOfMemoryError raised while parsing propagated as an Error and was not treated as a parse failure.",
     "fix_analysis": [
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java: new @GuardedBy(getLockObject()) helper isActiveAdminPackageValid(ActiveAdmin) performs three checks: mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) != null; mIPackageManager.getReceiverInfo(component, GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle) != null (GET_META_DATA is newly requested so the metadata bundle is populated); and that `new DeviceAdminInfo(mContext, ai)` succeeds, with any Exception logged as 'contains malformed metadata' and treated as invalid. The user id is taken from admin.getUserHandle().getIdentifier().",
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:handlePackagesChanged: the inline existence check is replaced by `if (!isActiveAdminPackageValid(aa))`; on failure the existing removal path runs unchanged (removedAdmin = true, remove from policy.mAdminList and policy.mAdminMap, pushActiveAdminPackagesLocked). The log message now reads 'not found or invalid' and uses Slogf format args instead of String.format.",
      "core/java/android/app/admin/DeviceAdminInfo.java: in the metadata parsing constructor, a new `catch (OutOfMemoryError e)` rethrows as XmlPullParserException(\"Out of memory when parsing\", null, e). Because isActiveAdminPackageValid catches Exception (not Error), this conversion is what makes an OOM during parsing count as 'malformed metadata' and lead to admin removal instead of an uncaught Error in system_server."
     ],
     "completeness": [
      "Covers only the handlePackagesChanged path; the validity check is applied to admins whose package matches packageName, or to all admins when packageName is null.",
      "Validation consists of a successful DeviceAdminInfo construction; the fix does not compare the newly parsed DeviceAdminInfo (e.g. used policies, headless mode, transfer-ownership support) with the stored aa.info, and the stored aa.info is not refreshed in this code when parsing succeeds.",
      "Other places that construct DeviceAdminInfo or load admins (boot-time policy loading, setActiveAdmin) are not touched by this commit and are not shown in the material.",
      "The OutOfMemoryError catch in DeviceAdminInfo only wraps the metadata parsing try block; isActiveAdminPackageValid catches Exception, so other Errors thrown during parsing would still propagate."
     ],
     "behavior_changes": [
      "An active device admin whose updated package has missing or unparsable device admin metadata is now removed from the active admin list on package change (previously it was kept).",
      "getReceiverInfo in this path now requests GET_META_DATA.",
      "The user id used for lookups is admin.getUserHandle().getIdentifier() rather than the handlePackagesChanged userHandle argument.",
      "OutOfMemoryError during DeviceAdminInfo metadata parsing surfaces as XmlPullParserException to all callers of that constructor, not only the DPMS path.",
      "New error log lines: '<pkg> no longer installed', '<pkg> no longer has the receiver', '<pkg> contains malformed metadata', and 'Admin package %s not found or invalid for user %d, removing active admin'."
     ],
     "preconditions": [
      "A package that is registered as an active device admin (present in DevicePolicyData.mAdminList) for some user.",
      "A package change event for that package (or a null-package rescan) reaching DevicePolicyManagerService.handlePackagesChanged, e.g. an update of the DPC package."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 0f9ca51bfd42 in android-security-14.0.0_r22; identical logic, only the removed lines used PackageManager.-qualified MATCH_DIRECT_BOOT_* constants; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 660c7075dc00 in android-security-15.0.0_r10; identical logic with PackageManager.-qualified constants in the removed lines; 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c0978bc04a25 in android-security-16.0.0_r1 (cherry-pick of a25dd078cf5e, Change-Id I1743c111f8e8b5f4c1f878a61b88b8f1ed6b86a1); 8/8 patched lines present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; patched region fully present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit touching two files. Both hunks belong together: the DPMS helper only catches Exception, so OOM during parsing is handled only if DeviceAdminInfo converts OutOfMemoryError to XmlPullParserException.",
      "The added code uses unqualified GET_META_DATA, MATCH_DIRECT_BOOT_AWARE and MATCH_DIRECT_BOOT_UNAWARE, and none of the 14/15/16 commits change imports. Forks should check that these static imports resolve in their DevicePolicyManagerService."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete EoP consequence of keeping a stale ActiveAdmin after an update with missing or malformed metadata is not described in the material.",
      "Whether bugs 384514657 and 414603411 cover two distinct issues (stale admin vs. OOM during parsing) is not shown.",
      "Whether other DeviceAdminInfo construction sites in DPMS were changed by related commits is not shown."
     ],
     "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-2025-48554": {
     "cve": "CVE-2025-48554",
     "summary": "DevicePolicyManagerService.handlePackagesChanged now re-validates an active admin's package on package change by fetching the receiver with GET_META_DATA and constructing a DeviceAdminInfo from it, removing the admin if the metadata is missing or malformed; DeviceAdminInfo's XML parsing additionally converts OutOfMemoryError into XmlPullParserException.",
     "component": "platform/frameworks/base, DevicePolicyManagerService / DeviceAdminInfo (device admin metadata handling)",
     "weakness": "Missing validation of updated device admin metadata (improper input validation leading to DoS); unhandled OutOfMemoryError during metadata parsing",
     "root_cause": "In DevicePolicyManagerService.handlePackagesChanged, when a package changed, the only checks on an ActiveAdmin were that mIPackageManager.getPackageInfo returned non-null and that mIPackageManager.getReceiverInfo (without GET_META_DATA) returned non-null. The admin receiver's device-admin metadata was not re-parsed, so an active admin whose package was updated with invalid or malformed metadata stayed in policy.mAdminList/mAdminMap. In DeviceAdminInfo's constructor, the shown catch clauses of the XML metadata parse block handled only NameNotFoundException; an OutOfMemoryError raised while parsing the metadata propagated as an Error rather than a checked parse failure. The bulletin rates the result as a High severity DoS; the exact crash site is not shown in the material.",
     "fix_analysis": [
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:isActiveAdminPackageValid (new, @GuardedBy getLockObject()): checks getPackageInfo(adminPackage, 0, userHandle) != null, then getReceiverInfo(admin.info.getComponent(), GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle) != null, then attempts new DeviceAdminInfo(mContext, ai) and returns false on any Exception (logged as 'contains malformed metadata'). userHandle is taken from admin.getUserHandle().getIdentifier().",
      "services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:handlePackagesChanged: replaces the inline existence check with !isActiveAdminPackageValid(aa); on failure the admin is removed from policy.mAdminList and policy.mAdminMap and pushActiveAdminPackagesLocked(userHandle) is called, same as the previous not-found path. Log message changed to 'not found or invalid'.",
      "core/java/android/app/admin/DeviceAdminInfo.java (constructor parsing metadata): adds catch (OutOfMemoryError e) that rethrows as XmlPullParserException(\"Out of memory when parsing\", null, e), so an OOM while parsing metadata becomes a checked exception that callers such as isActiveAdminPackageValid (which catches Exception, not Error) can handle."
     ],
     "completeness": [
      "Covers the package-change path: any active admin whose package changes (or all admins when packageName is null) is re-validated by fully parsing its metadata, and invalid admins are dropped.",
      "The OutOfMemoryError conversion applies only to the metadata parse try block in the DeviceAdminInfo constructor; other Error subclasses are not converted, and isActiveAdminPackageValid catches Exception only.",
      "Other sites that construct DeviceAdminInfo (e.g. admin activation or loading persisted admins at startup) are not touched by this commit; the material does not show whether they needed changes.",
      "The new check queries receiver info for admin.getUserHandle() rather than the handlePackagesChanged userHandle argument; the material does not show whether these always match."
     ],
     "behavior_changes": [
      "On package change, an active admin whose receiver metadata fails to parse is now removed as an active admin (previously it remained active as long as package and receiver existed).",
      "getReceiverInfo in this path now requests GET_META_DATA, and a DeviceAdminInfo is constructed per matching admin on every package change event (extra parsing work).",
      "DeviceAdminInfo construction now throws XmlPullParserException instead of OutOfMemoryError when the parser runs out of memory.",
      "New error log lines: '<pkg> no longer installed', '<pkg> no longer has the receiver', '<pkg> contains malformed metadata', and 'Admin package %s not found or invalid for user %d, removing active admin'."
     ],
     "preconditions": [
      "A package that is an active device admin (present in DevicePolicyData.mAdminList for the user) is updated or otherwise triggers handlePackagesChanged for that package or for all packages (packageName == null).",
      "The updated package's admin receiver metadata is malformed or unparseable such that DeviceAdminInfo construction fails (the exact malformed content is not shown in the material)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 0f9ca51bfd42 in android-security-14.0.0_r22, identical new code; removed lines differ only in using fully qualified PackageManager.MATCH_DIRECT_BOOT_* constants. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 660c7075dc00 in android-security-15.0.0_r10, identical new code with the same PackageManager.-qualified removed lines. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base c0978bc04a25 in android-security-16.0.0_r1 (cherry-pick of a25dd078cf5e). 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": [
      "Both files must go together: isActiveAdminPackageValid catches Exception, so without the DeviceAdminInfo OutOfMemoryError-to-XmlPullParserException conversion an OOM during the new parse would still escape the check.",
      "The new code uses unqualified GET_META_DATA / MATCH_DIRECT_BOOT_AWARE / MATCH_DIRECT_BOOT_UNAWARE and ActivityInfo; on branches where the removed lines used PackageManager.-qualified names (as in the 14 and 15 diffs), confirm the static imports and ActivityInfo import exist in DevicePolicyManagerService."
     ],
     "confidence": "high",
     "unknowns": [
      "Where the DoS manifested (which code path consumed the invalid admin metadata and failed) is not shown in the material.",
      "What specific malformed metadata triggered the failure (bug 384514657 / 414603411 contents are not provided).",
      "Whether other DeviceAdminInfo construction sites were changed in separate commits.",
      "Whether admin.getUserHandle() always equals the userHandle passed to handlePackagesChanged."
     ],
     "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-48556": {
     "cve": "CVE-2025-48556",
     "summary": "NotificationChannel.setId() and setConversationId() now pass their string arguments through getTrimmedString(), which caps the channel id, parent channel id and conversation id at MAX_TEXT_LENGTH. Before the fix these values were stored without any length limit.",
     "component": "platform/frameworks/base, core/java/android/app/NotificationChannel (notification channels)",
     "weakness": "Missing length bound on externally supplied strings (improper input validation)",
     "root_cause": "NotificationChannel already has a getTrimmedString() helper that caps strings at MAX_TEXT_LENGTH. An existing test asserts that id, name, description, parent id, group and conversation id are trimmed after a Parcel round trip. However, the setters setId(String) and setConversationId(String, String) assigned mId, mParentId and mConversationId directly. A caller using these setters could therefore store arbitrarily long strings in the channel object and bypass the length cap applied on the Parcel path.",
     "fix_analysis": [
      "core/java/android/app/NotificationChannel.java:setId: replaces `mId = id;` with `mId = getTrimmedString(id);`, so an id longer than MAX_TEXT_LENGTH is cut to that length. Per getTrimmedString, null passes through unchanged.",
      "core/java/android/app/NotificationChannel.java:setConversationId: replaces the direct assignments of mParentId and mConversationId with `getTrimmedString(parentChannelId)` and `getTrimmedString(conversationId)`.",
      "core/tests/coretests/src/android/app/NotificationChannelTest.java: adds testSetId_longStringIsTrimmed and testSetConversationId_longStringsAreTrimmed. They assert that the stored values have length MAX_TEXT_LENGTH and equal the prefix of the input."
     ],
     "completeness": [
      "Covers the three fields written by setId and setConversationId (mId, mParentId, mConversationId). These now match the trimming already applied on the Parcel read path.",
      "Reuses the existing getTrimmedString helper and MAX_TEXT_LENGTH constant. No other fields change.",
      "setGroup(String) in the same class (Android 16 context, line 655) still assigns `this.mGroup = groupId;` without trimming, while the existing Parcel test expects getGroup() to be capped. The material does not show whether other setters in the class trim."
     ],
     "behavior_changes": [
      "Channel ids, parent channel ids and conversation ids longer than MAX_TEXT_LENGTH that are set through setId or setConversationId are now silently truncated to their first MAX_TEXT_LENGTH characters. No exception or error is raised."
     ],
     "preconditions": [
      "A NotificationChannel whose id, parent channel id or conversation id is set through setId (marked @hide) or setConversationId with a string longer than MAX_TEXT_LENGTH.",
      "The material does not show how these setters are reached in the attack scenario or what the resulting EoP effect is. The bulletin rates the issue High severity EoP."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. The resolver reports 7/8 lines of the patched region present at android-security-14.0.0_r29. No commit for 14 is in the material.",
      "15": "Same fix: platform/frameworks/base fc5d9b6ef530 in android-security-15.0.0_r10. The code change is identical, at different line offsets (hunks at 581 and 818). 7/8 lines present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/frameworks/base 0da1ac9dda56 in android-security-16.0.0_r1 (cherry-pick of a9a1b4365b8d). 7/8 lines present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin. 6/8 lines of the patched region present at android-security-17.0.0_r1. No commit is shown, and whether the trimming is present is not shown."
     },
     "backport_notes": [
      "Single self-contained commit. Its only dependencies are the private getTrimmedString(String) helper and the MAX_TEXT_LENGTH constant already in NotificationChannel.java, which must exist in the target tree. The new tests use Strings.repeat, which the existing test file already uses, and Truth assertThat, which must be available in the test file."
     ],
     "confidence": "high",
     "unknowns": [
      "How oversized ids reach the setters in the attack scenario, and the concrete EoP impact (for example which system component mishandles the long string), are not shown.",
      "Whether a separate commit trims setGroup or other setters is not shown.",
      "Whether Android 14 and 17 contain equivalent trimming through a different commit is not shown. Only the resolver line counts are available.",
      "The API visibility of setConversationId is not shown in the material."
     ],
     "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-48558": {
     "cve": "CVE-2025-48558",
     "summary": "BatteryService now resolves the ACTION_REQUEST_SHUTDOWN intent itself and pins it to an activity whose uid holds android.permission.SHUTDOWN before launching it, instead of sending an implicit intent that any matching activity could receive.",
     "component": "platform/frameworks/base, services/core BatteryService (system_server low-battery / over-temperature shutdown path)",
     "weakness": "Implicit intent sent from a privileged (system) context without checking the target component (missing permission/target check on the receiving activity).",
     "root_cause": "In services/core/java/com/android/server/BatteryService.java, the low-battery shutdown path (the shouldShutdownLocked() branch) and shutdownIfOverTempLocked() each posted a Runnable. Once isSystemReady() was true, that Runnable built an implicit Intent(Intent.ACTION_REQUEST_SHUTDOWN) with EXTRA_KEY_CONFIRM=false and an EXTRA_REASON, set FLAG_ACTIVITY_NEW_TASK, and called mContext.startActivityAsUser(intent, UserHandle.CURRENT). No component was set and nothing checked which activity resolves the action. As a result, an activity from a package without android.permission.SHUTDOWN that declares a matching intent filter could be the one the system launches when the battery is low or too hot.",
     "fix_analysis": [
      "services/core/java/com/android/server/BatteryService.java: the low-battery path (shouldShutdownLocked() branch) and shutdownIfOverTempLocked() now build the intent at the call site (same action, extras and flag as before) and post a lambda that calls the new private method startShutdownActivity(intent).",
      "BatteryService.startShutdownActivity(Intent): returns early if !mActivityManagerInternal.isSystemReady() (keeping the old gate). It gets PackageManagerInternal via LocalServices.getService and returns early if that is null.",
      "startShutdownActivity: calls pmi.queryIntentActivities(intent, null, 0, Process.myUid(), mActivityManagerInternal.getCurrentUserId()) and walks the ResolveInfo list. Entries with a null activityInfo or applicationInfo are skipped. Entries whose applicationInfo.uid does not hold android.Manifest.permission.SHUTDOWN (per mContext.checkPermission(..., 0, uid)) are skipped and logged with Slog.w. The first entry that passes has its component set via intent.setComponent(ri.activityInfo.getComponentName()), and the loop stops.",
      "Finally mContext.startActivityAsUser(intent, UserHandle.CURRENT) is called. The intent is now explicit when a SHUTDOWN-permitted activity was found. New imports: PackageManager, PackageManagerInternal, ResolveInfo, Process, List."
     ],
     "completeness": [
      "Both launch sites in BatteryService (low battery via shouldShutdownLocked(), over-temperature via mHealthInfo.batteryTemperatureTenthsCelsius > mShutdownBatteryTemperature) now go through the single new startShutdownActivity method, so both are covered.",
      "Resolution uses the resolve list for mActivityManagerInternal.getCurrentUserId(), while the launch uses UserHandle.CURRENT.",
      "Gap visible in the code: if the resolve list is null or empty, or no entry's uid holds SHUTDOWN, the intent stays implicit and startActivityAsUser is still called unconditionally. So the fix relies on a SHUTDOWN-permitted activity for this action being present on the device.",
      "The permission check calls checkPermission with pid 0 and the resolved app's uid. It checks the permission of the uid hosting the activity, not whether the activity itself is protected by a permission attribute.",
      "Only BatteryService is changed. The material does not show whether other code sends ACTION_REQUEST_SHUTDOWN."
     ],
     "behavior_changes": [
      "The shutdown request intent is now explicit (component set) whenever a matching SHUTDOWN-permitted activity exists.",
      "New Slog.w log line 'Shutdown activity <component> does not have permission android.permission.SHUTDOWN' for each matching activity whose uid lacks the permission.",
      "If PackageManagerInternal is not available from LocalServices, no activity is launched at all (before the fix, the launch went ahead).",
      "The Intent is now built at the call site before mHandler.post and captured by the posted lambda, instead of being built inside the handler Runnable. The isSystemReady() gate works the same way as before."
     ],
     "preconditions": [
      "The device enters the low-battery shutdown condition (shouldShutdownLocked() returns true) or the battery temperature exceeds mShutdownBatteryTemperature. The commit's test line, 'adb shell dumpsys battery set temp 1001', shows how to trigger the over-temperature branch.",
      "mActivityManagerInternal.isSystemReady() is true.",
      "Before the fix, a package declared an activity with an intent filter matching Intent.ACTION_REQUEST_SHUTDOWN, so the implicit intent could resolve to it. After the fix, such an activity is skipped unless its uid holds android.permission.SHUTDOWN."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base e22e656b5e88 in android-security-14.0.0_r22, with the same startShutdownActivity logic and call-site changes; only the surrounding import context lines differ. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 0d30c78c8953 in android-security-15.0.0_r10, same diff body. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base b1c60eeadb2c in android-security-16.0.0_r1 (cherry-picked from internal commits 8cda6fda / 9ddccd08). 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": [
      "Single-commit, single-file change applied the same way on 14, 15 and 16; only the import block context differs per branch. The new code calls PackageManagerInternal.queryIntentActivities(Intent, String, int, int, int), LocalServices and ActivityManagerInternal.getCurrentUserId()/isSystemReady(), so a fork must have these available.",
      "Android 13 is listed as affected by the bulletin, but the material includes no 13 commit or 13 code."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the Android 13 fix commit, although the bulletin lists 13 as affected.",
      "The material does not show which activity on a stock build handles ACTION_REQUEST_SHUTDOWN with a uid holding android.permission.SHUTDOWN, nor what happens on a device that has no such activity (in that case the code still sends the implicit intent).",
      "Whether getCurrentUserId() and UserHandle.CURRENT always refer to the same user at launch time is not shown.",
      "Whether any other system component sends ACTION_REQUEST_SHUTDOWN implicitly is not shown in 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-2025-48559": {
     "cve": "CVE-2025-48559",
     "summary": "AppOpsService.verifyAndGetBypass no longer marks an attribution tag as valid for a ROOT_UID proxied op unless the proxy is null or a system package, and finishProxyOperation now passes the proxy UID/package through finishOperationUnchecked so the same proxy check applies when finishing ops for root and the other special proxied UIDs.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop/AppOpsService.java)",
     "weakness": "Missing validation of caller-supplied attribution tags for a privileged proxied UID, allowing unbounded creation of attribution entries (resource exhaustion / DoS)",
     "root_cause": "In AppOpsService.verifyAndGetBypass, the uid == Process.ROOT_UID branch always returned PackageVerificationResult with isAttributionTagValid = true, for backwards compatibility. The other special-UID branch (shell, system server, etc.) already treated the tag as valid only when the proxy was null or a system app, but root had no such check. A non-system proxy app issuing proxy ops on behalf of root could therefore have arbitrary, unregistered attribution tags treated as valid. The commit comment calls the result \"clogging the appops system with unlimited attribution tags via proxy calls\". Separately, finishProxyOperation called finishOperationUnchecked, which called verifyAndGetBypass(uid, packageName, attributionTag) with no proxy information. Per the commit message, this meant a non-system proxy finishing an op for one of the special proxied apps had its attribution tag wrongly treated as valid.",
     "fix_analysis": [
      "AppOpsService.java:verifyAndGetBypass (form with proxyUid/proxyPackageName): in the uid == Process.ROOT_UID branch, isAttributionTagValid changes from the constant true to isPackageNullOrSystem(proxyPackageName, proxyUid). In finishOperationUnchecked, isAttributionTagValid == false causes attributionTag to be set to null.",
      "AppOpsService.java:verifyAndGetBypass: the existing special-UID branch (RestrictionBypass.UNRESTRICTED) replaces its inline proxyIsSystemAppOrNull computation with the same isPackageNullOrSystem(proxyPackageName, proxyUid) helper.",
      "AppOpsService.java:isPackageNullOrSystem (new): returns true if packageName is null; returns true if UserHandle.getAppId(uid) is > 0 and < Process.FIRST_APPLICATION_UID; otherwise returns mPackageManagerInternal.isSystemPackage(packageName). The old inline code accepted any appId below FIRST_APPLICATION_UID automatically. Now a non-null package with appId 0 (root) or a negative appId falls through to the isSystemPackage check.",
      "AppOpsService.java:finishOperationUnchecked: a new overload takes (clientId, code, proxyUid, proxyPackageName, proxiedUid, proxiedPackageName, attributionTag, virtualDeviceId) and calls verifyAndGetBypass(proxiedUid, proxiedPackageName, attributionTag, proxyUid, proxyPackageName). The old signature is kept as a thin wrapper that passes proxyUid = -1 and proxyPackageName = null. getOpLocked and the error logs now use proxiedUid/proxiedPackageName.",
      "AppOpsService.java:finishProxyOperation: now calls the new overload with proxyUid and resolvedProxyPackageName in addition to proxiedUid/resolvedProxiedPackageName, so the finish path applies the same proxy-based tag validation."
     ],
     "completeness": [
      "Covers the ROOT_UID branch and the special-UID (shell/system server etc.) branch of verifyAndGetBypass through the shared isPackageNullOrSystem helper, and extends proxy-aware validation to the finishProxyOperation path.",
      "Non-proxy callers of finishOperationUnchecked keep the old signature and pass -1/null for the proxy. The helper treats this as a 'null proxy' (valid), so direct (non-proxy) operations behave as before.",
      "The remaining branches of verifyAndGetBypass (SDK sandbox UIDs, regular app UIDs) are not shown in the diff. The material does not show whether they already took proxy information into account before this change.",
      "The material does not show whether the other callers of verifyAndGetBypass (e.g. the note/start paths for proxy ops) already passed proxy information. Only finishProxyOperation is changed here."
     ],
     "behavior_changes": [
      "For proxy ops where the proxied UID is root and the proxy is a non-system app, the attribution tag is now treated as invalid and replaced with null.",
      "When a non-system proxy calls finishProxyOperation for root or another special proxied UID, the proxy is now taken into account and the attribution tag is nulled if invalid. Previously, per the commit message, the tag was erroneously treated as valid.",
      "A proxy whose UID has appId 0 (root) and a non-null package name is now validated via PackageManagerInternal.isSystemPackage instead of being accepted unconditionally.",
      "Error logs in finishOperationUnchecked now report the proxied uid/package explicitly (text unchanged otherwise)."
     ],
     "preconditions": [
      "A caller (per the commit message, a malicious non-system proxy app) that can invoke AppOpsService proxy operations (e.g. finishProxyOperation) with a proxied UID of Process.ROOT_UID or another special UID handled by verifyAndGetBypass.",
      "For the new check to reject the tag, the proxy package must be non-null and not a system package, and its appId must not be in the range 1 to FIRST_APPLICATION_UID-1.",
      "The material does not show the exact permission checks guarding the proxy entry points."
     ],
     "per_major": {
      "14": "Adapted fix: commit 1c6779245598 (RESTRICT AUTOMERGE) in android-security-14.0.0_r22. Same logic, but finishOperationUnchecked has no virtualDeviceId parameter, uses op.mAttributions.get(attributionTag), and logs SecurityException with inline Slog.e instead of logVerifyAndGetBypassFailure. At android-security-14.0.0_r29, 5/8 lines of the patched region present.",
      "15": "Same fix: commit 7b88db4928f3 in android-security-15.0.0_r10. It matches the 16 change except that it uses getPersistentId(virtualDeviceId) instead of getPersistentDeviceIdForOp(virtualDeviceId, code). At android-security-15.0.0_r17, 4/8 lines of the patched region present.",
      "16": "Same fix: commit f745b6763bff in android-security-16.0.0_r1 (cherry-pick, Change-Id I9b44465554e10b803bc9b4ab76130aaf9933f605). At android-security-16.0.0_r8, 5/8 lines of the patched region present.",
      "17": "Not affected per the bulletin (Android 17 is not listed). At android-security-17.0.0_r1, 5/8 lines of the patched region present."
     },
     "backport_notes": [
      "Single-commit, single-file change. The only version-specific differences are: the finishOperationUnchecked signature (virtualDeviceId parameter present on 15/16, absent on 14); the attributed-op lookup (op.mAttributions vs op.mDeviceAttributedOps with getPersistentId / getPersistentDeviceIdForOp); and the SecurityException logging helper. The change requires mPackageManagerInternal.isSystemPackage, which the pre-existing special-UID branch already uses.",
      "Keep the old-signature finishOperationUnchecked wrapper (which passes proxyUid -1 and proxyPackageName null) so non-proxy callers are unchanged. Only finishProxyOperation switches to the new overload."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the permission checks on the proxy op entry points, or which callers can reach them with proxiedUid == ROOT_UID.",
      "The material does not include the remaining branches of verifyAndGetBypass, how the shorter overload forwards proxy parameters, or the start/note proxy paths.",
      "The 'Code around the fix' excerpt shows setMode, not the patched functions, so the only evidence for the state of finishOperationUnchecked/verifyAndGetBypass at android-security-16.0.0_r8 is the per-major line counts.",
      "The commit message and the AppOpsMemoryUsageTest reference imply the practical impact (memory growth in system_server), but the material does not demonstrate it."
     ],
     "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-48560": {
     "cve": "CVE-2025-48560",
     "summary": "Moves the ACCESSIBILITY_MOTION_EVENT_OBSERVING permission check in AbstractAccessibilityServiceConnection out of code that runs under Binder.clearCallingIdentity() and into the service-info update Binder method, before identity is cleared. The check is now evaluated against the calling accessibility service rather than system_server, and the requested observed motion event sources are set to zero when the caller lacks the permission.",
     "component": "platform/frameworks/base, services/accessibility (AbstractAccessibilityServiceConnection, accessibility service info update path)",
     "weakness": "Permission check performed under the wrong identity: it ran within a Binder.clearCallingIdentity() region, so it used system_server's permissions instead of the caller's. In effect, a missing authorization check.",
     "root_cause": "In services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java, the check for android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING sat in the method that applies properties from an AccessibilityServiceInfo (it sets mNotificationTimeout, mIsDefault, mGenericMotionEventSources and mObservedMotionEventSources). The commit message says the check ran inside a Binder clear-identity call, so mContext.checkCallingOrSelfPermission(...) used system_server's permissions instead of those of the calling AccessibilityService. When Flags.motionEventObserving() was enabled, a caller's observedMotionEventSources could therefore be applied without the caller holding the permission.",
     "fix_analysis": [
      "AbstractAccessibilityServiceConnection.java, property-applying method (first hunk, the method that assigns mGenericMotionEventSources): removes the Flags.motionEventObserving() gate and the checkCallingOrSelfPermission(ACCESSIBILITY_MOTION_EVENT_OBSERVING) check. The method now simply does mObservedMotionEventSources = info.getObservedMotionEventSources().",
      "AbstractAccessibilityServiceConnection.java, service-info update Binder method (second hunk, the method that throws IllegalStateException 'Cannot update service info: size is larger than safe parcelable limits.' and then calls Binder.clearCallingIdentity()): before Binder.clearCallingIdentity(), adds a check. If info.getObservedMotionEventSources() != 0 and mContext.checkCallingPermission(ACCESSIBILITY_MOTION_EVENT_OBSERVING) != PERMISSION_GRANTED, it logs Slog.e and calls info.setObservedMotionEventSources(0).",
      "Switches from checkCallingOrSelfPermission to checkCallingPermission, and adds a static import of ACCESSIBILITY_MOTION_EVENT_OBSERVING."
     ],
     "completeness": [
      "Covers the service-info update Binder path: the requested observed sources are checked against the real caller's permission before identity is cleared.",
      "The check now exists only in that Binder method; the property-applying method no longer checks anything. Any other caller of that method would apply info.getObservedMotionEventSources() without a check. The material does not show whether such callers exist, so this cannot be judged from the code provided.",
      "The Flags.motionEventObserving() gate on assigning mObservedMotionEventSources is removed, so the field is always assigned from the info and the new check applies regardless of the flag's state.",
      "Only observedMotionEventSources is checked. mGenericMotionEventSources (info.getMotionEventSources()) is not touched by the fix and was not permission-gated before either."
     ],
     "behavior_changes": [
      "A caller of the service-info update method that passes a non-zero observedMotionEventSources without ACCESSIBILITY_MOTION_EVENT_OBSERVING now has that field reset to 0 in the passed-in AccessibilityServiceInfo object, and a Slog.e is logged. No exception is thrown.",
      "checkCallingPermission replaces checkCallingOrSelfPermission, so an invocation that is not part of an IPC is treated as lacking the permission (Context.checkCallingPermission denies when not processing an IPC, general platform knowledge).",
      "mObservedMotionEventSources is now assigned from the info even when Flags.motionEventObserving() is disabled. Before the fix, it was not assigned in that case.",
      "The Slog.e message text changed slightly."
     ],
     "preconditions": [
      "The caller is a bound accessibility service that reaches the service-info update method of AbstractAccessibilityServiceConnection.",
      "The caller supplies an AccessibilityServiceInfo with observedMotionEventSources != 0 while not holding android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING.",
      "Before the fix, android.view.accessibility.Flags.motionEventObserving() had to be enabled for mObservedMotionEventSources to be applied."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). The resolver reports 7/8 lines of the patched region present at android-security-14.0.0_r29. The material does not show whether the vulnerable check exists in 14.",
      "15": "Same fix: platform/frameworks/base 580118ec5b41 in android-security-15.0.0_r10. The diff is identical to the 16 commit except for line offsets (hunks at 405 and 516 instead of 420 and 531). The resolver reports 7/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/frameworks/base ff8b0d46a53a in android-security-16.0.0_r1 (a cherry-pick, Change-Id If64838388fa31bdc9abb0896d4011bfef8501a7c). The resolver reports 7/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). The resolver reports 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit fix confined to AbstractAccessibilityServiceConnection.java. The same diff was applied on 15 and 16 at different line offsets. It depends on AccessibilityServiceInfo.getObservedMotionEventSources()/setObservedMotionEventSources() and on the existing Flags.motionEventObserving() block."
     ],
     "confidence": "medium",
     "unknowns": [
      "The diff context does not name the two methods containing the hunks.",
      "Whether the property-applying method has other callers (for example initial binding from the parsed service info) that now apply observedMotionEventSources without any check.",
      "What happens to the checked info after Binder.clearCallingIdentity() (not shown in the diff).",
      "Which motion event data can be observed, and from which sources (the bulletin only classifies this as ID, High).",
      "Whether Flags.motionEventObserving() is enabled by default on the affected releases.",
      "Whether the vulnerable code is present on 14 and 17, which the bulletin does not list."
     ],
     "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-48562": {
     "cve": "CVE-2025-48562",
     "summary": "PrintSpooler's RemotePrintDocument.writeContent now opens the destination URI with mode \"wt\", so the output stream is truncated before the generated PDF is written. Previously it used the openOutputStream(uri) overload, which passes no mode.",
     "component": "platform/frameworks/base, packages/PrintSpooler (print \"Save as PDF\" path)",
     "weakness": "Incomplete overwrite of an existing file (output stream opened without requesting truncation), leading to residual data exposure",
     "root_cause": "In packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java, writeContent(ContentResolver, Uri) copies the rendered document file into the destination the user picked, using contentResolver.openOutputStream(uri). That call does not request truncation. When the user overwrites an existing file, the old content may not be cleared first. If the new PDF is shorter, old bytes past the new end could stay in the destination file (this is inferred from the fix). The commit message states the intent: \"request a truncated output stream in case the user selects to overwrite an existing file\".",
     "fix_analysis": [
      "RemotePrintDocument.java:writeContent: the single call contentResolver.openOutputStream(uri) is replaced by contentResolver.openOutputStream(uri, \"wt\"). This explicitly asks for write-with-truncate mode, so the destination is emptied before the 8192-byte copy loop writes the new PDF content.",
      "No other logic changes. The copy loop, the exception handling (IOException logged as \"Error writing document content.\") and the finally block (closeQuietly on in/out, fileProvider.releaseFile()) are unchanged."
     ],
     "completeness": [
      "The fix covers the single openOutputStream call in RemotePrintDocument.writeContent. The whole patch is a one-line mode change.",
      "The fix depends on the target ContentProvider honouring the \"wt\" mode. The material shows no fallback or check (for example, verifying the resulting file length) for a provider that ignores the mode.",
      "A failure to open or write the stream is still only logged, not reported to the caller. The fix does not change this."
     ],
     "behavior_changes": [
      "Destination files chosen for Save as PDF are now opened with truncation requested, so an overwritten file should end up the size of the new PDF."
     ],
     "preconditions": [
      "The user prints to the Save as PDF destination in PrintSpooler and selects an existing file to overwrite.",
      "Residual data would only remain if the new PDF is shorter than the existing file and the provider's default write mode does not truncate (inferred, not shown in the material)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base a52780f0ce9e in android-security-14.0.0_r22. It is the identical one-line change (openOutputStream(uri) -> openOutputStream(uri, \"wt\")) in the same hunk (@@ -376). At android-security-14.0.0_r29, 4/5 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/base 252efec89b01 in android-security-15.0.0_r10, the identical one-line change in the same hunk. At android-security-15.0.0_r17, 4/5 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/base 177eda822164 in android-security-16.0.0_r1 (cherry-pick, Change-Id Id598cd1a9d2456566f3905432eb7b5ffeab9d33e). The code at android-security-16.0.0_r8 shows openOutputStream(uri, \"wt\") at line 379.",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 4/5 lines of the patched region are present."
     },
     "backport_notes": [
      "This is a single self-contained one-line change with no dependencies. The same hunk is used on 14, 15 and 16, and 14 and 15 share the same pre-image blob (bfc00bb8b94d). On a customized PrintSpooler, find the openOutputStream call in RemotePrintDocument.writeContent and add the \"wt\" mode argument."
     ],
     "confidence": "high",
     "unknowns": [
      "Which content providers' default write mode actually left old trailing data behind (the material does not identify any).",
      "How providers that do not support \"wt\" behave with the new call (exception type, whether the write fails).",
      "Whether Android 13 received a matching patch. The bulletin lists 13 as affected, but no 13 commit is in the material.",
      "Which line differs in the \"4/5 lines present\" per-major check, and whether the fixed line itself is present in the latest 14, 15 and 17 tags."
     ],
     "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-48563": {
     "cve": "CVE-2025-48563",
     "summary": "Adds an onNullBinding() override to RemoteFillService that calls unbind(), so system_server no longer stays bound (with BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) to an autofill service whose onBind() returns null.",
     "component": "platform/frameworks/base, services/autofill (RemoteFillService, system_server autofill service connection)",
     "weakness": "Missing handling of a null service binding (unhandled ServiceConnection.onNullBinding callback), leaving a privileged binding in place",
     "root_cause": "RemoteFillService extends ServiceConnector.Impl<IAutoFillService>. Its constructor binds the autofill service with Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, plus BIND_ALLOW_INSTANT when permitted. It did not override ServiceConnection.onNullBinding(), so when the bound autofill service returned null from onBind(), nothing unbound the connection. According to the commit message, system_server therefore stayed bound to the service, and the service could launch activities from the background.",
     "fix_analysis": [
      "services/autofill/java/com/android/server/autofill/RemoteFillService.java:onNullBinding(ComponentName) is added as an @Override of ServiceConnection. It logs \"onNullBinding\" when sVerbose is set and calls unbind(), which drops the binding to the autofill service when the framework reports a null binding.",
      "Single-commit fix; no other files or callers changed. The existing onServiceConnectionStatusChanged, getAutoDisconnectTimeoutMs (TIMEOUT_IDLE_BIND_MILLIS) and addLast behavior are untouched."
     ],
     "completeness": [
      "Covers only the null-binding path of RemoteFillService: the connection is released when onBind() returns null. The bind flags (BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) are unchanged, so a service that returns a real binder is still bound with that flag.",
      "The fix lives in the RemoteFillService subclass, not in the shared ServiceConnector.Impl base class. The material does not show whether ServiceConnector.Impl or other ServiceConnector.Impl subclasses in system_server handle onNullBinding, so coverage of other remote-service connections cannot be judged from this material.",
      "No test file is included in the diff; the commit message only references an STS host test (Bug_401545800)."
     ],
     "behavior_changes": [
      "When the configured autofill service returns null from onBind(), system_server now unbinds instead of keeping the connection. Per the commit message, the service can then no longer launch activities from the background.",
      "A verbose log line \"onNullBinding\" is emitted under TAG when sVerbose is enabled.",
      "After unbind(), pending autofill jobs for a null-binding service presumably go unserviced. How queued jobs fail depends on ServiceConnector.Impl.unbind(), which the material does not show."
     ],
     "preconditions": [
      "system_server must create a RemoteFillService for the app's component, which requires the app to be the configured autofill service (the constructor also compares the component against credentialAutofillService).",
      "The autofill service's onBind() must return null, so that the framework delivers ServiceConnection.onNullBinding() to RemoteFillService.",
      "system_server must attempt the bind. The bind flags are set in the RemoteFillService constructor; the material does not show exactly when binding is triggered."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 1f704bff05f0 in android-security-14.0.0_r22 adds the identical onNullBinding() override to RemoteFillService.java (hunk at line 94); 2/2 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base a6a570a6f497 in android-security-15.0.0_r10, identical hunk at line 116; 2/2 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base dee86220f445 in android-security-16.0.0_r1, hunk at line 117; 2/2 patched lines present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); the patched region (onNullBinding override) is already present at android-security-17.0.0_r1 (2/2 lines)."
     },
     "backport_notes": [
      "The patch adds one self-contained 8-line method. On 14, 15 and 16 the hunk content and surrounding context are identical and only the line offsets differ. It relies on RemoteFillService extending ServiceConnector.Impl, which provides unbind(). When applying it, check that ComponentName, NonNull and Slog are imported."
     ],
     "confidence": "high",
     "unknowns": [
      "How ServiceConnector.Impl.unbind() disposes of queued jobs and whether it notifies FillServiceCallbacks of failure; not shown in the material.",
      "Whether other ServiceConnector.Impl-based system_server connections that use BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS handle onNullBinding the same way; not shown.",
      "Android 13 is listed as affected by the bulletin, but no Android 13 fix commit is included in the material, and the shape of the code on older trees 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."
     }
    }
   }
  },
  "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/2025-09-01",
  "latest_version": "2.2",
  "latest_revision": {
   "version": "2.2",
   "date": "December 8, 2025",
   "notes": "AOSP Links updated"
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "September 2, 2025",
    "notes": "Bulletin published"
   },
   {
    "version": "1.1",
    "date": "September 8, 2025",
    "notes": "Updated Issue List"
   },
   {
    "version": "1.2",
    "date": "September 11, 2025",
    "notes": "Updated AOSP Links"
   },
   {
    "version": "2.0",
    "date": "September 15, 2025",
    "notes": "Updated Issue List"
   },
   {
    "version": "2.1",
    "date": "December 2, 2025",
    "notes": "Updated Issue List"
   },
   {
    "version": "2.2",
    "date": "December 8, 2025",
    "notes": "AOSP Links updated"
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [],
  "out_of_scope_rows": {
   "Arm components": 3,
   "Imagination Technologies": 10,
   "Kernel": 3,
   "MediaTek components": 4,
   "Qualcomm closed-source components": 27,
   "Qualcomm components": 5,
   "Widevine DRM": 1
  },
  "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."
 }
}