{
 "month": "2025-05",
 "generated": "2026-10-06T12:05+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 18,
    "certain": 18,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2023-35657",
     "osv_id": "ASB-A-260230151",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In bta_av_setconfig_rej (system/bta/av/bta_av_aact.cc), the peer address placed in the BTA AV reject callback data is now taken from the stream control block (p_scb->PeerAddress()) instead of reading p_data->str_msg.bd_addr from a union that the caller may not have populated as a tBTA_AV_STR_MSG.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained one-line change; it only requires that tBTA_AV_SCB expose PeerAddress(), which the code context shows already used in bta_av_aact.cc. Bluetooth is a mainline module, so the fix may also arrive via module updates rather than the platform tag (general platform knowledge)."
      ],
      "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_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/packages/modules/Bluetooth",
       "sha": "09ae4e14509f863eabe7bde721f5e8e3ffa14412",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-34739",
     "osv_id": "ASB-A-294105066",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a USER_SETUP_COMPLETE check in UsbProfileGroupSettingsManager so that the USB device-attached path returns before resolveActivity while the current user's setup is still in progress.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained Java commit; it needs the two Settings imports plus the helper and the early return.",
       "Make sure the check is inserted immediately before resolveActivity(intent, matches, defaultActivity, device, null) in the device-attached path (hunk at ~line 917, just above deviceAttachedForFixedHandler). Forks that split or renamed this method need the check placed before their equivalent launch."
      ],
      "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-14.0.0_r11",
      "date": "2024-08-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r11",
      "date": "2024-08-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "50e1f8f36e32928d10e72324c05a203a6db9f7fb",
       "tag": "android-security-14.0.0_r11",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 269,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-0087",
     "osv_id": "ASB-A-333681693",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "UninstallerActivity now only accepts a cross-user uninstall request when the requesting user is the profile parent of the target user, replacing a check that accepted any user returned by userManager.getUserProfiles().",
      "confidence": "high",
      "backport_notes": [
       "Single-file, self-contained change in UninstallerActivity.java that uses UserManager.getProfileParent(UserHandle) and Process.myUserHandle(). Remove the java.util.List import if it becomes unused, and ignore the import reordering in the Android 15 variant."
      ],
      "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_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": "61762eea6f1e5d944fcaaa44d370d1b09e1ff8be",
       "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": 59,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-22425",
     "osv_id": "ASB-A-364604008",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PackageInstaller's InstallStart activity stops identifying the originating package via Activity.getCallingPackage() and uses getLaunchedFromPackage() instead. A follow-up commit removes the now-dead null-check branch that replaced the calling package with the PackageInstaller session's installer package name.",
      "confidence": "high",
      "backport_notes": [
       "Apply both commits in order: 8575592802b9 first, then 942884abf148. The second commit's diff base, e70acc314658, is the file after the first commit. Applying only the first leaves a dead `callingPackage == null` branch.",
       "On trees that still call getPackageManager() inline or use a literal -1 for the session id, the second commit's hunks may need small context adjustments. The material shows no dependency on other changes."
      ],
      "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_r19",
      "date": "2025-04-07",
      "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": "8575592802b9527fe0f7cf19e9cb7159c9aa5121",
       "tag": "android-security-14.0.0_r19",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "942884abf148426e948774b4857052da77ef77b3",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 24,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26420",
     "osv_id": "ASB-A-313909156",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "GrantPermissionsActivity now merges an older, still-showing permission dialog from the same package and task into a newly launched one, and caps simultaneous dialogs per package/task at 10, instead of letting multiple independent dialogs coexist.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit fix limited to GrantPermissionsActivity.java. It relies on the existing mDelegated, mFollowerActivities and mPreMergeShownGroupName fields and the system-triggered merge logic, and it moves mViewModel creation before the synchronized map block. The Android 14 cherry-pick itself needed conflict resolution, so trees with customized surrounding code may need adaptation.",
       "PermissionController ships in the Permission mainline module, so devices may receive this fix through a module update rather than a platform security tag (general platform knowledge)."
      ],
      "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_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/packages/modules/Permission",
       "sha": "6bed47f63ec0600b3c57388449db37405c68dc58",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2025-26421",
     "osv_id": "ASB-A-344865740",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an OEM-configurable list of \"biometric protected\" packages (frameworks/base resource config_biometric_protected_package_names). The Settings app-info screens (legacy AppButtonsPreferenceController and SPA PackageInfoPresenter) now require lock-screen confirmation via AppInfoDashboardFragment.showLockScreen before disabling, force-stopping or uninstalling (updates of) a package on that list.",
      "confidence": "high",
      "backport_notes": [
       "Two-repo dependency: the frameworks/base change (config.xml string-array plus the symbols.xml java-symbol) must land before or together with the Settings change, because Utils.isProtectedPackage references com.android.internal.R.array.config_biometric_protected_package_names. The Settings change also calls AppInfoDashboardFragment.showLockScreen without adding it, so that method must already exist in the target branch. The fix has no effect until a device overlay populates the array.",
       "The Android 14 and 15 Settings diffs differ in handleDialogClick (metrics argument) and in PackageInfoPresenter.forceStop/disable (reloadPackageInfo vs the appRestrictionsApi block). Use the variant that matches the target branch."
      ],
      "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_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": "6c2c795c4bc03788cb80f7a578f5b45cdecfc083",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "38fefded38a4c6d8cbc897d5966be6b86b2a313e",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-26423",
     "osv_id": "ASB-A-349550024",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds size and validity checks for the static IP configuration (DNS server count, domains length) and HTTP proxy info (isValid, host length, exclusion list count and total length) in WifiConfigurationUtil.validateIpConfiguration, so that oversized IP configuration data supplied with a WifiConfiguration is rejected during validation.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It needs only the existing MAX_STRING_LENGTH constant and the static-config block of validateIpConfiguration in WifiConfigurationUtil, plus android.net.ProxyInfo (isValid, getHost, getExclusionList) and StaticIpConfiguration.getDnsServers/getDomains. The 14 and 15 commits differ only by a line offset.",
       "The new tests rely on WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy / createStaticIpConfigurationWithStaticProxy and on a java.util.List import in WifiConfigurationUtilTest. Confirm these exist on the target branch."
      ],
      "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_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/packages/modules/Wifi",
       "sha": "a51669fbf6f63f04ed5cd42c69a4995b7ff32d7b",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2025-26425",
     "osv_id": "ASB-A-379362792",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an SDK-level precondition (SdkLevel.isAtLeastU()) at the top of RoleService's binder methods getDefaultApplicationAsUser and setDefaultApplicationAsUser so they throw IllegalStateException instead of executing when the Permission module runs on a platform below Android 14 (API 34).",
      "confidence": "high",
      "backport_notes": [
       "Single-file, self-contained change: insert Preconditions.checkState(SdkLevel.isAtLeastU(), ...) as the first statement of both methods and make sure com.android.modules.utils.build.SdkLevel is imported (com.android.internal.util.Preconditions is already imported in RoleService). SdkLevel from modules-utils is the standard SDK-level check available to mainline module code (general platform knowledge).",
       "The Android 14 branch carried the change as RESTRICT AUTOMERGE without the CTS test. Take RoleManagerSecurityTest.kt from the Android 15 commit if you want test coverage."
      ],
      "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_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/packages/modules/Permission",
       "sha": "8ae31de10c7cab32dcd9671cda2eea4efc458195",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2025-26426",
     "osv_id": "ASB-A-310632322",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the special case in ActivityManagerService.registerReceiverWithFeature that let any caller pass \"android\" as callerPackage without being validated against the calling process's package list, and then widens the exemption from SYSTEM_UID to all core uids so system components in other core processes keep working.",
      "confidence": "high",
      "backport_notes": [
       "Apply in order: first remove the `!\"android\".equals(callerPackage)` clause (d48f297f2165), then switch `callerApp.info.uid != SYSTEM_UID` to `!UserHandle.isCore(callerApp.info.uid)` (93c036bb67be); per the second commit's message, system components in core uids other than SYSTEM_UID register receivers as \"android\" and need the widened exemption.",
       "The change is a one-line condition in a single method; UserHandle.isCore is already used a few lines below (line 13842), so no new imports are needed."
      ],
      "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_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": "93c036bb67bea16fc613c532c576bd7d4fc652a3",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "d48f297f2165791829407b6ce7c4d144da4bc1a9",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26427",
     "osv_id": "ASB-A-200034476",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Extends the Android 11 SAF privacy restriction on /Android/data, /Android/obb and /Android/sandbox to their whole subtrees. ExternalStorageProvider now hides those subtrees through a new FileSystemProvider.shouldHideDocument() hook, and the DocumentsUI picker no longer launches to an EXTRA_INITIAL_URI inside them.",
      "confidence": "high",
      "backport_notes": [
       "Apply frameworks/base and DownloadProvider together. FileSystemProvider.queryChildDocumentsForManage() becomes final, and queryChildDocumentsShowAll()/shouldHide()/shouldBlockFromTree() are removed or renamed. So DownloadStorageProvider, and any OEM FileSystemProvider subclass, must switch to overriding queryChildDocuments(..., boolean includeHidden) / shouldHideDocument() / shouldBlockDirectoryFromTree(). The DocumentsUI commit has no code dependency on the provider commits."
      ],
      "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_r5",
      "date": "2024-02-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r19",
      "date": "2025-04-07",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "4af5db76f25348849252e0b8a08f4a517ef842b7",
       "tag": "android-security-14.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "5acd646e0cf63e2c9c0862da7e03531ef0074394",
       "tag": "android-security-14.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/DocumentsUI",
       "sha": "954b6d5397eed78a59b570e0cf1df717c76804ce",
       "tag": "android-security-14.0.0_r19",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "public_before_announcement_days": 24,
     "first_commit_before_announcement_days": 451,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/DocumentsUI",
      "platform/packages/providers/DownloadProvider"
     ]
    },
    {
     "cve": "CVE-2025-26428",
     "osv_id": "ASB-A-378514614",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return in LockTaskController.startLockTaskMode so that an app-initiated request on a LOCK_TASK_AUTH_PINNABLE task that is already in mLockTaskModeTasks no longer triggers a new screen pinning request via StatusBarManagerInternal.showScreenPinningRequest.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, four-line guard relying on mLockTaskModeTasks and Slog, both already used in LockTaskController; check that TAG_LOCKTASK is defined/imported in the target branch, since the shown pre-fix context does not use it."
      ],
      "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_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": "d4cd532ad022a13f3e106168ac310b5da5f887b2",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26429",
     "osv_id": "ASB-A-372678095",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a cap of about 2000 (NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) on the total number of attributed op entries that AppOpsService packs into the OpEntry list returned for IAppOpsService#getPackagesForOpsForDevice, so the reply is less likely to exceed the binder transaction limit.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit: one new constant plus a counter around the two existing loops. Match the target branch's getOpEntryForResult signature: one argument on 14, two arguments with persistentDeviceId on 15. In Google's fix one counter is shared between the ops == null and ops != null branches."
      ],
      "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_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": "22a375717e61f3f6c2ddf9a6ad974f248ed9624e",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26436",
     "osv_id": "ASB-A-322159724",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PendingIntentRecord.clearAllowBgActivityStarts now also downgrades the token's duration-based temp-allowlist entry from TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED to TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, instead of only removing the token from the BAL token sets; a companion commit adds equals/hashCode to BackgroundStartPrivileges for the tests.",
      "confidence": "high",
      "backport_notes": [
       "Apply 88d7046ea668 (BackgroundStartPrivileges equals/hashCode) before c8ac5c6dec12: the new PendingIntentControllerTest.testClearAllowBgActivityStartsClearsToken uses assertEquals over BackgroundStartPrivileges values. The runtime fix is self-contained in PendingIntentRecord.clearAllowBgActivityStarts and needs TempAllowListDuration.type to be assignable and the two PowerWhitelistManager TEMPORARY_ALLOWLIST_TYPE_* constants in the target branch.",
       "No fork comparison section was provided, so no per-file fork verdict can be given."
      ],
      "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_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": "88d7046ea6686bda87b068f82bb931b84fd425fc",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "c8ac5c6dec12e1c53d6bddf7d6c2715a32ff608d",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26438",
     "osv_id": "ASB-A-251514171",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a check in smp_process_secure_connection_oob_data that aborts LE Secure Connections OOB pairing with SMP_OOB_FAIL when the peer's OOB flag is SMP_OOB_PRESENT but no local OOB data is stored, instead of continuing with a zeroed local randomizer.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in smp_act.cc; the only per-branch adaptation is the logging macro (SMP_TRACE_WARNING with __func__ on 14 vs log::warn on 15). Place the check after the existing local_random = {0} block and before the peer_oob_data.present check.",
       "Bluetooth is a mainline module (com.android.btservices APEX, per the Test line), so the fix may also ship via module updates independent of the platform security tag (general platform knowledge)."
      ],
      "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_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/packages/modules/Bluetooth",
       "sha": "022ac90c296b847406f664f663b20416fc57e1ee",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-26440",
     "osv_id": "ASB-A-290086710",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "CameraService now registers its AppOps watcher with WATCH_FOREGROUND_CHANGES and blocks untrusted MODE_IGNORED clients that are not user-visible (proc state above PROCESS_STATE_BOUND_TOP). Before, it decided between mute and block based on whether the uid was \"active\". AppOpsUidStateTrackerImpl now delays committing a uid-state change that loses a capability instead of committing it immediately.",
      "confidence": "medium",
      "backport_notes": [
       "Two commits are linked by bug 290086710: frameworks/av b9a047c94deb (CameraService.cpp) and frameworks/base b90d4d01e1bf (AppOpsUidStateTrackerImpl). Both landed in android-security-14.0.0_r20, so apply them together. The av change uses AppOpsManager::WATCH_FOREGROUND_CHANGES, UidPolicy::getProcState and ActivityManager::PROCESS_STATE_BOUND_TOP."
      ],
      "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_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/av",
       "sha": "b9a047c94deb06ab7ff956e4fb50b19ddd70cf9a",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "b90d4d01e1bfaacae0e1f144075f72b1fb036799",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/av",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26442",
     "osv_id": "ASB-A-386216637",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Settings NotificationAccessConfirmationActivity now matches the requested notification listener against the full ComponentName of each resolved NotificationListenerService instead of only its package name, so the BIND_NOTIFICATION_LISTENER_SERVICE permission and intent-filter checks are applied to the exact requested service.",
      "confidence": "high",
      "backport_notes": [
       "Single-line functional change in one file, with no other commits in the material. Android 13 is listed as affected, but no Android 13 commit is included in the material."
      ],
      "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_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/packages/apps/Settings",
       "sha": "a395a242270c77860920da211bfd7b098dbc374f",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-26444",
     "osv_id": "ASB-A-191743558",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the code in VoiceInteractionManagerService's PackageMonitor.onHandleForceStop that ran when the current voice interactor package was force stopped. That code nulled the interactor and recognizer settings, reset the current assistant, re-ran initForUser and cleared the ROLE_ASSISTANT role holders (falling back to the roles.xml default). Now the service is only shut down and restarted.",
      "confidence": "high",
      "backport_notes": [
       "Single commit that only deletes code, inside PackageMonitor.onHandleForceStop in VoiceInteractionManagerService.java. On a branch without it, delete the setCurInteractor, setCurRecognizer, resetCurAssistant and initForUser calls and the RoleManager.clearRoleHoldersAsUser block between setImplLocked(null) and switchImplementationIfNeededLocked(true). UserHandle is still used in this method."
      ],
      "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_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": "c439c7e75e73056e6201fa4f4fe340e715196182",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-27363",
     "osv_id": "ASB-A-399065987",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restructures heap buffer allocation in external/freetype src/truetype/ttgload.c in two places. In the TrueType composite-glyph variation path (load_truetype_glyph), the temporary outline is now allocated directly into the FT_Outline fields with explicit sizes, and n_points/n_contours are set only after allocation succeeds. In TT_Process_Simple_Glyph, the unrounded array switches to a non-zeroing allocation.",
      "confidence": "medium",
      "backport_notes": [
       "Single-commit fix, self-contained in src/truetype/ttgload.c, and it does not touch the Exit1 cleanup. When applying it to an older FreeType, confirm that Exit1 frees outline.points/tags/contours and that ON_CURVE_POINT is defined in that tree. external/freetype is a platform library consumed by Skia/hwui, not a mainline module, so the fix ships with the platform image (general platform knowledge).",
       "The material has no comparison against the fork, so no per-file fork verdict can be given."
      ],
      "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_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/external/freetype",
       "sha": "f9c146bda91f441b7bebac4387e46ae40caaf3b1",
       "tag": "android-security-14.0.0_r20",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r20"
   ],
   "unannounced": [],
   "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-35657": {
     "cve": "CVE-2023-35657",
     "summary": "In bta_av_setconfig_rej (system/bta/av/bta_av_aact.cc), the peer address placed in the BTA AV reject callback data is now taken from the stream control block (p_scb->PeerAddress()) instead of reading p_data->str_msg.bd_addr from a union that the caller may not have populated as a tBTA_AV_STR_MSG.",
     "component": "platform/packages/modules/Bluetooth, BTA AV state machine, system/bta/av/bta_av_aact.cc",
     "weakness": "Type confusion on a union argument leading to out-of-bounds read (CWE-843 / CWE-125)",
     "root_cause": "bta_av_setconfig_rej takes a tBTA_AV_DATA* union and reads p_data->str_msg.bd_addr, assuming the union was set up as a tBTA_AV_STR_MSG. Per the commit message, bta_av_config_ind in bta_av_aact.cc calls bta_av_setconfig_rej in some user journeys with p_data constructed as a tBTA_AV_CI_SETCONFIG, a valid but different union member, and the commit states this causes OOB access. The bulletin classifies the issue as information disclosure (ID).",
     "fix_analysis": [
      "system/bta/av/bta_av_aact.cc:bta_av_setconfig_rej: the initializer of the local tBTA_AV bta_av_data.reject.bd_addr is changed from p_data->str_msg.bd_addr to p_scb->PeerAddress(), so the address comes from the stream control block rather than from interpreting p_data as a specific union member; .hndl was already sourced from p_scb->hndl.",
      "Single one-line commit; no change to callers (bta_av_config_ind) or to the union definition."
     ],
     "completeness": [
      "The fix removes the only p_data->str_msg access visible in the diff hunk of bta_av_setconfig_rej; the p_data parameter remains in the signature.",
      "The caller side (bta_av_config_ind passing a tBTA_AV_CI_SETCONFIG) is left as is; the fix relies on the callee no longer trusting the union type rather than on correcting the caller's construction.",
      "The material does not show the rest of bta_av_setconfig_rej or other bta_av_* handlers, so whether any other handler reads a union member mismatched with how its caller builds p_data cannot be judged from this material."
     ],
     "behavior_changes": [
      "The bd_addr in the reject callback data (bta_av_data.reject) now comes from the stream control block's peer address. For the tBTA_AV_CI_SETCONFIG path it replaces an out-of-bounds read with the stored peer address; for callers that passed a proper tBTA_AV_STR_MSG the material does not show whether the two addresses always match.",
      "No new error codes, rejections, or timeouts introduced in the diff."
     ],
     "preconditions": [
      "bta_av_config_ind reaches bta_av_setconfig_rej with p_data constructed as a tBTA_AV_CI_SETCONFIG (per commit message, 'in some user journeys').",
      "Bluetooth enabled with the BTA AV (A2DP) path in use; the exact journeys and whether a remote peer can trigger them are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 09ae4e14509f in android-security-14.0.0_r20 (one-line change, hunk at line 1794); at android-security-14.0.0_r29 the patched region is reported as 2/3 lines present.",
      "15": "same fix: commit 655dbc919736 in android-security-15.0.0_r8, identical one-line diff (hunk at line 1819); at android-security-15.0.0_r17 the patched region is reported as 2/3 lines present.",
      "16": "not listed by the bulletin (not affected per the bulletin); at android-security-16.0.0_r8 the patched region is reported as 2/3 lines present.",
      "17": "not listed by the bulletin (not affected per the bulletin); at android-security-17.0.0_r1 the patched region is reported as 2/3 lines present."
     },
     "backport_notes": [
      "Self-contained one-line change; it only requires that tBTA_AV_SCB expose PeerAddress(), which the code context shows already used in bta_av_aact.cc. Bluetooth is a mainline module, so the fix may also arrive via module updates rather than the platform tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which exact branch(es) in bta_av_config_ind pass a tBTA_AV_CI_SETCONFIG to bta_av_setconfig_rej, whether any other caller does the same, and whether the path is remotely reachable.",
      "Why the per-major check reports 2/3 lines of the patched region rather than 3/3 at the latest tags, and whether the fixed line is among those present.",
      "Android 13 is listed as affected by the bulletin but no Android 13 commit is included in the material."
     ],
     "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-2024-34739": {
     "cve": "CVE-2024-34739",
     "summary": "Adds a USER_SETUP_COMPLETE check in UsbProfileGroupSettingsManager so that the USB device-attached path returns before resolveActivity while the current user's setup is still in progress.",
     "component": "platform/frameworks/base, system_server USB service (services/usb, UsbProfileGroupSettingsManager)",
     "weakness": "Missing state check: the USB device-attached handler activity could be started before user setup was complete (no USER_SETUP_COMPLETE gating)",
     "root_cause": "In services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java, the device-attached handling path (the method just before deviceAttachedForFixedHandler, ending in resolveActivity(intent, matches, defaultActivity, device, null)) went on to start the resolved handler activity after its earlier early-return checks without consulting Settings.Secure.USER_SETUP_COMPLETE. As a result, an activity could be launched by a USB attach while device setup was still in progress (commit title: 'Restrict USB poups while setup is in progress'). The bulletin rates this High / EoP.",
     "fix_analysis": [
      "UsbProfileGroupSettingsManager.java: a new private helper, shouldRestrictOverlayActivities(), reads Settings.Secure.getIntForUser(mContext.getContentResolver(), USER_SETUP_COMPLETE, defaultValue=1, UserHandle.CURRENT.getIdentifier()). If the value is 0 it logs 'restricting usb overlay activities as setup is not complete' via Slog.d and returns true; otherwise it returns false.",
      "UsbProfileGroupSettingsManager.java: in the device-attached path, immediately before '// Start activity with registered intent' / resolveActivity(intent, matches, defaultActivity, device, null), the fix inserts 'if (shouldRestrictOverlayActivities()) return;'.",
      "Two imports are added: static android.provider.Settings.Secure.USER_SETUP_COMPLETE and android.provider.Settings. The fix is a single Java-only commit with 21 added lines and no removed lines."
     ],
     "completeness": [
      "Covers only the one resolveActivity call site in the USB device-attached path (called with device, null). The diff does not change deviceAttachedForFixedHandler(UsbDevice, ComponentName), which begins right after the patched method and also builds a device-attached intent; the rest of its body is not shown.",
      "The check uses defaultValue=1: if USER_SETUP_COMPLETE is absent for the user, the launch is allowed (fail-open).",
      "The check is evaluated for UserHandle.CURRENT rather than for the specific user/profile resolved; the material does not show whether these can differ here.",
      "The launch is suppressed rather than deferred; this diff adds no re-trigger after setup completes."
     ],
     "behavior_changes": [
      "While Settings.Secure.USER_SETUP_COMPLETE == 0 for the current user, a USB device attach handled by this path no longer reaches resolveActivity; Slog.d logs 'restricting usb overlay activities as setup is not complete'.",
      "No change when the setting is 1 or unset (default 1).",
      "Adds a Settings.Secure.getIntForUser read before resolveActivity in this path."
     ],
     "preconditions": [
      "A USB device attach event reaches the UsbProfileGroupSettingsManager device-attached path and passes the earlier early-return checks, so that resolveActivity would be called.",
      "The current user's USER_SETUP_COMPLETE secure setting is 0 (setup in progress) for the new restriction to apply."
     ],
     "per_major": {
      "14": "same fix: commit 50e1f8f36e32 in android-security-14.0.0_r11 (cherry-pick of internal 2ce2e54a/71042ac0, Change-Id I7d54534696fd73f3b94c5b4250142eed9341c5d8); at android-security-14.0.0_r29 the patched region is fully present (8/8 lines).",
      "15": "not affected per the bulletin (the bulletin's affected list shows '15-next'); patched region fully present (8/8) at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin; patched region fully present (8/8) at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; patched region fully present (8/8) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained Java commit; it needs the two Settings imports plus the helper and the early return.",
      "Make sure the check is inserted immediately before resolveActivity(intent, matches, defaultActivity, device, null) in the device-attached path (hunk at ~line 917, just above deviceAttachedForFixedHandler). Forks that split or renamed this method need the check placed before their equivalent launch."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact name of the patched method is not visible in the hunk; it is identified by its resolveActivity(..., device, null) call and its position before deviceAttachedForFixedHandler.",
      "Whether deviceAttachedForFixedHandler or any accessory-attach path also launches activities, and whether separate commits for this CVE gated them.",
      "The concrete privilege-escalation impact; the bulletin gives only High / EoP.",
      "Status of Android 13, which the bulletin lists as affected but for which no per-major fact is provided."
     ],
     "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-0087": {
     "cve": "CVE-2025-0087",
     "summary": "UninstallerActivity now only accepts a cross-user uninstall request when the requesting user is the profile parent of the target user, replacing a check that accepted any user returned by userManager.getUserProfiles().",
     "component": "platform/frameworks/base, packages/PackageInstaller (UninstallerActivity)",
     "weakness": "Improper authorization: insufficient cross-user check when deciding which user a caller may request an uninstall for.",
     "root_cause": "In packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java, when mDialogInfo.user was non-null the code accepted the request if userManager.getUserProfiles() contained mDialogInfo.user. It did not check whether the caller was the parent of the target profile. The commit message states the intended policy: only the parent profile can uninstall an app that is in a child profile.",
     "fix_analysis": [
      "UninstallerActivity.java (the mDialogInfo.user check, hunk at about line 160): the getUserProfiles().contains(...) membership test is removed. If mDialogInfo.user equals Process.myUserHandle(), the request proceeds. Otherwise the code computes isCurrentUserProfileOwner = Process.myUserHandle().equals(userManager.getProfileParent(mDialogInfo.user)); if that is false it logs the same error, calls showUserIsNotAllowed() and returns, as the old rejection path did.",
      "The unused java.util.List import is dropped. The Android 15 commit (4c269d7b0ec7) contains the same logic change plus unrelated import reordering."
     ],
     "completeness": [
      "The fix covers the single user check in UninstallerActivity.java: self-user requests are allowed, requests from the profile parent of the target are allowed, and all other targets are rejected via showUserIsNotAllowed().",
      "Only this check site is changed. The material shows no other cross-user checks in PackageInstaller (for example in the v2 UninstallLaunch path imported on Android 15) and does not show how mDialogInfo.user is populated, so whether equivalent validation exists elsewhere cannot be judged from the material.",
      "If userManager.getProfileParent(mDialogInfo.user) returns null, the equals comparison fails and the request is rejected, which is the restrictive outcome."
     ],
     "behavior_changes": [
      "Uninstall requests targeting a user other than the caller's are now rejected (showUserIsNotAllowed) unless the caller's user is the profile parent of the target. Previously, any target in the caller's getUserProfiles() result was accepted.",
      "Requests where the target user equals the caller's user no longer go through the getUserProfiles() lookup.",
      "The error log text is unchanged; no new return codes are introduced."
     ],
     "preconditions": [
      "UninstallerActivity is reached with a non-null mDialogInfo.user that differs from the caller's user; the material does not show where this value comes from.",
      "Before the fix, the target user was in the caller's getUserProfiles() result but the caller was not the target's profile parent."
     ],
     "per_major": {
      "14": "same fix: 61762eea6f1e in android-security-14.0.0_r18 (RESTRICT AUTOMERGE cherry-pick); 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "adapted fix: 4c269d7b0ec7 in android-security-15.0.0_r6, with identical logic plus import reordering in a file that already imports the v2 UninstallLaunch and common.* classes; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin (not listed); the patched region is present (8/8 lines) at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); the patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-file, self-contained change in UninstallerActivity.java that uses UserManager.getProfileParent(UserHandle) and Process.myUserHandle(). Remove the java.util.List import if it becomes unused, and ignore the import reordering in the Android 15 variant."
     ],
     "confidence": "high",
     "unknowns": [
      "How mDialogInfo.user is populated (the code that parses the launching intent is not in the material).",
      "Exactly which caller/target user relationships the old getUserProfiles() check accepted.",
      "Whether the v2 PackageInstaller UI path (UninstallLaunch, present on Android 15) performs its own user check; not shown.",
      "Why Android 16 and 17 are not listed by the bulletin although the patched region is present there."
     ],
     "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-22425": {
     "cve": "CVE-2025-22425",
     "summary": "PackageInstaller's InstallStart activity stops identifying the originating package via Activity.getCallingPackage() and uses getLaunchedFromPackage() instead. A follow-up commit removes the now-dead null-check branch that replaced the calling package with the PackageInstaller session's installer package name.",
     "component": "platform/frameworks/base, packages/PackageInstaller (InstallStart activity)",
     "weakness": "Unreliable caller identification (Activity.getCallingPackage() used to determine the source package of an install request)",
     "root_cause": "InstallStart.onCreate() got `callingPackage` from Activity.getCallingPackage(). The commit message says this API is unreliable (go/getcallingpackage-misuse). The value feeds getSourceInfo(callingPackage). Per the surrounding code comment, the uid from the resulting ApplicationInfo is preferred as the source package uid, over the uid from getLaunchedFromUid(). So the package name used to identify the install source might not match the package that actually launched the activity. Also, when getCallingPackage() returned null for a session-based install, the code filled callingPackage from sessionInfo.getInstallerPackageName(). That value comes from the session, not from the actual launcher. getCallingPackage() only reflects a caller that used startActivityForResult() and is otherwise null (general platform knowledge).",
     "fix_analysis": [
      "Commit 8575592802b9 (android-security-14.0.0_r19), InstallStart.java:onCreate replaces `String callingPackage = getCallingPackage();` with `String callingPackage = getLaunchedFromPackage();`. This makes it consistent with the existing getLaunchedFromUid() call a few lines below.",
      "Commit 942884abf148 (android-security-14.0.0_r20), InstallStart.java:onCreate: per the commit message, getLaunchedFromPackage() never returns null, so the old `if (callingPackage == null && sessionId != -1)` branch could no longer run. It is rewritten as `if (sessionId != SessionInfo.INVALID_ID)` and now only reads callingAttributionTag from sessionInfo.getInstallerAttributionTag(). The assignment `callingPackage = sessionInfo.getInstallerPackageName()` is removed. The commit also uses SessionInfo.INVALID_ID instead of the literal -1 and imports PackageInstaller.SessionInfo.",
      "Commit 942884abf148, InstallStart.java:getSourceInfo and isSystemDownloadsProvider: switch from getPackageManager() to the cached mPackageManager field, with no semantic change."
     ],
     "completeness": [
      "Covers the point in InstallStart.onCreate where the calling package name is obtained. getSourceInfo(callingPackage) now receives the getLaunchedFromPackage() value.",
      "Removes the path that substituted the session's installer package name for the calling package. The attribution tag is still read from the session.",
      "getSourceInfo keeps its @Nullable parameter and null handling. Since getLaunchedFromPackage() never returns null, that handling is harmless.",
      "The provided commits change only InstallStart.java (the legacy v1 flow). Per index search, the v2 flow (InstallRepository.kt) has its own getSourceInfo(callingPackage), but the material does not show how it obtains callingPackage. Coverage of that path cannot be judged."
     ],
     "behavior_changes": [
      "callingPackage is never null and is taken from getLaunchedFromPackage(), the launcher record, instead of getCallingPackage().",
      "For session installs (ACTION_CONFIRM_INSTALL / ACTION_CONFIRM_PRE_APPROVAL), the source package is no longer taken from sessionInfo.getInstallerPackageName(). Only the installer attribution tag is read from the session.",
      "The session id sentinel changes from the literal -1 to SessionInfo.INVALID_ID. There is no functional difference if the constant equals -1."
     ],
     "preconditions": [
      "An application launches the PackageInstaller InstallStart activity, with an install intent or a session confirmation intent carrying EXTRA_SESSION_ID.",
      "Before the fix, the behavior depended on what getCallingPackage() returned for that launch. This differs between startActivity() and startActivityForResult() launches (general platform knowledge)."
     ],
     "per_major": {
      "14": "same fix: two commits, 8575592802b9 (getLaunchedFromPackage) in android-security-14.0.0_r19 and 942884abf148 (removes the dead null-check branch) in android-security-14.0.0_r20. Patched region fully present (5/5) at android-security-14.0.0_r29.",
      "15": "not affected per the bulletin. Patched region present (5/5) at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin. Patched region present (5/5) at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin. Patched region present (5/5) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply both commits in order: 8575592802b9 first, then 942884abf148. The second commit's diff base, e70acc314658, is the file after the first commit. Applying only the first leaves a dead `callingPackage == null` branch.",
      "On trees that still call getPackageManager() inline or use a literal -1 for the session id, the second commit's hunks may need small context adjustments. The material shows no dependency on other changes."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how getCallingPackage() could return a misleading value in this flow. The commit only references an internal document.",
      "The material does not show whether the v2 PackageInstaller flow (InstallRepository.kt) obtained callingPackage through the same API, or whether it was changed separately.",
      "The Android 13 fix commit is not included in the material.",
      "The later checks in InstallStart that use sourceInfo and the source uid are not shown in full."
     ],
     "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-26420": {
     "cve": "CVE-2025-26420",
     "summary": "GrantPermissionsActivity now merges an older, still-showing permission dialog from the same package and task into a newly launched one, and caps simultaneous dialogs per package/task at 10, instead of letting multiple independent dialogs coexist.",
     "component": "platform/packages/modules/Permission, PermissionController runtime permission grant UI (GrantPermissionsActivity)",
     "weakness": "Improper state management of concurrent permission request dialogs (no merge and no limit for multiple GrantPermissionsActivity instances for the same package and task)",
     "root_cause": "In GrantPermissionsActivity.onCreate, the sCurrentGrantRequests map (keyed by Pair<package, taskId>) handled only three cases: no current activity (register this one), a new system-triggered request (delegate and finish), or an existing system-triggered dialog (merge into it). When a normal app request arrived while another normal request from the same package in the same task was already registered, none of these branches applied. The new activity went on independently with its own view model and UI, and there was no limit on how many such dialogs could stack in one task. showNextRequest and the photo picker, settings and Health Connect launches went to the top follower activity ('top') instead of the current activity.",
     "fix_analysis": [
      "GrantPermissionsActivity.java:onCreate: GrantPermissionsViewModelFactory/mViewModel creation moves before the synchronized (sCurrentGrantRequests) block, so the new activity has a view model when it absorbs an older dialog. The later creation inside `if (!mDelegated)` is removed and only the observe() call remains.",
      "GrantPermissionsActivity.java:onCreate: a new else branch handles a current dialog that is not system-triggered. If current.mFollowerActivities.size() + 2 > MAX_DIALOGS_PER_PKG_TASK (10), the new activity calls finishAfterTransition() and returns. Otherwise it calls onNewFollowerActivity(current, current.mRequestedPermissions, true) to merge the older dialog into itself, then replaces the map entry with `this`.",
      "GrantPermissionsActivity.java:onNewFollowerActivity: adds a boolean parameter followerIsOlder. When it is true: the older follower is marked mDelegated = true; the pre-merge shown group and the current RequestInfos are read from the older activity (its mRootView visibility and its mViewModel LiveData) instead of from `this`; if the older dialog was visible, the new activity takes over its request list (mRequestInfos = currentGroups; showNextRequest()) and sets the older activity's mRootView to GONE; the older activity's own followers are merged recursively into the new activity and its follower list is cleared. Existing callers pass false.",
      "GrantPermissionsActivity.java:showNextRequest: returns early if mDelegated is set. The `top` follower selection is removed, and sendDirectlyToSettings, openPhotoPicker and handleHealthConnectPermissions are now called with `this`.",
      "GrantPermissionsActivity.java, GRANTED_USER_SELECTED photo picker branch (near line 678; the material does not name the enclosing method): mViewModel.openPhotoPicker is called with `this` instead of the computed `top` activity.",
      "GrantPermissionsActivity.java:setResultIfNeeded: mResultCode is assigned before removeActivityFromMap(). If mRequestedPermissions changed in the meantime (a merge came in), mResultCode is reset to Integer.MAX_VALUE and false is returned.",
      "GrantPermissionsActivity.java:onCreate: when icicle is null, window.setDimAmount(0f) now runs only if mRootView is null or not VISIBLE, so a dialog that is already showing merged content is not un-dimmed."
     ],
     "completeness": [
      "Covers the previously unhandled case of two or more normal (non-system-triggered) requests from the same package in the same task: older dialogs merge upward into the newest one, and more than 10 simultaneous dialogs per package/task are refused.",
      "The system-triggered branches (a new system request delegates and finishes; a normal request merges into an existing system-triggered dialog) are unchanged apart from the new `false` argument. The 10-dialog cap does not apply on those paths.",
      "The merge key is still Pair<package, taskId>, and the finishSystemStartedDialogsOnOtherTasksLocked() call is unchanged. Requests from the same package in different tasks are not merged by this change.",
      "In the GRANTED_USER_SELECTED photo picker branch, the local `Activity top = ...` is still computed but no longer used (dead code).",
      "The per-major facts report 7/8 lines of the patched region present at android-security-14.0.0_r29. The material does not show which line differs."
     ],
     "behavior_changes": [
      "A new permission request from an app that already has a non-system permission dialog open in the same task now takes over that dialog: the older activity is hidden (root view GONE) and marked delegated, and the newer activity shows its pending request groups.",
      "When the follower count reaches the limit (current.mFollowerActivities.size() + 2 > 10), a new dialog for the same package and task is finished at once via finishAfterTransition() and is not shown.",
      "Settings redirect, photo picker and Health Connect flows are launched from `this` (the newest, owning activity) instead of the top follower.",
      "Screen dim is no longer reset to 0 when the root view is already visible after a merge."
     ],
     "preconditions": [
      "An app makes more than one runtime permission request that creates GrantPermissionsActivity instances for the same package in the same task while an earlier dialog is still registered. Requests are launched via PackageManager.buildRequestPermissionsIntent / startActivityForResult, as in Activity.requestPermissions (per index search).",
      "The requests are not system-triggered (mIsSystemTriggered is false on both the current and the new activity); otherwise the existing delegate/merge branches apply.",
      "The material does not show what an attacker gains beyond the bulletin's High EoP rating."
     ],
     "per_major": {
      "14": "same fix: cherry-pick with merge conflicts resolved (RESTRICT AUTOMERGE), commit 6bed47f63ec0 in android-security-14.0.0_r20, Change-Id I25f72c30c22429472bfc75073431f84431debd7b. Patched region largely present (7/8 lines) at android-security-14.0.0_r29.",
      "15": "not affected per the bulletin (not listed). Patched region largely present (7/8 lines) at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin (not listed). Patched region largely present (7/8 lines) at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed). Patched region largely present (7/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit fix limited to GrantPermissionsActivity.java. It relies on the existing mDelegated, mFollowerActivities and mPreMergeShownGroupName fields and the system-triggered merge logic, and it moves mViewModel creation before the synchronized map block. The Android 14 cherry-pick itself needed conflict resolution, so trees with customized surrounding code may need adaptation.",
      "PermissionController ships in the Permission mainline module, so devices may receive this fix through a module update rather than a platform security tag (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete security impact of stacked independent dialogs (how the EoP shows up) is not in the material.",
      "Why setResultIfNeeded now assigns mResultCode before removeActivityFromMap() is not explained in the material.",
      "Which single line of the 8-line patched region differs at the latest tags, and whether that difference matters.",
      "Whether Android 13 (listed as affected by the bulletin) received an equivalent change; no Android 13 commit is in the material.",
      "The original upstream commits (6edd0982..., 61447324..., 87809935...) and whether they changed tests (PermissionTest23#testAppCanOnlyShowOneDialog) beyond this file."
     ],
     "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-26421": {
     "cve": "CVE-2025-26421",
     "summary": "Adds an OEM-configurable list of \"biometric protected\" packages (frameworks/base resource config_biometric_protected_package_names). The Settings app-info screens (legacy AppButtonsPreferenceController and SPA PackageInfoPresenter) now require lock-screen confirmation via AppInfoDashboardFragment.showLockScreen before disabling, force-stopping or uninstalling (updates of) a package on that list.",
     "component": "platform/packages/apps/Settings (app info: AppButtonsPreferenceController, spa PackageInfoPresenter, Utils) plus platform/frameworks/base core/res config resource",
     "weakness": "Missing user authentication step before a sensitive package-modification action (disable / force-stop / uninstall) in the Settings UI, i.e. no re-authentication requirement for protected packages",
     "root_cause": "In Settings, the app-info actions that modify a package ran as soon as the button or dialog was clicked, with no extra user verification. AppButtonsPreferenceController.handleDialogClick ran DisableChangerRunnable (COMPONENT_ENABLED_STATE_DISABLED_USER) and forceStopPackage directly. AppButtonsPreferenceController.uninstallPkg started ACTION_UNINSTALL_PACKAGE directly. The SPA PackageInfoPresenter.disable/forceStop/startUninstallActivity called setApplicationEnabledSetting, forceStopPackageAsUser and startUninstallActivity directly. There was no concept of a protected package whose modification requires the user to re-authenticate.",
     "fix_analysis": [
      "1) platform/frameworks/base 6c2c795c4bc0: core/res/res/values/config.xml adds an empty, non-translatable string-array config_biometric_protected_package_names. core/res/res/values/symbols.xml exports it as a java-symbol of type array, so Settings can read com.android.internal.R.array.config_biometric_protected_package_names.",
      "2) Settings 38fefded38a4, src/com/android/settings/Utils.java: new static Utils.isProtectedPackage(Context, String) reads that array and returns true if it contains the package name.",
      "3) Settings, AppButtonsPreferenceController: new private requireAuthAndExecute(Runnable). If Utils.isProtectedPackage(mContext, mAppEntry.info.packageName) is true, it routes the action through AppInfoDashboardFragment.showLockScreen(mContext, ...); otherwise it runs the action directly. handleDialogClick wraps both the DISABLE case (metrics + DisableChangerRunnable DISABLED_USER) and the FORCE_STOP case (forceStopPackage) in it. uninstallPkg wraps the whole uninstall-intent construction and mFragment.startActivityForResult in it. The system-app button handler is split: the enabled-but-disabled-until-used case (ACTION_SETTINGS_DISABLE_APP, DisableChangerRunnable COMPONENT_ENABLED_STATE_DEFAULT) now goes through requireAuthAndExecute, while the enable path stays unauthenticated.",
      "4) Settings, spa/app/appinfo/PackageInfoPresenter.kt: the same private requireAuthAndExecute(action) helper. disable(), startUninstallActivity() and forceStop() now run their coroutine or intent bodies inside it. enable() is not wrapped, and the diff does not change clearInstantApp().",
      "5) Tests: ShadowUtils gains an isProtectedPackage shadow and setter, and AppButtonsPreferenceControllerTest registers the shadow. PackageInfoPresenterTest adds *_protectedPackage tests. They static-mock Utils.isProtectedPackage through ExtendedMockito and use an ordinary KeyguardManager mock whose isKeyguardSecure returns false."
     ],
     "completeness": [
      "Gating depends entirely on config_biometric_protected_package_names, which ships empty in AOSP. With no overlay, Utils.isProtectedPackage always returns false and behaviour is unchanged for every package.",
      "Covered: disable, force-stop and uninstall in AppButtonsPreferenceController (dialog clicks, uninstallPkg, the disabled-until-used disable branch) and in PackageInfoPresenter (disable, forceStop, startUninstallActivity).",
      "Not gated by this change: the enable paths in both classes, PackageInfoPresenter.clearInstantApp, and the separate AppInfoDashboardFragment.uninstallPkg(String, boolean, boolean). Per index search at android-14.0.0_r75, that method builds and starts an ACTION_UNINSTALL_PACKAGE intent without a protected-package check. Whether it is reachable is not shown.",
      "The check exists only in the Settings UI. The diffs do not change PackageManager.setApplicationEnabledSetting, ActivityManager.forceStopPackageAsUser or the uninstaller.",
      "The actual authentication is delegated to the existing AppInfoDashboardFragment.showLockScreen, whose implementation is not in the material. The new tests stub KeyguardManager.isKeyguardSecure to false in a helper named setAuthPassesAutomatically. This suggests, but does not confirm, that the action proceeds without a challenge when no secure lock screen is set.",
      "In PackageInfoPresenter the metrics logAction call still happens before the authentication step."
     ],
     "behavior_changes": [
      "For packages listed in config_biometric_protected_package_names, Settings routes disable, force-stop and uninstall through AppInfoDashboardFragment.showLockScreen. The action runs only in its callback.",
      "New system resource com.android.internal.R.array.config_biometric_protected_package_names (empty by default) and new public helper Utils.isProtectedPackage in Settings.",
      "In AppButtonsPreferenceController the system-app button handler now logs ACTION_SETTINGS_DISABLE_APP and ACTION_SETTINGS_ENABLE_APP from separate branches instead of a ternary. For unprotected packages the semantics are unchanged.",
      "No behaviour change for packages not on the list, or on devices that do not overlay the array."
     ],
     "preconditions": [
      "The device build overlays config_biometric_protected_package_names with at least one package name (the AOSP default is empty).",
      "The user reaches the Settings app-info page for that package (legacy AppButtonsPreferenceController or SPA PackageInfoPresenter) and uses the Disable, Force stop or Uninstall/Uninstall updates actions."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin, but the material includes no Android 13 commit. Fix status on 13 is unknown from this material.",
      "14": "Same fix: frameworks/base 6c2c795c4bc0 (resource + symbol) and Settings 38fefded38a4 in android-security-14.0.0_r20 (Merged-In I0c494e307b02229d751de118abcc89e4e61a6861). At android-security-14.0.0_r29, 8/8 lines of the patched region are present.",
      "15": "Adapted fix: Settings commit f16fa58405ed in android-security-15.0.0_r8. The logic is the same, but PackageInfoPresenter.forceStop also wraps the android.app.Flags.appRestrictionsApi()/noteAppRestrictionEnabled block, disable has no reloadPackageInfo, the handleDialogClick DISABLE case keeps getPackageNameForMetric(), ShadowUtils lives under tests/robotests/testutils, and the test uses mockito-kotlin. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin). At android-security-16.0.0_r8, 4/4 lines of the patched region are present.",
      "17": "Not affected (not listed by the bulletin). At android-security-17.0.0_r1, 4/4 lines of the patched region are present."
     },
     "backport_notes": [
      "Two-repo dependency: the frameworks/base change (config.xml string-array plus the symbols.xml java-symbol) must land before or together with the Settings change, because Utils.isProtectedPackage references com.android.internal.R.array.config_biometric_protected_package_names. The Settings change also calls AppInfoDashboardFragment.showLockScreen without adding it, so that method must already exist in the target branch. The fix has no effect until a device overlay populates the array.",
      "The Android 14 and 15 Settings diffs differ in handleDialogClick (metrics argument) and in PackageInfoPresenter.forceStop/disable (reloadPackageInfo vs the appRestrictionsApi block). Use the variant that matches the target branch."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of AppInfoDashboardFragment.showLockScreen (which challenge it uses, and what it does when no secure keyguard is set) is not in the material.",
      "Which packages Google or OEMs put in config_biometric_protected_package_names; the AOSP default is empty.",
      "Whether an Android 13 fix was shipped (the bulletin lists 13 as affected, but no 13 commit is provided).",
      "The concrete scenario behind the High EoP rating (bugs 352504490, 344865740) is not described in the material.",
      "Whether AppInfoDashboardFragment.uninstallPkg is still reachable in security branches."
     ],
     "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-26423": {
     "cve": "CVE-2025-26423",
     "summary": "Adds size and validity checks for the static IP configuration (DNS server count, domains length) and HTTP proxy info (isValid, host length, exclusion list count and total length) in WifiConfigurationUtil.validateIpConfiguration, so that oversized IP configuration data supplied with a WifiConfiguration is rejected during validation.",
     "component": "platform/packages/modules/Wifi, Wifi service (WifiConfigurationUtil, IP configuration validation of WifiConfiguration)",
     "weakness": "Missing size/bounds validation of caller-supplied configuration fields (unbounded DNS server list, domains string, proxy host and proxy exclusion list) in the IpConfiguration attached to a WifiConfiguration",
     "root_cause": "Before the fix, WifiConfigurationUtil.validateIpConfiguration (as far as the diff context shows) checked the static IP configuration for a null static address. It did not bound the number of DNS servers in StaticIpConfiguration or the length of the domains string. It also did not check anything about the IpConfiguration's ProxyInfo: its validity, host length, number of exclusion list entries, or total exclusion list length. A WifiConfiguration passed to validate() (for example via the VALIDATE_FOR_ADD path the tests use) could therefore carry arbitrarily large IP configuration fields and still pass validation. The material does not show the downstream consequence. The bulletin rates the issue High EoP.",
     "fix_analysis": [
      "service/java/com/android/server/wifi/WifiConfigurationUtil.java: adds the constant MAX_ENTRY_SIZE = 100 next to the existing MAX_STRING_LENGTH = 512, and imports android.net.ProxyInfo.",
      "WifiConfigurationUtil.validateIpConfiguration, inside the block that handles staticIpConfig, after the null static-address check: rejects the config if staticIpConfig.getDnsServers() is non-null and has more than MAX_ENTRY_SIZE (100) entries, or if staticIpConfig.getDomains() is non-null and longer than MAX_STRING_LENGTH (512). Each failure logs an error and returns false.",
      "WifiConfigurationUtil.validateIpConfiguration, after the static block closes (so it is not limited to static configs): fetches ipConfig.getHttpProxy(). If that is non-null, it returns false when proxyInfo.isValid() is false, when getHost() is longer than 512, when getExclusionList() has more than 100 entries, or when the running sum of exclusion list string lengths exceeds 512.",
      "service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java: adds testInvalidStaticIpConfig (a 513-char domains string in a static config with a PAC proxy) and testInvalidProxyInfo (a direct proxy with a 513-char exclusion entry). Both assert that WifiConfigurationUtil.validate(..., VALIDATE_FOR_ADD) returns false."
     ],
     "completeness": [
      "Covers: DNS server count and domains length for the static IP configuration; proxy validity, host length, exclusion list entry count and cumulative entry length for any IpConfiguration that carries an HTTP proxy.",
      "The DNS server and domains checks sit only inside the static-config block. The code shown does not indicate whether these StaticIpConfiguration fields can be populated otherwise.",
      "No explicit length check is added for a PAC proxy URL (ProxyInfo.getPacFileUrl). Only proxyInfo.isValid() covers that case, and the material does not show what isValid enforces.",
      "Per-entry exclusion list length is bounded only through the cumulative sum (512 total).",
      "The checks only take effect on code paths that call WifiConfigurationUtil.validate / validateIpConfiguration. The material shows only the VALIDATE_FOR_ADD path from the tests, so coverage of other entry points (update, restore/deserialization) is not demonstrated."
     ],
     "behavior_changes": [
      "WifiConfiguration validation now fails (validate returns false and logs a Log.e 'validateIpConfiguration failed: ...' message) for static IP configs with more than 100 DNS servers or a domains string over 512 characters.",
      "Validation now fails for any IpConfiguration whose ProxyInfo is not valid per ProxyInfo.isValid(), has a host over 512 characters, has more than 100 exclusion list entries, or whose exclusion list entries total more than 512 characters.",
      "Configurations with unusually large proxy exclusion lists or domain search strings, which were previously accepted, are now rejected."
     ],
     "preconditions": [
      "A WifiConfiguration carrying an IpConfiguration with a StaticIpConfiguration and/or an HTTP ProxyInfo must reach WifiConfigurationUtil.validate (the tests use VALIDATE_FOR_ADD). The material does not show which public API entry points lead there.",
      "The supplied IP configuration must contain oversized fields (many DNS servers, a long domains string, a long proxy host, or a large proxy exclusion list)."
     ],
     "per_major": {
      "14": "same fix: commit a51669fbf6f6 in android-security-14.0.0_r20 (cherry-pick, Change-Id I761dbb38ffa2e4dcebad2a6abb8d37d45a3b1fd8). The patched region is fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "same fix: commit 01e708a7a9af in android-security-15.0.0_r8. The hunks are identical apart from a line offset in validateIpConfiguration (@@ -721 vs -694). The patched region is fully 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. It needs only the existing MAX_STRING_LENGTH constant and the static-config block of validateIpConfiguration in WifiConfigurationUtil, plus android.net.ProxyInfo (isValid, getHost, getExclusionList) and StaticIpConfiguration.getDnsServers/getDomains. The 14 and 15 commits differ only by a line offset.",
      "The new tests rely on WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy / createStaticIpConfigurationWithStaticProxy and on a java.util.List import in WifiConfigurationUtilTest. Confirm these exist on the target branch."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete downstream effect of an oversized IP configuration (which component fails, or how it leads to EoP) is not shown in the material.",
      "Which public Wifi API entry points reach WifiConfigurationUtil.validateIpConfiguration, and whether all of them (add, update, restore) pass through it.",
      "What ProxyInfo.isValid() enforces, and therefore whether PAC URL length is bounded.",
      "The full pre-fix body of validateIpConfiguration and the condition guarding the static-config block.",
      "The bulletin lists Android 13 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": 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-26425": {
     "cve": "CVE-2025-26425",
     "summary": "Adds an SDK-level precondition (SdkLevel.isAtLeastU()) at the top of RoleService's binder methods getDefaultApplicationAsUser and setDefaultApplicationAsUser so they throw IllegalStateException instead of executing when the Permission module runs on a platform below Android 14 (API 34).",
     "component": "platform/packages/modules/Permission, role management service (RoleService, IRoleManager binder)",
     "weakness": "Missing precondition / improper access control: methods gated by a permission that only exists on U+ were callable on older platform releases",
     "root_cause": "RoleService.getDefaultApplicationAsUser and setDefaultApplicationAsUser are the service side of RoleManager.getDefaultApplication/setDefaultApplication. Per index search, those client methods are annotated @RequiresPermission(MANAGE_DEFAULT_APPLICATIONS) and @RequiresApi(UPSIDE_DOWN_CAKE). The fix's own comment says the MANAGE_DEFAULT_APPLICATIONS permission is only available on U+. The service methods did not check that the platform was U or newer. So when the updatable Permission module ran on an older platform (the new CTS test targets SDK S through T), the binder methods could still be reached and ran their logic: the cross-user check, the user existence check, then the role default-application get/set. This happened without the intended U+ permission guarantee. The material does not show the exact permission-enforcement code inside these methods.",
     "fix_analysis": [
      "service/java/com/android/role/RoleService.java:getDefaultApplicationAsUser (inner IRoleManager stub): adds Preconditions.checkState(SdkLevel.isAtLeastU(), \"This API is only available on Android 34 and above\") as the first statement, before UserUtils.enforceCrossUserPermission and the UserUtils.isUserExistent check.",
      "service/java/com/android/role/RoleService.java:setDefaultApplicationAsUser: adds the same Preconditions.checkState(SdkLevel.isAtLeastU(), ...) as the first statement, before enforceCrossUserPermission and the user existence check.",
      "service/java/com/android/role/RoleService.java: adds import com.android.modules.utils.build.SdkLevel. Only the Android 14 commit adds this import; the Android 15 diff does not add it, so the file already had it. com.android.internal.util.Preconditions was already imported.",
      "The Android 15 commit also adds tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt. It contains two @AsbSecurityTest(cveBugId=379362792) tests, limited by SdkSuppress to S through TIRAMISU, that check getDefaultApplication/setDefaultApplication throw NoSuchMethodError or IllegalStateException on pre-U platforms."
     ],
     "completeness": [
      "Covers exactly the two binder entry points getDefaultApplicationAsUser and setDefaultApplicationAsUser in RoleService. The check is unconditional and comes before all other logic in both methods.",
      "Does not change the client-side RoleManager.getDefaultApplication/setDefaultApplication wrappers (framework-s) or the underlying permission enforcement. The gate is based only on SDK level.",
      "The fix does not touch other IRoleManager methods in RoleService. The material does not show whether any of them depend on a U+-only permission in the same way.",
      "The Android 14 commit shown does not include the CTS test file. Only the Android 15 commit adds RoleManagerSecurityTest.kt."
     ],
     "behavior_changes": [
      "On platforms where SdkLevel.isAtLeastU() is false (API 33 and below), both binder methods now throw IllegalStateException (\"This API is only available on Android 34 and above\") right away. A client calling through RoleManager gets this exception instead of a result or callback.",
      "On U and newer platforms nothing changes: the check passes and execution continues as before.",
      "The check runs before enforceCrossUserPermission. On pre-U platforms, callers therefore get IllegalStateException instead of a SecurityException for cross-user violations."
     ],
     "preconditions": [
      "The updated Permission mainline module code (RoleService) is running on a device whose platform SDK is below 34 (the CTS test covers S through TIRAMISU).",
      "A caller reaches IRoleManager.getDefaultApplicationAsUser or setDefaultApplicationAsUser, for example through RoleManager.getDefaultApplication / RoleManager.setDefaultApplication in framework-s (per index search), or directly over binder.",
      "The material does not show what permission check, if any, the methods perform on pre-U platforms."
     ],
     "per_major": {
      "14": "same fix: commit 8ae31de10c7c (RESTRICT AUTOMERGE cherry-pick, Change-Id Iadc97d09cf083e2525de421362c5a4bece92ee8a) in android-security-14.0.0_r20. It adds the SdkLevel import and the two checkState calls. 8/8 patched lines are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 850ce9ea3ac7 in android-security-15.0.0_r8 with the same checkState lines (SdkLevel import already present), plus the new CTS test RoleManagerSecurityTest.kt. 8/8 lines are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). 6/8 lines of the patched region are present at android-security-16.0.0_r8; the material does not show the exact form of the code there.",
      "17": "not affected (not listed by the bulletin). 6/8 lines of the patched region are present at android-security-17.0.0_r1; the material does not show the exact form of the code there."
     },
     "backport_notes": [
      "Single-file, self-contained change: insert Preconditions.checkState(SdkLevel.isAtLeastU(), ...) as the first statement of both methods and make sure com.android.modules.utils.build.SdkLevel is imported (com.android.internal.util.Preconditions is already imported in RoleService). SdkLevel from modules-utils is the standard SDK-level check available to mainline module code (general platform knowledge).",
      "The Android 14 branch carried the change as RESTRICT AUTOMERGE without the CTS test. Take RoleManagerSecurityTest.kt from the Android 15 commit if you want test coverage."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the permission-enforcement code inside getDefaultApplicationAsUser/setDefaultApplicationAsUser, that is, how MANAGE_DEFAULT_APPLICATIONS is checked and how that behaves on pre-U platforms.",
      "The material does not explain why the bulletin lists 14, 15 and 15-next as affected while the fix and its test target platforms below U.",
      "The state of the patched region on Android 16/17 (6/8 lines present) and whether the check exists there in a different form."
     ],
     "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-26426": {
     "cve": "CVE-2025-26426",
     "summary": "Removes the special case in ActivityManagerService.registerReceiverWithFeature that let any caller pass \"android\" as callerPackage without being validated against the calling process's package list, and then widens the exemption from SYSTEM_UID to all core uids so system components in other core processes keep working.",
     "component": "platform/frameworks/base, ActivityManagerService (broadcast receiver registration)",
     "weakness": "Improper caller identity validation: an explicit bypass of the callerPackage ownership check for the literal package name \"android\"",
     "root_cause": "In services/core/java/com/android/server/am/ActivityManagerService.java, registerReceiverWithFeature verified that the supplied callerPackage is actually running in the calling process (callerApp.getPkgList().containsKey(callerPackage)) unless the caller was SYSTEM_UID, but it also skipped the check whenever callerPackage equaled \"android\" (`&& !\"android\".equals(callerPackage)`). A caller with a process record known to AMS could therefore supply \"android\" as its package name without the ownership check; in the shown context callerPackage is then passed to isInstantApp and mUserController.handleIncomingUser.",
     "fix_analysis": [
      "Commit d48f297f2165 (first): ActivityManagerService.java:registerReceiverWithFeature drops the `&& !\"android\".equals(callerPackage)` clause, so a non-SYSTEM_UID caller whose process does not contain callerPackage now hits the SecurityException(\"Given caller package ... is not running in process ...\") regardless of the name supplied.",
      "Commit 93c036bb67be (second, same hunk): replaces `callerApp.info.uid != SYSTEM_UID` with `!UserHandle.isCore(callerApp.info.uid)`. Per the commit message, system components can run in any core-uid process and register receivers as \"android\"; the exemption from the package-list check is extended from exactly SYSTEM_UID to all core uids.",
      "Resulting check at android-security-14.0.0_r29 line 13827: `if (!UserHandle.isCore(callerApp.info.uid) && !callerApp.getPkgList().containsKey(callerPackage)) throw new SecurityException(...)`."
     ],
     "completeness": [
      "The fix is confined to the single check in registerReceiverWithFeature; the deprecated registerReceiver wrapper is covered because it delegates to registerReceiverWithFeature (lines 13802-13807).",
      "The \"android\" bypass is removed for all non-core callers; no other package name was special-cased in this hunk.",
      "The second commit loosens the check for every core uid (not only SYSTEM_UID): core-uid callers can still register with any callerPackage value without the package-list check. This is intended per the commit message, but it means callerPackage is not validated for core uids.",
      "Other AMS entry points that take a callerPackage string are not touched by these commits; the material does not show whether they have a comparable special case."
     ],
     "behavior_changes": [
      "Non-core-uid callers that pass \"android\" (or any package not in their process) as callerPackage to registerReceiver/registerReceiverWithFeature now receive a SecurityException instead of a successful registration.",
      "Callers in core uids other than SYSTEM_UID are newly exempt from the package-list check (previously only SYSTEM_UID was exempt); per the commit message this keeps system components in core-uid processes able to register receivers as \"android\".",
      "Warning path at line 13842 (UserHandle.isCore(callingUid) priority check) is unchanged."
     ],
     "preconditions": [
      "Caller must have a ProcessRecord known to AMS (getRecordForAppLOSP(caller) non-null) and must not be an isolated process (enforceNotIsolatedCaller is applied first).",
      "Caller supplies callerPackage = \"android\" to registerReceiver or registerReceiverWithFeature while running in a non-SYSTEM_UID process."
     ],
     "per_major": {
      "14": "same fix: both commits (d48f297f2165 then 93c036bb67be) applied in android-security-14.0.0_r20; at r29 the patched region shows the final `!UserHandle.isCore(...)` form (4/5 lines present).",
      "15": "same fix for the first commit: 475f9914f716 in android-security-15.0.0_r8 removes the \"android\" clause with identical diff (line 14688). The material does not show the isCore follow-up on 15; at 15.0.0_r17 only 3/5 lines of the patched region are present, so whether the second commit landed there is unknown.",
      "16": "not affected (not listed by the bulletin); at android-security-16.0.0_r8, 4/5 lines of the patched region are present.",
      "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": [
      "Apply in order: first remove the `!\"android\".equals(callerPackage)` clause (d48f297f2165), then switch `callerApp.info.uid != SYSTEM_UID` to `!UserHandle.isCore(callerApp.info.uid)` (93c036bb67be); per the second commit's message, system components in core uids other than SYSTEM_UID register receivers as \"android\" and need the widened exemption.",
      "The change is a one-line condition in a single method; UserHandle.isCore is already used a few lines below (line 13842), so no new imports are needed."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the isCore follow-up (93c036bb67be) was applied on Android 15 (only the first commit is shown for 15).",
      "Which downstream consumers of the spoofed callerPackage make this EoP per the bulletin; the material only shows the check and its use in isInstantApp and handleIncomingUser, not the receiver record.",
      "Whether any permission check beyond enforceNotIsolatedCaller gates this path.",
      "Whether other AMS APIs accepting a callerPackage have a similar \"android\" special case."
     ],
     "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-26427": {
     "cve": "CVE-2025-26427",
     "summary": "Extends the Android 11 SAF privacy restriction on /Android/data, /Android/obb and /Android/sandbox to their whole subtrees. ExternalStorageProvider now hides those subtrees through a new FileSystemProvider.shouldHideDocument() hook, and the DocumentsUI picker no longer launches to an EXTRA_INITIAL_URI inside them.",
     "component": "platform/packages/apps/DocumentsUI (picker ActionHandler), platform/frameworks/base (FileSystemProvider / ExternalStorageProvider), platform/packages/providers/DownloadProvider (DownloadStorageProvider)",
     "weakness": "Incomplete access restriction: the path deny-lists matched only the exact restricted directories (patterns anchored with \"$\", on non-canonicalized paths), not their subdirectories",
     "root_cause": "Two deny-lists were too narrow.\n\n1. DocumentsUI picker/ActionHandler.launchToInitialUri(): the old PATTERN_BLOCK_PATH (\".*:android\\\\/(?:data|obb|sandbox)$\") was matched against the lowercased raw URI path. It only matched when that path ended exactly in Android/data, Android/obb or Android/sandbox. A document URI for any subdirectory of those passed the check, and launchToDocument() loaded the picker directly into it.\n\n2. Provider side: FileSystemProvider.shouldHide() used PATTERN_HIDDEN_PATH (\"^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$\"), which again covers only the exact directories. As the fix comment states, ExternalStorageProvider \"hides\" only Android/data/, Android/obb/ and Android/sandbox/, \"but NOT their subdirectories\".\n\nThe DocumentsUI path was not canonicalized before matching. Together, this let a requesting app steer the ACTION_OPEN_DOCUMENT / ACTION_OPEN_DOCUMENT_TREE picker into restricted subtrees that the Android 11 restriction is meant to exclude.",
     "fix_analysis": [
      "frameworks/base core/java/com/android/internal/content/FileSystemProvider.java:\n- removes PATTERN_HIDDEN_PATH, shouldHide(File) and shouldShow();\n- adds an overridable shouldHideDocument(String documentId) (default false), plus a final File variant that maps through getDocIdForFile();\n- renames shouldBlockFromTree() to shouldBlockDirectoryFromTree().",
      "FileSystemProvider.queryChildDocuments(): the old variants (public queryChildDocuments, queryChildDocumentsShowAll, and a private predicate variant) are collapsed into one protected queryChildDocuments(documentId, projection, sortOrder, boolean includeHidden).\n- The public 3-arg overload calls it with includeHidden=false.\n- queryChildDocumentsForManage() becomes final and calls it with includeHidden=true.\n- When includeHidden is false, the queried directory itself and each child are skipped if shouldHideDocument() is true.",
      "FileSystemProvider.querySearchDocuments(): the BFS is rewritten with an ArrayDeque. It skips any file or directory for which shouldHideDocument(file) is true, so hidden subtrees are not descended into. The loop condition is now getCount() < MAX_RESULTS_NUMBER (23).",
      "FileSystemProvider.includeFile(): FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE is now set on any directory where shouldBlockDirectoryFromTree(docId) is true, whether or not file.canWrite() is true. Previously it was set only inside the canWrite branch.",
      "frameworks/base packages/ExternalStorageProvider ExternalStorageProvider.java:\n- implements shouldHideDocument(documentId): it returns false on removable USB roots (new isOnRemovableUsbStorage()); otherwise it matches the canonicalized getPathFromDocId() result against PATTERN_RESTRICTED_ANDROID_SUBTREES \"^Android/(?:data|obb|sandbox)(?:/.+)?\" (CASE_INSENSITIVE).\n- rewrites shouldBlockDirectoryFromTree(): it blocks the storage root, Download, Android, and anything shouldHideDocument() hides, and no longer wraps IOException into IllegalArgumentException.\n- getPathFromDocId() no longer throws; on IOException it logs and returns \"\".\n- removes the old queryChildDocumentsForManage() override.\n- resolveDocId/buildFile drop the 'visible' parameter.",
      "DownloadProvider DownloadStorageProvider.java: drops its own queryChildDocuments()/queryChildDocumentsForManage() overrides and instead overrides the protected queryChildDocuments(documentId, projection, sortOrder, includeHidden). It passes includeHidden through to super and to includeDownloadsFromMediaStore(), where the old 'manage' flag was used.",
      "DocumentsUI src/com/android/documentsui/picker/ActionHandler.java launchToInitialUri(): removes PATTERN_BLOCK_PATH and the loadDeviceRoot() redirect. It now classifies the URI:\n- root URI: loadRoot();\n- neither root nor document: return false;\n- document URI: calls the new shouldPreemptivelyRestrictRequestedInitialUri(uri). If that returns true, it logs a warning and returns false; otherwise it calls launchToDocument(uri).\nThe commit message says a restricted request is redirected to the default location (last accessed stack).",
      "ActionHandler.shouldPreemptivelyRestrictRequestedInitialUri():\n- returns false if !Shared.shouldRestrictStorageAccessFramework(mActivity), or if the authority is not Providers.AUTHORITY_STORAGE;\n- otherwise canonicalizes the docId path via the new FileUtils.getPathFromStorageDocId() and returns true if it matches PATTERN_RESTRICTED_INITIAL_PATH \"^/Android/(?:data|obb|sandbox).*\" (CASE_INSENSITIVE);\n- an IOException during canonicalization is treated as restricted;\n- a hard-coded externalProviderImplementsSafRestrictions=false keeps this client-side check active.",
      "DocumentsUI src/com/android/documentsui/util/FileUtils.java (new file): getCanonicalPath(String) and getPathFromStorageDocId(String docId). The latter strips the \"root:\" prefix and canonicalizes with File.getCanonicalPath(), without trimming slashes.",
      "DocumentsUI AbstractActionHandler.launchToDocument()/onStackToLaunchToLoaded(): refactor only (early return on archive URI, callback renamed from onStackLoaded, debug logging). The stack-reset and metrics behaviour is unchanged."
     ],
     "completeness": [
      "Provider side: ExternalStorageProvider.shouldHideDocument() is consulted in FileSystemProvider.queryChildDocuments() (non-manage path) and querySearchDocuments(), and it feeds shouldBlockDirectoryFromTree(). It covers the full Android/data, Android/obb and Android/sandbox subtrees on non-USB roots. The material does not show shouldHideDocument() being applied in queryDocument(), openDocument() or findDocumentPath().",
      "Client side: the DocumentsUI guard exists only in picker ActionHandler.launchToInitialUri(). It applies only to document URIs with authority Providers.AUTHORITY_STORAGE, and only when Shared.shouldRestrictStorageAccessFramework(mActivity) is true. Root URIs go straight to loadRoot() without the check, and other authorities are not inspected.",
      "The DocumentsUI pattern \"^/Android/(?:data|obb|sandbox).*\" has no path separator after the directory name, so it also matches siblings that share the prefix (for example /Android/database). This over-restricts rather than under-restricts. The ExternalStorageProvider pattern requires a separator.",
      "FileSystemProvider's default shouldHideDocument() returns false, so the old absolute-path hiding (PATTERN_HIDDEN_PATH) no longer applies to other FileSystemProvider subclasses unless they override the hook. In these diffs only ExternalStorageProvider overrides it.",
      "The DocumentsUI check is explicitly kept active (externalProviderImplementsSafRestrictions=false) even when the provider fix is present."
     ],
     "behavior_changes": [
      "When the picker is launched with an EXTRA_INITIAL_URI document inside Android/data, Android/obb or Android/sandbox (or any subdirectory) on com.android.externalstorage.documents, launchToInitialUri() no longer opens it and logs a warning. Per the commit message the picker goes to the default (last accessed) location; previously the exact directories were redirected to the device root.",
      "ExternalStorageProvider child listings and search results omit Android/data, Android/obb, Android/sandbox and everything beneath them on non-USB roots, except when queried via queryChildDocumentsForManage(). Hidden directories are also flagged FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE.",
      "FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE is now set on blocked directories even when they are not writable.",
      "Subclass API changes: FileSystemProvider.queryChildDocumentsForManage() is final; queryChildDocumentsShowAll() and shouldHide(File) are removed; shouldBlockFromTree() is renamed to shouldBlockDirectoryFromTree().",
      "ExternalStorageProvider.getPathFromDocId() returns \"\" instead of throwing when canonicalization fails. shouldBlockDirectoryFromTree() propagates FileNotFoundException instead of wrapping it in IllegalArgumentException.",
      "querySearchDocuments() now returns at most 23 results (MAX_RESULTS_NUMBER); the previous '< 24' loop condition allowed up to 24."
     ],
     "preconditions": [
      "A caller starts the DocumentsUI picker with DocumentsContract.EXTRA_INITIAL_URI set to a document URI under Providers.AUTHORITY_STORAGE that points into Android/data, Android/obb or Android/sandbox (or a subdirectory). The path is ActionHandler.initLocation() -> launchToInitialUri(), reached when mFeatures.isLaunchToDocumentEnabled() is true and the stack is not already initialized (per index search).",
      "Shared.shouldRestrictStorageAccessFramework(mActivity) must be true for the DocumentsUI restriction to apply.",
      "Provider-side hiding applies to non-removable-USB roots of ExternalStorageProvider."
     ],
     "per_major": {
      "14": "same fix: frameworks/base 4af5db76f253 and DownloadProvider 5acd646e0cf6 landed in android-security-14.0.0_r5, and DocumentsUI 954b6d5397ee in android-security-14.0.0_r19. The code context at android-security-14.0.0_r29 shows the patched launchToDocument()/onStackToLaunchToLoaded(), the new imports in picker/ActionHandler.java, util/FileUtils.java, and MAX_RESULTS_NUMBER in FileSystemProvider. The 3/8 line-match metric understates this.",
      "15": "not affected (not listed by the bulletin). android-security-15.0.0_r17 shows the same 3/8 region match as the fixed 14 branch.",
      "16": "not affected (not listed by the bulletin). android-security-16.0.0_r8 shows the same 3/8 region match.",
      "17": "not affected (not listed by the bulletin). android-security-17.0.0_r1 shows the same 3/8 region match."
     },
     "backport_notes": [
      "Apply frameworks/base and DownloadProvider together. FileSystemProvider.queryChildDocumentsForManage() becomes final, and queryChildDocumentsShowAll()/shouldHide()/shouldBlockFromTree() are removed or renamed. So DownloadStorageProvider, and any OEM FileSystemProvider subclass, must switch to overriding queryChildDocuments(..., boolean includeHidden) / shouldHideDocument() / shouldBlockDirectoryFromTree(). The DocumentsUI commit has no code dependency on the provider commits."
     ],
     "confidence": "high",
     "unknowns": [
      "The criteria used by Shared.shouldRestrictStorageAccessFramework() are not in the material.",
      "Whether ExternalStorageProvider.queryDocument()/openDocument()/findDocumentPath() consult shouldHideDocument() is not shown.",
      "Whether DownloadStorageProvider or other FileSystemProvider subclasses override shouldHideDocument() is not shown.",
      "The rest of ActionHandler.initLocation() after launchToInitialUri() returns false is not shown.",
      "Which of the three commits the bulletin considers the primary fix, and the fixed tags for Android 13 and 15-next, are not shown.",
      "Whether the picker UI has additional guards against navigating into restricted directories is not 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-26428": {
     "cve": "CVE-2025-26428",
     "summary": "Adds an early return in LockTaskController.startLockTaskMode so that an app-initiated request on a LOCK_TASK_AUTH_PINNABLE task that is already in mLockTaskModeTasks no longer triggers a new screen pinning request via StatusBarManagerInternal.showScreenPinningRequest.",
     "component": "platform/frameworks/base, WindowManager lock task / screen pinning (services/core/java/com/android/server/wm/LockTaskController.java)",
     "weakness": "Missing state check (lock task state not consulted before issuing a pinning request to the status bar)",
     "root_cause": "In LockTaskController.startLockTaskMode, when isSystemCaller is false and task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE (app not in the lock task allowlist), the code unconditionally called StatusBarManagerInternal.showScreenPinningRequest(task.mTaskId) to ask the user to pin the app. It did not check whether the task was already locked (already present in mLockTaskModeTasks, e.g. after the system had pinned it with isSystemCaller true). An app whose task was already pinned could therefore cause the system pinning request UI to be shown again. The bulletin rates this as a High severity EoP; the material does not describe the concrete consequence further.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/LockTaskController.java:startLockTaskMode: inside the `!isSystemCaller` block, in the `task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE` branch, a new guard `if (mLockTaskModeTasks.contains(task)) { Slog.i(TAG_LOCKTASK, \"Already locked.\"); return; }` is inserted before the existing ProtoLog and the StatusBarManagerInternal.showScreenPinningRequest(task.mTaskId) call. If the task is already in lock task mode, the method returns without showing a pinning request.",
      "services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java:testStartLockTaskMode_pinnedBySystem: extended so that after the system pins the task, an app call `startLockTaskMode(tr, false, TEST_UID)` is verified to never invoke mStatusBarManagerInternal.showScreenPinningRequest(anyInt())."
     ],
     "completeness": [
      "Covers the app-caller (isSystemCaller == false) path for tasks with LOCK_TASK_AUTH_PINNABLE, which is the only place in startLockTaskMode that calls showScreenPinningRequest; other callers outside this method are not shown.",
      "The guard checks only whether the same Task object is in mLockTaskModeTasks; a request for a different PINNABLE task while another task is locked is not affected by this change (the code still proceeds to showScreenPinningRequest in that case). Whether that matters is not shown by the material.",
      "The `task.mLockTaskUid = callingUid;` assignment still executes before the new check, so mLockTaskUid is updated even when the request is rejected as already locked.",
      "The allowlisted branch (`else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED)` -> stopLockTaskMode then setLockTaskMode) and the isSystemCaller path are untouched by this fix."
     ],
     "behavior_changes": [
      "An app calling startLockTask on its already-locked PINNABLE task no longer causes the system UI pinning request to be displayed; the call becomes a no-op logged at info level with TAG_LOCKTASK \"Already locked.\".",
      "No new exception or error is returned to the caller; the method simply returns."
     ],
     "preconditions": [
      "Caller reaches LockTaskController.startLockTaskMode with isSystemCaller == false (the app-initiated startLockTask() path referenced in the code comment; the system path goes through ActivityTaskManagerService#startSystemLockTaskMode per the javadoc).",
      "The target Task has mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE, i.e. the app is not in the lock task allowlist.",
      "The Task is already present in mLockTaskModeTasks (already locked/pinned, e.g. after the system re-entered with isSystemCaller true following a pinning request)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base d4cd532ad022 in android-security-14.0.0_r20 (Change-Id Ic5d2125d39e5e98301d79c8f5aa8986a44e06794); at android-security-14.0.0_r29 all 8/8 patched lines are present.",
      "15": "same fix: platform/frameworks/base afd05dc17e02 in android-security-15.0.0_r8, identical diff (same hunk, same test change); at android-security-15.0.0_r17 8/8 lines present.",
      "16": "not affected per the bulletin (not listed); at android-security-16.0.0_r8 the patched region is present (8/8 lines).",
      "17": "not affected per the bulletin (not listed); at android-security-17.0.0_r1 7/8 lines of the patched region are present; which line differs is not shown."
     },
     "backport_notes": [
      "Single-commit, four-line guard relying on mLockTaskModeTasks and Slog, both already used in LockTaskController; check that TAG_LOCKTASK is defined/imported in the target branch, since the shown pre-fix context does not use it."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the concrete security impact behind the High/EoP rating (what repeated pinning requests enable).",
      "The material does not show the binder entry point or permission checks between an app's startLockTask() call and LockTaskController.startLockTaskMode.",
      "The exact one-line difference in the patched region at android-security-17.0.0_r1 is not shown.",
      "Whether TAG_LOCKTASK is available in LockTaskController on older branches 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-26429": {
     "cve": "CVE-2025-26429",
     "summary": "Adds a cap of about 2000 (NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) on the total number of attributed op entries that AppOpsService packs into the OpEntry list returned for IAppOpsService#getPackagesForOpsForDevice, so the reply is less likely to exceed the binder transaction limit.",
     "component": "platform/frameworks/base, services/core AppOps (com.android.server.appop.AppOpsService)",
     "weakness": "Missing bound on result size (unbounded collection returned over binder)",
     "root_cause": "The helper in AppOpsService.java that builds the List<OpEntry> result loops over pkgOps or over the requested ops[] array, calling getOpEntryForResult(curOp). It added every OpEntry with no limit. Each OpEntry carries its attributed op entries (per attribution tag). Per the commit message, when an app uses many attribution tags across many ops, the PackageOps payload for IAppOpsService#getPackagesForOpsForDevice exceeds the binder transaction limit. The new constant's Javadoc says the cap is there to avoid TransactionTooLargeException.",
     "fix_analysis": [
      "AppOpsService.java (class constants): adds private static final int NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000, documented as the upper limit of attributed op entries returned in one binder transaction. Per the commit message, it was sized assuming each attribution tag is 50 bytes long.",
      "AppOpsService.java (result-building helper calling getOpEntryForResult): adds int totalAttributedOpEntryCount = 0 before the two loops. Both branches work the same way. Before building each entry the loop runs `if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) break;`. It then builds the OpEntry, adds it to resOps, and adds opEntry.getAttributedOpEntries().size() to the counter. The two branches are ops == null (all ops of the package) and ops != null (requested ops only).",
      "After both loops, if the counter is over the threshold the helper logs Slog.w(TAG, ...) saying the result is throttled and may be due to a DoS attack. It then returns resOps, which may be truncated. No exception is thrown and no new return code is added."
     ],
     "completeness": [
      "Both enumeration paths of the helper are bounded (all ops when ops == null, and the explicit ops[] list), and they share the same counter.",
      "The check runs before an OpEntry is added and uses a strict `>`. So the total can go past 2000 by the size of the last entry added, and a single OpEntry with many attributed entries is still added in full. The cap is enforced between ops, not inside one op's attributed entries.",
      "The cap counts entries; it does not measure parcel bytes. Per the commit message it assumes 50-byte attribution tags, so longer tags leave less margin.",
      "Only this helper is changed. The material does not show whether other IAppOpsService methods return attributed entries through a different code path.",
      "The fix does not limit how many attribution tags or attributed ops a caller can store. It only throttles the returned result."
     ],
     "behavior_changes": [
      "Callers of getPackagesForOpsForDevice (and any other caller of the same helper) may get a truncated OpEntry list once the running attributed op entry count passes 2000. The remaining ops for that package are left out silently.",
      "A new Slog.w message with tag \"AppOps\" is logged when the threshold is exceeded.",
      "No API, error code, or permission change."
     ],
     "preconditions": [
      "The ability to call IAppOpsService#getPackagesForOpsForDevice. The material does not show which permission, if any, gates that call. GET_APP_OPS_STATS only controls whether restricted ops are included.",
      "App-op state with a very large number of attribution tags across many ops for a package. The commit message attributes this to a malicious app."
     ],
     "per_major": {
      "14": "same fix: commit 22a375717e61 in android-security-14.0.0_r20 (cherry-pick, Change-Id I43cd4b9774dbe554edcec296c4b8a3d7fc60c85c). At android-security-14.0.0_r29 the full patched region (8/8 lines) is present.",
      "15": "adapted fix: commit 0f3c01c326b6 in android-security-15.0.0_r8. Same logic, but the helper calls getOpEntryForResult(curOp, persistentDeviceId) and the surrounding context differs. 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: one new constant plus a counter around the two existing loops. Match the target branch's getOpEntryForResult signature: one argument on 14, two arguments with persistentDeviceId on 15. In Google's fix one counter is shared between the ops == null and ops != null branches."
     ],
     "confidence": "high",
     "unknowns": [
      "The diff hunks do not show the name of the patched helper method, or the full list of IAppOpsService entry points that call it.",
      "Whether untrusted apps can call getPackagesForOpsForDevice without a privileged permission is not shown.",
      "The material does not show which process receives the exception on the caller side.",
      "The Android 14 hunk does not show whether the ops[] branch there also has an opRestrictsRead check."
     ],
     "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-26436": {
     "cve": "CVE-2025-26436",
     "summary": "PendingIntentRecord.clearAllowBgActivityStarts now also downgrades the token's duration-based temp-allowlist entry from TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED to TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, instead of only removing the token from the BAL token sets; a companion commit adds equals/hashCode to BackgroundStartPrivileges for the tests.",
     "component": "platform/frameworks/base, ActivityManager PendingIntentRecord (background start / temp allowlist privileges of PendingIntents)",
     "weakness": "Incomplete revocation of privileges (privilege state not fully cleared on clear path)",
     "root_cause": "PendingIntentRecord keeps two kinds of background-start privilege state per allowlist token: the token sets mAllowBgActivityStartsForActivitySender / mAllowBgActivityStartsForBroadcastSender / mAllowBgActivityStartsForServiceSender, and the duration-based map mAllowlistDuration (TempAllowListDuration with a duration and a type). clearAllowBgActivityStarts(IBinder token) only removed the token from the three sets and left the mAllowlistDuration entry untouched (commit message: 'only cleared the tokens, but kept the duration based entries'), so an entry with type TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED stayed in place after the caller (per the commit message, SystemUI in NotificationManagerService) cleared the BAL privileges of that PendingIntent.",
     "fix_analysis": [
      "Commit 88d7046ea668 (test support only): core/java/android/app/BackgroundStartPrivileges.java makes the class final and adds equals() and hashCode() over mAllowsBackgroundActivityStarts, mAllowsBackgroundForegroundServiceStarts and mOriginatingToken; adds backgroundStartPrivilege_equals_works to BackgroundStartPrivilegesTest. No runtime behavior change beyond value equality.",
      "Commit c8ac5c6dec12 (the actual fix): services/core/java/com/android/server/am/PendingIntentRecord.java:clearAllowBgActivityStarts, after removing the token from the three sender sets, checks mAllowlistDuration != null, looks up mAllowlistDuration.get(token), and if the entry is non-null with type TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED sets its type to TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED. The entry and its duration are kept.",
      "Same commit adds @VisibleForTesting getAllowlistDurationLocked(IBinder) and makes getBackgroundStartPrivilegesForActivitySender @VisibleForTesting (was private); adds testClearAllowBgActivityStartsClearsToken and testClearAllowBgActivityStartsClearsDuration to PendingIntentControllerTest, the latter asserting the entry stays non-null with duration 1000 while type becomes FOREGROUND_SERVICE_NOT_ALLOWED."
     ],
     "completeness": [
      "Covers only the token passed to clearAllowBgActivityStarts; the three token sets and the mAllowlistDuration entry for that token are handled.",
      "The mAllowlistDuration entry is not removed; only type FOREGROUND_SERVICE_ALLOWED is rewritten to FOREGROUND_SERVICE_NOT_ALLOWED, so the duration entry itself remains for that token.",
      "Entries with any other type are left unchanged (the condition only matches FOREGROUND_SERVICE_ALLOWED).",
      "No change to setAllowlistDurationLocked, setAllowBgActivityStarts or the send path; the fix is confined to the clear path. The commit message states clearAllowBgActivityStarts is used exclusively by SystemUI (in NotificationManagerService).",
      "Not flag guarded (Flag: EXEMPT bugfix)."
     ],
     "behavior_changes": [
      "After clearAllowBgActivityStarts(token), the mAllowlistDuration entry for that token has type FOREGROUND_SERVICE_NOT_ALLOWED instead of FOREGROUND_SERVICE_ALLOWED; the entry and its duration remain.",
      "BackgroundStartPrivileges is now final and has value-based equals/hashCode (commit says used only in tests for now)."
     ],
     "preconditions": [
      "A PendingIntentRecord for which setAllowlistDurationLocked was called with TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED for an allowlist token (the test uses REASON_NOTIFICATION_SERVICE / \"NotificationManagerService\").",
      "A subsequent clearAllowBgActivityStarts(token) on that record; per the commit message this is used exclusively by SystemUI (NotificationManagerService)."
     ],
     "per_major": {
      "14": "same fix: both commits (88d7046ea668 equals/hashCode, c8ac5c6dec12 clearAllowBgActivityStarts duration downgrade, the latter RESTRICT AUTOMERGE) are in android-security-14.0.0_r20; at android-security-14.0.0_r29 7/8 lines of the patched region are present.",
      "15": "same fix: resolved in android-security-15.0.0_r8; the material shows the equals/hashCode commit (bba26504af51) as an identical diff at different line offsets. The PendingIntentRecord commit for 15 is not shown; 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 present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply 88d7046ea668 (BackgroundStartPrivileges equals/hashCode) before c8ac5c6dec12: the new PendingIntentControllerTest.testClearAllowBgActivityStartsClearsToken uses assertEquals over BackgroundStartPrivileges values. The runtime fix is self-contained in PendingIntentRecord.clearAllowBgActivityStarts and needs TempAllowListDuration.type to be assignable and the two PowerWhitelistManager TEMPORARY_ALLOWLIST_TYPE_* constants in the target branch.",
      "No fork comparison section was provided, so no per-file fork verdict can be given."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the PendingIntentRecord commit as applied on Android 15 (only the equals/hashCode commit is shown), nor which 7 of 8 lines of the patched region are present in each latest tag.",
      "How the mAllowlistDuration entry is consumed on PendingIntent send, and the concrete EoP impact, are not shown beyond the bulletin rating and commit message.",
      "Whether Android 13 (listed as affected) received an equivalent change is not shown."
     ],
     "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-26438": {
     "cve": "CVE-2025-26438",
     "summary": "Adds a check in smp_process_secure_connection_oob_data that aborts LE Secure Connections OOB pairing with SMP_OOB_FAIL when the peer's OOB flag is SMP_OOB_PRESENT but no local OOB data is stored, instead of continuing with a zeroed local randomizer.",
     "component": "platform/packages/modules/Bluetooth, system/stack/smp (Security Manager Protocol, LE Secure Connections OOB pairing)",
     "weakness": "Authentication bypass: missing consistency check between the peer's claimed OOB capability and the local OOB state, with a predictable (zero) randomizer used as fallback",
     "root_cause": "In system/stack/smp/smp_act.cc, smp_process_secure_connection_oob_data() is invoked from smp_start_secure_connection_phase1() when the selected association model is SMP_MODEL_SEC_CONN_OOB, a model for which p_cb->sec_level is set to SMP_SEC_AUTHENTICATED. Per the commit message, when no local OOB data is available the stack sets the local OOB randomizer to zero (p_cb->local_random = {0}) and continues. Nothing verified that a peer which flagged OOB data as present (p_cb->peer_oob_flag == SMP_OOB_PRESENT) could actually have obtained local OOB data. Per the commit message, a malicious peer could therefore claim to have OOB data and proceed with pairing against the zero randomizer, bypassing SMP authentication.",
     "fix_analysis": [
      "system/stack/smp/smp_act.cc:smp_process_secure_connection_oob_data: right after the existing block that sets p_cb->local_random = {0} (the zero fallback the commit message describes for missing local OOB data), a new check tests p_cb->peer_oob_flag == SMP_OOB_PRESENT && !p_sc_oob_data->loc_oob_data.present.",
      "If the condition holds it logs a warning, builds a tSMP_INT_DATA with status = SMP_OOB_FAIL, posts SMP_AUTH_CMPL_EVT to the SMP state machine via smp_sm_event(), and returns, so the peer-OOB processing and the rest of the OOB phase-1 flow are not reached for that connection.",
      "The check is placed before the peer_oob_data.present handling, so it applies regardless of whether peer OOB data was locally stored."
     ],
     "completeness": [
      "Covers only the LE Secure Connections OOB path (smp_process_secure_connection_oob_data, reached via smp_start_secure_connection_phase1 for SMP_MODEL_SEC_CONN_OOB). Legacy (non-SC) pairing OOB handling is not part of the diff and the material does not show whether an analogous check exists there.",
      "The fix does not change the fallback that sets p_cb->local_random = {0}; it only prevents proceeding when the peer claims OOB while local OOB data is absent.",
      "The reverse mismatch (local OOB data present while the peer does not claim OOB) is not addressed by this change; the material does not show whether it is relevant.",
      "The check dereferences p_sc_oob_data; the diff does not add a null check and the surrounding retrieval of p_sc_oob_data is not shown."
     ],
     "behavior_changes": [
      "SC OOB pairing now fails with SMP_AUTH_CMPL_EVT / status SMP_OOB_FAIL (connection dropped per the log message) when the peer flags OOB present but no local OOB data has been stored; previously pairing continued with a zero local randomizer.",
      "New warning log line: \"local OOB data is not present but peer claims to have received it; dropping connection\" (SMP_TRACE_WARNING on 14, log::warn on 15)."
     ],
     "preconditions": [
      "LE Secure Connections pairing in progress where the selected association model is SMP_MODEL_SEC_CONN_OOB (smp_start_secure_connection_phase1 case, from code context).",
      "The remote peer has set its OOB flag so that p_cb->peer_oob_flag == SMP_OOB_PRESENT.",
      "No local OOB data is stored (p_sc_oob_data->loc_oob_data.present is false).",
      "Reachable from a remote Bluetooth peer during pairing per the commit message; the bulletin rates it High EoP."
     ],
     "per_major": {
      "14": "same fix: commit 022ac90c296b in android-security-14.0.0_r20, uses SMP_TRACE_WARNING with __func__; at android-security-14.0.0_r29 4/4 lines of the patched region present.",
      "15": "adapted fix: commit e6130675c047 in android-security-15.0.0_r8, identical logic but uses log::warn without __func__ and different line wrapping; at android-security-15.0.0_r17 4/4 lines present.",
      "16": "not affected per the bulletin (not listed); at android-security-16.0.0_r8 3/4 lines of the patched region present. The exact code is not shown in the material.",
      "17": "not affected per the bulletin (not listed); at android-security-17.0.0_r1 3/4 lines of the patched region present. The exact code is not shown in the material."
     },
     "backport_notes": [
      "Single self-contained hunk in smp_act.cc; the only per-branch adaptation is the logging macro (SMP_TRACE_WARNING with __func__ on 14 vs log::warn on 15). Place the check after the existing local_random = {0} block and before the peer_oob_data.present check.",
      "Bluetooth is a mainline module (com.android.btservices APEX, per the Test line), so the fix may also ship via module updates independent of the platform security tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "How p_cb->peer_oob_flag is populated from the peer's pairing PDU and whether it is validated elsewhere (not shown).",
      "The exact condition guarding the existing local_random = {0} block (only its tail is shown in the diff).",
      "Whether the legacy (non-SC) pairing OOB path has an analogous issue or check.",
      "The exact state of the patched region on Android 16 and 17 (only a 3/4 line match is reported) and why the bulletin does not list them.",
      "Whether p_sc_oob_data can be null at this point."
     ],
     "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-26440": {
     "cve": "CVE-2025-26440",
     "summary": "CameraService now registers its AppOps watcher with WATCH_FOREGROUND_CHANGES and blocks untrusted MODE_IGNORED clients that are not user-visible (proc state above PROCESS_STATE_BOUND_TOP). Before, it decided between mute and block based on whether the uid was \"active\". AppOpsUidStateTrackerImpl now delays committing a uid-state change that loses a capability instead of committing it immediately.",
     "component": "platform/frameworks/av (libcameraservice, CameraService::BasicClient AppOps handling) and platform/frameworks/base (AppOps, AppOpsUidStateTrackerImpl)",
     "weakness": "Missing enforcement of a foreground-only access restriction (camera client not notified of, and not blocking on, loss of foreground camera capability)",
     "root_cause": "In services/camera/libcameraservice/CameraService.cpp, BasicClient::startCameraOps() called mAppOpsManager->startWatchingMode(OP_CAMERA, pkg, cb) without the WATCH_FOREGROUND_CHANGES flag, so the client was not notified of foreground-related changes such as the app losing its camera capability. BasicClient::opChanged() handled MODE_IGNORED for untrusted uids using mUidPolicy->isUidActive(). It muted only if the uid was active, camera privacy was enabled and mute was supported. It blocked only if the uid was not active, or if camera privacy was enabled and mute was not supported. An active but not visible uid (such as a foreground service) with camera privacy disabled matched neither branch, so the client was neither muted nor blocked. The commit message states that an app that should not have camera access outside the foreground could otherwise keep it.",
     "fix_analysis": [
      "frameworks/av CameraService.cpp:CameraService::BasicClient::startCameraOps(): startWatchingMode(OP_CAMERA, ...) now passes AppOpsManager::WATCH_FOREGROUND_CHANGES, so the OpsCallback receives the notifications AppOps sends when the app loses its camera capability (per the commit message).",
      "frameworks/av CameraService.cpp:CameraService::BasicClient::opChanged(): on MODE_IGNORED, it computes procState via sCameraService->mUidPolicy->getProcState(mClientUid) and sets isUidVisible = (procState <= ActivityManager::PROCESS_STATE_BOUND_TOP). For !mUidIsTrusted, it calls setCameraMute(true) only if isUidVisible && isCameraPrivacyEnabled && supportsCameraMute(). In every other case it calls block().",
      "frameworks/base AppOpsUidStateTrackerImpl.java:updateUidProcState(): introduces hasLostCapability = (prevCapability & ~capability) != 0. The state is now committed immediately in only three cases: moving to a more important state (uidState < prevUidState), the state is unchanged and no capability was lost, or the new state is at or below UID_STATE_MAX_LAST_NON_RESTRICTED and no capability was lost. A transition that loses a capability (for example UID_STATE_TOP -> UID_STATE_FOREGROUND, as in the new test) falls through to the settle-time delay path. The clause (uidState <= MAX_LAST_NON_RESTRICTED && prevUidState > MAX_LAST_NON_RESTRICTED) was removed; it is already covered by uidState < prevUidState.",
      "frameworks/base AppOpsUidStateTrackerTest.java: adds assertions that MODE_FOREGROUND ops stay MODE_ALLOWED until the settle time elapses after capability loss, and the new test testProcStateChangesAndStaysUnrestrictedAndCapabilityRemoved."
     ],
     "completeness": [
      "The av fix covers CameraService::BasicClient (startCameraOps and opChanged) only. Per index search (android-14.0.0_r75), the sibling CameraOfflineSessionClient::startCameraOps in api2/CameraOfflineSessionClient.cpp calls startWatchingMode(OP_CAMERA, pkg, cb) without WATCH_FOREGROUND_CHANGES. This diff does not touch it.",
      "The visibility test is a single threshold (procState <= PROCESS_STATE_BOUND_TOP) and applies only inside the MODE_IGNORED branch for untrusted uids. MODE_ERRORED handling and trusted-uid handling are unchanged.",
      "handleAppOpMode(), which startCameraOps calls at open time, still uses isUidActive rather than visibility, as the r29 context shows. The fix changes only the runtime opChanged path.",
      "The frameworks/base change defers commits of capability-losing transitions by the settle time; it does not add a new check of its own."
     ],
     "behavior_changes": [
      "Camera clients registered via BasicClient::startCameraOps now receive opChanged callbacks for foreground changes (WATCH_FOREGROUND_CHANGES).",
      "Untrusted camera clients evaluated as MODE_IGNORED are now blocked unless they are visible, camera privacy is enabled and mute is supported, in which case they are muted. Active but not visible clients that were previously muted or left alone are now blocked.",
      "AppOps uid-state commits that lose a capability without moving to a more important state are deferred by the applicable settle time (TOP_STATE_SETTLE_TIME, FG_SERVICE_STATE_SETTLE_TIME or BG_STATE_SETTLE_TIME). During that window MODE_FOREGROUND ops still evaluate to MODE_ALLOWED, as the new tests assert."
     ],
     "preconditions": [
      "An app with an untrusted uid (mUidIsTrusted false) has an open camera client with a non-null mAppOpsManager, so startCameraOps registered an OpsCallback.",
      "The app's OP_CAMERA evaluation becomes MODE_IGNORED while its uid stays active but not visible (proc state above PROCESS_STATE_BOUND_TOP, for example a foreground service).",
      "In the old code, camera privacy disabled meant such a client was neither muted nor blocked. With camera privacy enabled and mute supported, it was muted rather than blocked."
     ],
     "per_major": {
      "14": "Same fix. frameworks/av b9a047c94deb and frameworks/base b90d4d01e1bf landed in android-security-14.0.0_r20 (linked by bug 290086710). At android-security-14.0.0_r29, the CameraService.cpp context shows WATCH_FOREGROUND_CHANGES and AppOpsUidStateTrackerImpl shows the hasLostCapability logic; 6/8 lines of the patched region are present.",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17, 7/8 lines of the patched region are present. The material does not show whether the change itself is there.",
      "16": "Not affected per the bulletin. At android-security-16.0.0_r8, 4/8 lines of the patched region are present. The code is not shown in the material.",
      "17": "Not affected per the bulletin. At android-security-17.0.0_r1, 3/8 lines of the patched region are present. The code is not shown in the material."
     },
     "backport_notes": [
      "Two commits are linked by bug 290086710: frameworks/av b9a047c94deb (CameraService.cpp) and frameworks/base b90d4d01e1bf (AppOpsUidStateTrackerImpl). Both landed in android-security-14.0.0_r20, so apply them together. The av change uses AppOpsManager::WATCH_FOREGROUND_CHANGES, UidPolicy::getProcState and ActivityManager::PROCESS_STATE_BOUND_TOP."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the frameworks/av commit b9a047c94deb, or an equivalent, is present on Android 15+ branches. The per-major facts give only line-match counts.",
      "The exact AppOps flow that produced MODE_IGNORED for the affected app (bug 290086710 details are not in the material).",
      "How the two commits depend on each other, beyond sharing a bug ID.",
      "Whether CameraOfflineSessionClient was addressed by a separate change or needs this one."
     ],
     "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-26442": {
     "cve": "CVE-2025-26442",
     "summary": "The Settings NotificationAccessConfirmationActivity now matches the requested notification listener against the full ComponentName of each resolved NotificationListenerService instead of only its package name, so the BIND_NOTIFICATION_LISTENER_SERVICE permission and intent-filter checks are applied to the exact requested service.",
     "component": "platform/packages/apps/Settings, notification access confirmation (CDM flow), NotificationAccessConfirmationActivity",
     "weakness": "Incorrect validation target: the identity check uses the package name instead of the component name, so the NLS permission and intent-filter checks can be satisfied by a different service in the same package.",
     "root_cause": "In its setup code (the enclosing method is not shown), NotificationAccessConfirmationActivity validates the requested component (mComponentName) before it shows the notification-access confirmation dialog. It queries the services matching NotificationListenerService.SERVICE_INTERFACE for mUserId. It then loops over the results to check that the requested service declares android.permission.BIND_NOTIFICATION_LISTENER_SERVICE and appears in the NLS query result. The loop matched on service.serviceInfo.packageName.equals(mComponentName.getPackageName()), which compares only the package. The first service from that package in the result was the one checked. If it declared the permission, the loop broke with hasNLSIntentFilter = true, even when mComponentName referred to a different service in that package that does not itself declare the permission or the NLS intent filter.",
     "fix_analysis": [
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java, NLS validation loop: the loop condition changes from service.serviceInfo.packageName.equals(mComponentName.getPackageName()) to service.serviceInfo.getComponentName().equals(mComponentName). The permission check (requiredPermission.equals(service.serviceInfo.permission)) and the hasNLSIntentFilter result now apply only to the exact requested service. A component that is not in the NLS query result now reaches the existing 'lacks an intent-filter action' branch and finish().",
      "Same file, second hunk: trailing whitespace is removed after getWindow().setCloseOnTouchOutside(false). No functional change."
     ],
     "completeness": [
      "The fix covers the single validation loop in NotificationAccessConfirmationActivity. The other existing checks (non-null component and package name, component name length, package exists, label non-empty) are unchanged.",
      "A requested component that is not itself in the NLS query result is now rejected instead of being accepted through a sibling service.",
      "The material does not show onAllow() or the CDM caller side, so it does not show whether those have equivalent component-level validation."
     ],
     "behavior_changes": [
      "Requests whose ComponentName is not itself in the NotificationListenerService query result, or which lack BIND_NOTIFICATION_LISTENER_SERVICE, are now rejected with finish() and a Slog.e, even if another service in the same package qualifies.",
      "No new strings, APIs or user-visible dialog changes. Legitimate requests for a correctly declared NLS component behave as before."
     ],
     "preconditions": [
      "NotificationAccessConfirmationActivity must be started with a ComponentName (mComponentName) and a user id (mUserId) for the target service. The '[CDM]' commit title points to the CDM notification access confirmation path, but the material does not show the exact launch intent or who may start the activity.",
      "The target package must be installed for mUserId and contain at least one service returned by the NotificationListenerService.SERVICE_INTERFACE query, while the requested component is a different service in that package."
     ],
     "per_major": {
      "14": "same fix: commit a395a242270c in android-security-14.0.0_r20 (cherry-pick of f5ed187ffb6f). At android-security-14.0.0_r29 the code context shows the getComponentName().equals(mComponentName) check in place.",
      "15": "same fix: commit 63f8849244d1 in android-security-15.0.0_r8, the same two-hunk diff shifted by one line.",
      "16": "not affected per the bulletin (not listed). 7/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed). 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-line functional change in one file, with no other commits in the material. Android 13 is listed as affected, but no Android 13 commit is included in the material."
     ],
     "confidence": "high",
     "unknowns": [
      "The Android 13 fix commit is not included in the material.",
      "The material does not show what onAllow() does with mComponentName or which caller (CDM) launches this activity. The full effect of the loose check is therefore inferred from the loop alone.",
      "The name of the method that contains the validation loop is not shown.",
      "The material does not explain why 7/8 rather than 8/8 lines of the patched region are reported present in the later tags."
     ],
     "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-26444": {
     "cve": "CVE-2025-26444",
     "summary": "Removes the code in VoiceInteractionManagerService's PackageMonitor.onHandleForceStop that ran when the current voice interactor package was force stopped. That code nulled the interactor and recognizer settings, reset the current assistant, re-ran initForUser and cleared the ROLE_ASSISTANT role holders (falling back to the roles.xml default). Now the service is only shut down and restarted.",
     "component": "platform/frameworks/base, services/voiceinteraction (VoiceInteractionManagerService, assistant role handling)",
     "weakness": "Unintended state change on a lifecycle event: a package force stop caused the system to clear the user's assistant role and voice interaction settings. The exact CWE is not evident from the diff.",
     "root_cause": "In services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java, the anonymous PackageMonitor's onHandleForceStop(Intent, String[] packages, int uid, boolean doit) checks whether any force-stopped package equals the current interactor's package (hitInt). When hitInt && doit was true, the old code shut down mImpl and then called setCurInteractor(null, userHandle), setCurRecognizer(null, userHandle), resetCurAssistant(userHandle) and initForUser(userHandle), followed by RoleManager.clearRoleHoldersAsUser(RoleManager.ROLE_ASSISTANT, 0, UserHandle.of(userHandle), ...). The commit message says this behaviour 'seems to be accidental' and grew out of fixes for b/20882522, b/121104681 and b/124450140. The net effect was that force stopping a third-party assistant reset the default assistant (role and settings) to the fallback app set in roles.xml, instead of just restarting the service.",
     "fix_analysis": [
      "VoiceInteractionManagerService.java: PackageMonitor.onHandleForceStop, hitInt && doit branch. The fix deletes setCurInteractor(null, userHandle), setCurRecognizer(null, userHandle), resetCurAssistant(userHandle) and initForUser(userHandle). The interactor, recognizer and assistant settings are no longer changed when the current interactor's package is force stopped.",
      "Same branch: the fix deletes the RoleManager.clearRoleHoldersAsUser(RoleManager.ROLE_ASSISTANT, ...) call and its failure log. The assistant role holder is no longer cleared, so it is no longer reset to the roles.xml fallback.",
      "Kept: unloadAllKeyphraseModels(), mImpl.shutdownLocked() plus setImplLocked(null) when mImpl != null, and switchImplementationIfNeededLocked(true). The service is still torn down and restarted, which preserves the original fix for b/20882522 (the assistant responds after a force stop). The comment now says 'restart the service'."
     ],
     "completeness": [
      "The fix covers only the hitInt && doit branch of onHandleForceStop (the current interactor package is force stopped). It is a pure deletion and adds no check on who triggered the force stop.",
      "The sibling hitRec && doit branch (the current recognizer, not the interactor, is force stopped) is untouched at android-security-14.0.0_r29. It still calls setCurInteractor(null, userHandle) before initRecognizer(userHandle), with a TODO saying the reason for clearing the interactor is not known.",
      "The separate path around lines 2329-2330 of the same class calls setCurInteractor(null, userHandle) and resetCurAssistant(userHandle) when the configured service is not valid. It is untouched, and it is a different condition from a force stop.",
      "According to the commit message, clearing the assistant app's data still turns off its in-app setting. That behaviour is outside the scope of this change."
     ],
     "behavior_changes": [
      "Force stopping the current assistant (voice interactor) package no longer resets the assistant role or the voice interactor and recognizer settings. The user's chosen assistant stays selected.",
      "No RoleManager.clearRoleHoldersAsUser call is made for ROLE_ASSISTANT on force stop, so the roles.xml fallback assistant is no longer applied as a side effect.",
      "initForUser(userHandle) no longer runs again on force stop. Only switchImplementationIfNeededLocked(true) restarts the implementation.",
      "The 'Failed to clear default assistant for force stop' error log can no longer appear."
     ],
     "preconditions": [
      "A voice interactor (assistant) is configured for the user, and getCurInteractor(userHandle) is non-null.",
      "PackageMonitor.onHandleForceStop is called with doit == true and a package list that contains the current interactor's package name, meaning that package is force stopped. PackageMonitor delivers this from package-restart broadcasts sent by the system (general platform knowledge).",
      "The material does not show who can cause the force stop (the user through Settings, or another process)."
     ],
     "per_major": {
      "14": "Same fix: commit c439c7e75e73 in android-security-14.0.0_r20, cherry-picked from ba9625e664c7 / 072de93c14f1, Change-Id I180e699bd8d3fb5ea6aa4807f435060999436416. At android-security-14.0.0_r29 the code context shows the post-fix onHandleForceStop body; the per-major facts report 6/8 lines of the patched region present.",
      "15": "Not affected: the bulletin does not list Android 15. 6/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Not affected: the bulletin does not list Android 16. 6/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. 6/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit that only deletes code, inside PackageMonitor.onHandleForceStop in VoiceInteractionManagerService.java. On a branch without it, delete the setCurInteractor, setCurRecognizer, resetCurAssistant and initForUser calls and the RoleManager.clearRoleHoldersAsUser block between setImplLocked(null) and switchImplementationIfNeededLocked(true). UserHandle is still used in this method."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the attack surface behind the bulletin's EoP rating (which caller can trigger the force stop of the assistant package).",
      "The material does not show whether the two cherry-pick sources (ba9625e664c7, 072de93c14f1) differ from this commit.",
      "The material does not show whether the untouched setCurInteractor(null) in the hitRec branch is considered part of the same issue.",
      "The material does not show why only 6/8 lines of the patched region match on each branch."
     ],
     "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-27363": {
     "cve": "CVE-2025-27363",
     "summary": "Restructures heap buffer allocation in external/freetype src/truetype/ttgload.c in two places. In the TrueType composite-glyph variation path (load_truetype_glyph), the temporary outline is now allocated directly into the FT_Outline fields with explicit sizes, and n_points/n_contours are set only after allocation succeeds. In TT_Process_Simple_Glyph, the unrounded array switches to a non-zeroing allocation.",
     "component": "platform/external/freetype, TrueType glyph loader (src/truetype/ttgload.c, GX variation support)",
     "weakness": "unknown from the diff alone. The change only touches heap buffer allocation and sizing of the outline arrays in the composite-glyph variation path, and adds no explicit new check.",
     "root_cause": "In load_truetype_glyph, a composite glyph loaded for a non-default variation instance (TT_CONFIG_OPTION_GX_VAR_SUPPORT) gets a temporary FT_Outline that passes the subglyph anchor points plus the 4 phantom points to TT_Vary_Apply_Glyph_Deltas. The old code did this in order: (1) set outline.n_contours = outline.n_points = limit (a short) before any allocation; (2) allocated four separate local arrays (points, tags, contours, unrounded), each of limit + 4 entries, with zeroing FT_NEW_ARRAY; (3) filled the locals; (4) only then attached them to the outline. The fix changes the buffer layout and sizing of this temporary outline. The material does not state what memory-safety fault the old layout caused.",
     "fix_analysis": [
      "src/truetype/ttgload.c:load_truetype_glyph: the locals points/tags/contours are removed. The FT_Outline is zero-initialized ({ 0, 0, NULL, NULL, NULL, 0 }) and its points/tags/contours members are allocated directly with FT_QNEW_ARRAY. Sizes: points gets limit + 4 entries (limit anchor points plus pp1..pp4), tags and contours get limit entries each, and unrounded gets limit + 4.",
      "src/truetype/ttgload.c:load_truetype_glyph: outline.n_contours = outline.n_points = limit is now set only after all allocations succeed. If an allocation fails (goto Exit1), the outline has zero counts. Before, it could be left with non-zero counts and arrays not yet attached.",
      "src/truetype/ttgload.c:load_truetype_glyph: the fill loop writes outline.points[i], outline.tags[i] = ON_CURVE_POINT (was literal 1) and outline.contours[i] = i, then appends loader->pp1..pp4 at outline.points[limit..limit+3]. The readback after the deltas are applied uses outline.points[i].x/.y.",
      "src/truetype/ttgload.c:TT_Process_Simple_Glyph: FT_NEW_ARRAY(unrounded, n_points) becomes FT_QNEW_ARRAY, so the array is no longer zeroed."
     ],
     "completeness": [
      "Covers only the two allocation sites in ttgload.c: the composite-glyph temporary outline in load_truetype_glyph and the unrounded array in TT_Process_Simple_Glyph.",
      "No validation of limit (a short derived from the subglyph count) or of n_points is added. The fix relies on the allocation sizes now matching how the arrays are indexed in this function (points: limit + 4; tags/contours: limit).",
      "This commit does not change TT_Vary_Apply_Glyph_Deltas itself or its other callers.",
      "The diff is complete per the diffstat (18 insertions, 29 deletions) and does not modify the Exit1 cleanup. What Exit1 frees is not shown."
     ],
     "behavior_changes": [
      "Allocated arrays are no longer zero-filled (FT_QNEW_ARRAY instead of FT_NEW_ARRAY).",
      "The tags and contours arrays of the temporary composite outline shrink from limit + 4 to limit entries.",
      "On allocation failure in the composite variation path, the temporary outline has n_points = n_contours = 0 (before, both were set to limit before allocation).",
      "No new error codes are introduced in the diff."
     ],
     "preconditions": [
      "FreeType is built with TT_CONFIG_OPTION_GX_VAR_SUPPORT, and a TrueType variable font is loaded with a non-default variation instance (!IS_DEFAULT_INSTANCE).",
      "For the load_truetype_glyph change, the glyph being loaded is a composite glyph with subglyphs. For the TT_Process_Simple_Glyph change, it is a simple glyph.",
      "Per index search, frameworks/base/libs/hwui/utils/TypefaceUtils.h exposes FreeTypeFontMgr(), which turns font bytes into an SkTypeface using FreeType. The minikin snippets returned (LayoutCore.cpp, Font.cpp) use HarfBuzz, and the material does not show their link to FreeType."
     ],
     "per_major": {
      "14": "same fix: DO NOT MERGE cherry-pick f9c146bda91f in android-security-14.0.0_r20 (Change-Id I647f2145da6f0a7ab862e7b31632ad3199d9d2ad). At android-security-14.0.0_r29, 5/8 lines of the patched region are present, and the code context shows the FT_QNEW_ARRAY form at line 979.",
      "15": "not affected per the bulletin. 5/8 lines of the patched region are present at android-security-15.0.0_r17. The exact code state is not shown.",
      "16": "not affected per the bulletin. 5/8 lines of the patched region are present at android-security-16.0.0_r8. The exact code state is not shown.",
      "17": "not affected per the bulletin. 4/8 lines of the patched region are present at android-security-17.0.0_r1. The exact code state is not shown."
     },
     "backport_notes": [
      "Single-commit fix, self-contained in src/truetype/ttgload.c, and it does not touch the Exit1 cleanup. When applying it to an older FreeType, confirm that Exit1 frees outline.points/tags/contours and that ON_CURVE_POINT is defined in that tree. external/freetype is a platform library consumed by Skia/hwui, not a mainline module, so the fix ships with the platform image (general platform knowledge).",
      "The material has no comparison against the fork, so no per-file fork verdict can be given."
     ],
     "confidence": "medium",
     "unknowns": [
      "The commit message and diff do not say what memory-safety fault the old allocation layout caused (which index exceeded which buffer). The commit is described as a cleanup.",
      "What the Exit1 cleanup frees (it is unchanged by this commit and not shown).",
      "Whether TT_Vary_Apply_Glyph_Deltas fully writes the unrounded array.",
      "Whether Android 13 received the same cherry-pick (the bulletin lists 13 as affected, but the material only shows the Android 14 commit).",
      "The exact code state on the Android 15/16/17 branches; only partial line-match counts are given."
     ],
     "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."
     }
    }
   }
  },
  "15": {
   "counts": {
    "announced": 16,
    "certain": 16,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2023-35657",
     "osv_id": "ASB-A-260230151",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In system/bta/av/bta_av_aact.cc, bta_av_setconfig_rej now builds the .reject payload's bd_addr from the stream control block (p_scb->PeerAddress()) instead of reading p_data->str_msg.bd_addr, which assumed a union member that is not always the one actually passed.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained one-line change in bta_av_setconfig_rej. It assumes tBTA_AV_SCB provides PeerAddress(), as it does in the Android 14 and 15 fixes."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "655dbc919736f0a67c5d408e91b6c0a47fb7ccc3",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-0077",
     "osv_id": "ASB-A-360838273",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restores locking the device (keyguard shown and waited for) at the beginning of a foreground user switch in UserController.startUser, moves the IUserSwitchObserver.onBeforeUserSwitching dispatch to that same early point, and makes onBeforeUserSwitching a synchronous binder call so observers (SystemUI user tracking) have updated the current user before the keyguard is locked.",
      "confidence": "medium",
      "backport_notes": [
       "Apply in order: a09b6451c99f (revert) first, then 37a4df78c7e1 (its context depends on the revert's startUser code), then 3b04c948727c (AIDL). The revert's message requires a companion fix in WindowManagerService.lockDeviceNow() that is not in this material, so verify it is present in the target tree.",
       "The AIDL change alters IUserSwitchObserver binder semantics for every registered observer (SystemUI per the commit message). Check other observer implementers in the fork, such as Car services (per index search only CarUserServiceTest user-switch events are shown). Later Android 15 (r17) uses onBeforeUserSwitching(int, IRemoteCallback reply) on a oneway interface, so pick one consistent approach."
      ],
      "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_r6",
      "date": "2025-03-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "37a4df78c7e1b91066b341b05fb767f27c5da835",
       "tag": "android-security-15.0.0_r6",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "3b04c948727c35e6ad429eefc6aaa9c261addf12",
       "tag": "android-security-15.0.0_r6",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "a09b6451c99f8aa99c49a0e584e12be455c414f4",
       "tag": "android-security-15.0.0_r6",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "5f59ac63cb7042d58dae196e890ec52424ebe8b5",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "first_commit_before_announcement_days": 59,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-0087",
     "osv_id": "ASB-A-333681693",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "UninstallerActivity now only accepts an uninstall request for a different user when the calling user is that user's profile parent (UserManager.getProfileParent), instead of accepting any user contained in UserManager.getUserProfiles().",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. The only functional hunk is the user check in UninstallerActivity. On a fork branch, apply it where the getUserProfiles().contains(mDialogInfo.user) block exists, and remove the java.util.List import only if nothing else in the file uses it.",
       "UserManager.getProfileParent(UserHandle) is a system API requiring MANAGE_USERS or INTERACT_ACROSS_USERS, which the PackageInstaller system app holds (general platform knowledge)."
      ],
      "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_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": "4c269d7b0ec71951f773844b2a325e556f982a9c",
       "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": 59,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26421",
     "osv_id": "ASB-A-344865740",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a device-configurable list of \"biometric protected\" packages (frameworks/base resource config_biometric_protected_package_names) and makes Settings require a lock-screen confirmation (AppInfoDashboardFragment.showLockScreen) before disabling, force-stopping or uninstalling such a package from the App info UI, in both the legacy AppButtonsPreferenceController and the SPA PackageInfoPresenter.",
      "confidence": "high",
      "backport_notes": [
       "Two-repo dependency: the Settings change references com.android.internal.R.array.config_biometric_protected_package_names, so the frameworks/base config.xml and symbols.xml commit must be in the same build. The Settings change also calls AppInfoDashboardFragment.showLockScreen without adding it, so that method must already exist in the target branch.",
       "The fix does nothing unless the device overlays the string-array, which is empty by default. To get any protection, populate it through a product overlay with the packages you intend to protect."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b66817d2ed3b6ef29873bfe9857081cdef63681f",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "f16fa58405ed94703ea1886c483a2e2ce1c2b176",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-26422",
     "osv_id": "ASB-A-383328827",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Moves the DumpUtils.checkDumpPermission check in WindowManagerService from the private doDump helper into the Binder-exposed dump() entry point, before PriorityDump.dump is called, so that the --high-priority dump (named in the commit title) is permission-gated.",
      "confidence": "medium",
      "backport_notes": [
       "Single one-line move with no dependencies. In downstream trees, check that doDump is reached only through the checked dump() and that any OEM-added dump entry points also go through dump()."
      ],
      "meta": {
       "generated": "2026-10-03",
       "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-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "63ae789499395abc2b71fd46f57cac3c4ba1bd9d",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26423",
     "osv_id": "ASB-A-349550024",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds size and validity checks to WifiConfigurationUtil.validateIpConfiguration so that a WifiConfiguration whose IpConfiguration carries an oversized static DNS server list, domain string, proxy host, or proxy exclusion list, or an invalid ProxyInfo, is rejected by validate().",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit: only validateIpConfiguration and a new constant change. The Android 14 and 15 diffs have identical hunk content apart from line offsets, so it should apply to a branch where validateIpConfiguration already has the 'null static ip Address' check and the MAX_STRING_LENGTH constant.",
       "The new tests call the helpers createStaticIpConfigurationWithPacProxy and createStaticIpConfigurationWithStaticProxy in WifiConfigurationTestUtil; these must exist on the target branch for the tests to compile. The material does not show them."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Wifi",
       "sha": "01e708a7a9af970b3aa40cdca2cbde71d07a859b",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2025-26424",
     "osv_id": "ASB-A-341253275",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds NETWORK_STACK / MAINLINE_NETWORK_STACK permission enforcement to the four VPN profile store binder methods (get/put/remove/list) in VpnManagerService, which previously had no caller permission check.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix; it uses com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission, from the same class that already provides the enforceAnyPermissionOf import in this file.",
       "For Android 14 (partial 4/8 region match), check directly whether the four *VpnProfileStore methods exist and whether they lack the permission check; the material does not show this."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1b4ee554c8234d1ac16105c92fee2ea0803b8a39",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26425",
     "osv_id": "ASB-A-379362792",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an SdkLevel.isAtLeastU() precondition at the top of RoleService's getDefaultApplicationAsUser and setDefaultApplicationAsUser. On platforms below Android 14 (API 34), these binder methods now throw IllegalStateException instead of running.",
      "confidence": "high",
      "backport_notes": [
       "The Android 14 variant needed an explicit import of com.android.modules.utils.build.SdkLevel in RoleService.java. The Android 15 diff does not add it.",
       "Insert the precondition as the first statement in each method. Apply just the inserted lines, because the surrounding enforceCrossUserPermission call differs between versions: the Android 15 r36 index snippet shows an extra enforceForProfileGroup argument."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "850ce9ea3ac72540ce310722633d9c893a32dfdd",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2025-26426",
     "osv_id": "ASB-A-310632322",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix removes the blanket exemption that let any app process pass \"android\" as the calling package when registering a broadcast receiver in ActivityManagerService. A follow-up commit then widens the uid exemption from SYSTEM_UID to all core uids, so legitimate system components in core-uid processes can still register as \"android\".",
      "confidence": "high",
      "backport_notes": [
       "Apply both changes together (or squash them): first remove the `!\"android\".equals(callerPackage)` clause, then replace `callerApp.info.uid != SYSTEM_UID` with `!UserHandle.isCore(callerApp.info.uid)`. Shipping only the first commit breaks system components running in core uids other than SYSTEM_UID (Bug 387930030). On Android 14 only the second change is shown (around line 13810); its pre-image already lacks the \"android\" clause."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "475f9914f71641f0eedc4a8412cf48f49290a60c",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "99aae825ded253fe58695ceb853f2f631137f1c4",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26428",
     "osv_id": "ASB-A-378514614",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "LockTaskController.startLockTaskMode now returns early without showing a screen pinning request when a non-system caller asks to lock a LOCK_TASK_AUTH_PINNABLE task that is already in mLockTaskModeTasks.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. The diff adds no imports or fields: it uses mLockTaskModeTasks, TAG_LOCKTASK and Slog, which already exist in the shown Android 15 code. Place the check before the showScreenPinningRequest call inside the LOCK_TASK_AUTH_PINNABLE branch of startLockTaskMode."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "afd05dc17e027734f665ac978ad6fb4584b6fd40",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26429",
     "osv_id": "ASB-A-372678095",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a per-call cap (NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000) on the total number of attributed op entries that AppOpsService puts into the OpEntry list returned to IAppOpsService#getPackagesForOpsForDevice callers. Once the cap is passed, the result is cut short and a warning is logged, instead of building a reply that exceeds the binder transaction limit.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, self-contained change. The only version difference shown is the getOpEntryForResult signature: 15 passes persistentDeviceId, 14 does not. The bulletin lists Android 13 as affected, but the material includes no 13 commit; compare the 13 code against the 14 variant."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "0f3c01c326b6966590c652adec5baa61351756e4",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26430",
     "osv_id": "ASB-A-372895305",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SpaAppBridgeActivity.getDestinationForApp now validates that the package name taken from the incoming \"package:\" intent URI matches a package-name regex before interpolating it into the SPA navigation route, returning null otherwise.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, self-contained change to one Kotlin line plus a unit test; it applies wherever SpaAppBridgeActivity.getDestinationForApp exists with the schemeSpecificPart interpolation."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "484b4be8f3634fa0d0fed53729490b9135c644b5",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-26435",
     "osv_id": "ASB-A-337774836",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Settings content protection toggle controller now disables its main switch bar when the current user is a guest user, so a guest can no longer flip the content protection switch from that screen.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, self-contained change. The production side needs only UserManager. The test relies on com.android.settings.testutils.shadow.ShadowUserManager with getShadow() and setGuestUser(), which must exist in the target tree's robotests testutils."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "9dc0dd2c50ceb30ca5062ff3a02e48a8b4165863",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2025-26436",
     "osv_id": "ASB-A-322159724",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PendingIntentRecord.clearAllowBgActivityStarts now also downgrades the token's temp-allowlist duration entry from TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED to TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, so clearing a PendingIntent's background-start privileges no longer leaves the duration-based entry with FGS-allowed type; a companion commit adds equals/hashCode to BackgroundStartPrivileges for the tests.",
      "confidence": "high",
      "backport_notes": [
       "Apply bba26504af51 (equals/hashCode on BackgroundStartPrivileges) before ccba6717779f: the new PendingIntentControllerTest uses assertEquals on BackgroundStartPrivileges and relies on value equality. Making the class final will break any downstream subclass of BackgroundStartPrivileges.",
       "The PendingIntentRecord hunk depends on mAllowlistDuration (map of TempAllowListDuration with a mutable 'type' field) and the PowerWhitelistManager TEMPORARY_ALLOWLIST_TYPE_* constants existing in the target branch."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bba26504af51d2dd3b8eddeb96e59cac8fcb9070",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "ccba6717779fea0a86b6326f9925c36fc837738c",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-26438",
     "osv_id": "ASB-A-251514171",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a check in smp_process_secure_connection_oob_data that stops LE Secure Connections OOB pairing with SMP_OOB_FAIL when the peer flags OOB data as present but no local OOB data has been stored. Before the fix, pairing continued with a zeroed local randomizer.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single self-contained hunk. Between the 14 and 15 diffs, the only difference is the logging call (log::warn on 15, SMP_TRACE_WARNING on 14). Check that the branch has p_cb->peer_oob_flag and p_sc_oob_data->loc_oob_data.present, and insert the check after the local_random zeroing block and before the peer_oob_data handling."
      ],
      "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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "e6130675c04752947ac4779c178ce70eb959a97f",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2025-26442",
     "osv_id": "ASB-A-386216637",
     "source": "osv",
     "announced": "2025-05-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "NotificationAccessConfirmationActivity now checks that the exact requested ComponentName (not just some service in the same package) resolves for the NotificationListenerService intent and declares BIND_NOTIFICATION_LISTENER_SERVICE before it shows the notification access confirmation dialog.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single one-line logic change in NotificationAccessConfirmationActivity.java plus a whitespace-only hunk. The bulletin also lists Android 13 as affected, but no Android 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": 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_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r8",
      "date": "2025-05-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "63f8849244d1817be2729f522a75424c219e9ecb",
       "tag": "android-security-15.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    }
   ],
   "tags": [
    "android-security-15.0.0_r8"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "364387397"
     ],
     "commits": [
      {
       "project": "platform/build/release",
       "sha": "0d371027177da3deadbd23486482dce4572ff2c4",
       "subject": "AP3A: Set SPL to 2024-11-05",
       "tag": "android-security-15.0.0_r8",
       "merged": "2024-11-06",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r8"
     ],
     "tag": "android-security-15.0.0_r8"
    },
    {
     "bug_ids": [
      "372345596"
     ],
     "commits": [
      {
       "project": "platform/build/release",
       "sha": "2654c89041a0b346e4d204af214acab3a96bba1f",
       "subject": "Update aconfig flags for ap3a and trunk_staging to (ab/AP3A.241105.008).",
       "tag": "android-security-15.0.0_r8",
       "merged": "2024-10-24",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r8"
     ],
     "tag": "android-security-15.0.0_r8"
    },
    {
     "bug_ids": [
      "357763656"
     ],
     "commits": [
      {
       "project": "platform/build/release",
       "sha": "0e03cf3ce4a39a70545ebb79dcb503e0d034857f",
       "subject": "Update aconfig flags for ap3a and trunk_staging to (ab/AP3A.241005.015).",
       "tag": "android-security-15.0.0_r8",
       "merged": "2024-10-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r8"
     ],
     "tag": "android-security-15.0.0_r8"
    }
   ],
   "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-35657": {
     "cve": "CVE-2023-35657",
     "summary": "In system/bta/av/bta_av_aact.cc, bta_av_setconfig_rej now builds the .reject payload's bd_addr from the stream control block (p_scb->PeerAddress()) instead of reading p_data->str_msg.bd_addr, which assumed a union member that is not always the one actually passed.",
     "component": "platform/packages/modules/Bluetooth, BTA AV (system/bta/av/bta_av_aact.cc)",
     "weakness": "Out-of-bounds read via union member type confusion (tBTA_AV_DATA read as tBTA_AV_STR_MSG when constructed as tBTA_AV_CI_SETCONFIG)",
     "root_cause": "bta_av_setconfig_rej(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* p_data) built the .reject payload with .bd_addr = p_data->str_msg.bd_addr, so it treated the p_data union as a tBTA_AV_STR_MSG. Per the commit message, bta_av_config_ind in the same file calls bta_av_setconfig_rej in some user journeys with a p_data constructed as a tBTA_AV_CI_SETCONFIG, which is a different member of the union. Reading str_msg.bd_addr from such an object causes out-of-bounds access, per the commit message.",
     "fix_analysis": [
      "system/bta/av/bta_av_aact.cc:bta_av_setconfig_rej: the single changed line replaces `.bd_addr = p_data->str_msg.bd_addr` with `.bd_addr = p_scb->PeerAddress()`. The address is now taken from the stream control block, which the commit message says is not subject to the union confusion.",
      "Callers (bta_av_config_ind) and the union layout are not changed. The .hndl field was already taken from p_scb->hndl."
     ],
     "completeness": [
      "Removes the type-confused read of p_data->str_msg.bd_addr shown in the diff. The rest of bta_av_setconfig_rej is not shown, so other uses of p_data in it cannot be assessed from the material.",
      "The fix does not change bta_av_config_ind or how it constructs p_data. It relies on p_scb->PeerAddress() being set for the stream. The code context shows bta_av_save_addr calling p_scb->OnConnected(bd_addr) and comparing against PeerAddress().",
      "The material does not show other functions that read p_data->str_msg while being called with a different union member."
     ],
     "behavior_changes": [
      "The bd_addr in the reject payload now comes from the stream control block's peer address rather than from the incoming message union.",
      "No new error paths, return codes or validation are added. Only the data source changes."
     ],
     "preconditions": [
      "Bluetooth enabled with the BTA AV (A2DP) path in use. Per the commit message, the issue occurs in some user journeys where bta_av_config_ind calls bta_av_setconfig_rej with a p_data constructed as a tBTA_AV_CI_SETCONFIG.",
      "The exact trigger conditions inside bta_av_config_ind are not in the material."
     ],
     "per_major": {
      "14": "same fix: commit 09ae4e14509f in android-security-14.0.0_r20, the same one-line change in bta_av_setconfig_rej (hunk at line 1794 vs 1819). 2/3 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: commit 655dbc919736 in android-security-15.0.0_r8 (cherry-pick of internal 4cb4f37ffc89, Change-Id Id6cdb2b5a5e0b25d0926a83d09b68c483bd0df98). 2/3 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). 2/3 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 2/3 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained one-line change in bta_av_setconfig_rej. It assumes tBTA_AV_SCB provides PeerAddress(), as it does in the Android 14 and 15 fixes."
     ],
     "confidence": "high",
     "unknowns": [
      "The code of bta_av_config_ind and the exact conditions under which it passes a tBTA_AV_CI_SETCONFIG to bta_av_setconfig_rej are not in the material. They are only described in the commit message.",
      "The definitions and sizes of tBTA_AV_CI_SETCONFIG and tBTA_AV_STR_MSG are not shown, so the extent of the over-read is unknown.",
      "How the reject payload is delivered and consumed by upper layers is not shown.",
      "Whether p_scb->PeerAddress() always equals the address that str_msg.bd_addr carried in the legitimate case is not shown.",
      "The material does not explain why the per-major check reports 2/3 rather than 3/3 lines of the patched region in all latest 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-2025-0077": {
     "cve": "CVE-2025-0077",
     "summary": "Restores locking the device (keyguard shown and waited for) at the beginning of a foreground user switch in UserController.startUser, moves the IUserSwitchObserver.onBeforeUserSwitching dispatch to that same early point, and makes onBeforeUserSwitching a synchronous binder call so observers (SystemUI user tracking) have updated the current user before the keyguard is locked.",
     "component": "platform/frameworks/base, ActivityManager multi-user switching (services/core/java/com/android/server/am/UserController.java, core/java/android/app/IUserSwitchObserver.aidl)",
     "weakness": "Ordering/race flaw in keyguard enforcement during user switch: the lock screen for a secure target user was shown only at the end of the switch (after observers), and observer notification was asynchronous, so the keyguard could be skipped or shown late.",
     "root_cause": "Android 15 carried commit ad64f73046717374d4f9bc613307d01e93e19ebe (\"Move showing keyguard after the UserSwitchObservers\"), which removed the lock at the start of UserController.startUser (foreground path) and instead called showKeyguard()/dismissKeyguard() from completeUserSwitch via runWithTimeout, i.e. only at the end of the switch after the observers. The revert's message states this caused b/360838273 and can cause security issues, and that the keyguard must be shown at the beginning of the user switch as before. In addition, IUserSwitchObserver was a fully oneway interface, so onBeforeUserSwitching returned before SystemUI's UserTrackerImpl updated its notion of the current user. When switching from a user with lock \"None\" to a user with credentials, the keyguard could believe the current user was the old unlocked user and skip showing (per commit 3b04c948727c message). The onBeforeUserSwitching loop also lived in dispatchUserSwitch, after setCurrentUser and the lock point.",
     "fix_analysis": [
      "Commit a09b6451c99f (revert), UserController.java:startUser (foreground branch): after mInjector.getWindowManager().setCurrentUser(userId) and reportCurWakefulnessUsageEvent(), when userSwitchUiEnabled it calls setSwitchingUser(true) and, if mInjector.getKeyguardManager().isDeviceSecure(userId), mInjector.lockDeviceNowAndWaitForKeyguardShown() before the switch proceeds.",
      "Commit a09b6451c99f, UserController.Injector: adds lockDeviceNowAndWaitForKeyguardShown(). It returns immediately if getWindowManager().isKeyguardLocked(). Otherwise it registers an ActivityTaskManagerInternal.ScreenObserver, calls getWindowManager().lockDeviceNow(), and blocks on a CountDownLatch until onKeyguardStateChanged(true). It throws RuntimeException if the keyguard is not shown within 20 seconds or if interrupted. The observer is unregistered in finally.",
      "Commit a09b6451c99f, UserController.java:completeUserSwitch: removes showKeyguard()/runWithTimeout()/SHOW_KEYGUARD_TIMEOUT_MS and restores the await() chain: dismissKeyguard only when UI is enabled and the target user is not secure, then dismissUserSwitchDialog, then REPORT_USER_SWITCH_COMPLETE_MSG. Injector.dismissKeyguard regains a 2 s DISMISS_KEYGUARD_TIMEOUT_MS runOnce fallback posted via mHandler.",
      "Commit 37a4df78c7e1, UserController.java:startUser (foreground branch): adds dispatchOnBeforeUserSwitching(userId) right after updateProfileRelatedCaches() and before setCurrentUser/lockDeviceNowAndWaitForKeyguardShown. The new private dispatchOnBeforeUserSwitching() iterates mUserSwitchObservers and calls onBeforeUserSwitching(newUserId) with tracing. The equivalent loop is removed from dispatchUserSwitch.",
      "Commit 3b04c948727c, IUserSwitchObserver.aidl: drops the interface-level oneway so onBeforeUserSwitching(int) becomes a synchronous binder call. onUserSwitching, onUserSwitchComplete, onForegroundProfileSwitch and onLockedBootComplete are individually marked oneway. Per the commit message, this makes the system wait for onBeforeUserSwitching calls to complete before lockDeviceNowAndWaitForKeyguardShown runs.",
      "UserControllerTest.java: the keyguard-shown test now runs startUser on a thread and asserts REPORT_USER_SWITCH_MSG is not sent until onKeyguardStateChanged(true). onBeforeUserSwitching is now verified after startUser rather than after dispatchUserSwitch. testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown is removed."
     ],
     "completeness": [
      "Covers the foreground startUser path only: locking happens only when userSwitchUiEnabled is true and isDeviceSecure(userId) is true for the target user. If the user switch UI is disabled no lock is performed at the start of the switch.",
      "The revert's commit message says it must be submitted together with a proper fix in WindowManagerService.lockDeviceNow(). That companion change is not in the provided material, so whether the WindowManager side is complete cannot be judged from the code shown.",
      "The ordering guarantee depends on observers doing their current-user update inside the now synchronous onBeforeUserSwitching. onUserSwitching and later callbacks remain oneway.",
      "At android-security-15.0.0_r17 the code has moved on from the r6 fix. IUserSwitchObserver.aidl is again oneway with onBeforeUserSwitching(int newUserId, IRemoteCallback reply). In the shown startUser region, updateProfileRelatedCaches() is directly followed by setCurrentUser(userId) without a visible dispatchOnBeforeUserSwitching(userId) call (7/8 patched lines present). The lockDeviceNowAndWaitForKeyguardShown call is present at r17."
     ],
     "behavior_changes": [
      "startUser now blocks the calling thread for up to 20 seconds waiting for the keyguard to be shown when switching to a secure user with switch UI enabled. If the keyguard is not shown within 20 s (or the wait is interrupted), Injector.lockDeviceNowAndWaitForKeyguardShown throws a RuntimeException.",
      "In the r6 fix onBeforeUserSwitching is a synchronous binder call, so slow observers directly delay the user switch in system_server. RemoteException is ignored.",
      "IUserSwitchObserver.aidl transaction semantics changed (the interface is no longer oneway at top level and the other methods are explicitly oneway), affecting all observer implementations built against this hidden AIDL.",
      "Observers receive onBeforeUserSwitching earlier (before WindowManager setCurrentUser and before the lock) and no longer from dispatchUserSwitch.",
      "Injector.dismissKeyguard callbacks are posted to mHandler with a 2 s DISMISS_KEYGUARD_TIMEOUT_MS fallback that runs the continuation at most once. SHOW_KEYGUARD_TIMEOUT_MS and showKeyguard()/runWithTimeout() are removed."
     ],
     "preconditions": [
      "Multi-user device performing a foreground user switch (UserController.startUser with USER_START_MODE_FOREGROUND) with the user switch UI enabled (mUserSwitchUiEnabled).",
      "Target user has a secure keyguard (KeyguardManager.isDeviceSecure(userId) true). Per commit 3b04c948727c the problematic case is switching from a user whose lock is set to None to a user with Pattern/PIN/Password.",
      "Device runs the Android 15 code that included commit ad64f73046717374d4f9bc613307d01e93e19ebe (keyguard shown in completeUserSwitch rather than startUser)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29, 8/8 lines of the patched region are present.",
      "15": "Fixed in android-security-15.0.0_r6 by three frameworks/base commits: a09b6451c99f (revert, restores early lockDeviceNowAndWaitForKeyguardShown), 37a4df78c7e1 (early dispatchOnBeforeUserSwitching) and 3b04c948727c (synchronous onBeforeUserSwitching AIDL). At android-security-15.0.0_r17 the lock call is present (7/8 lines), but the AIDL has since changed to an IRemoteCallback reply form and the direct dispatchOnBeforeUserSwitching call is not visible in the shown region.",
      "16": "Not affected per the bulletin. At android-security-16.0.0_r8, 8/8 lines of the patched region are present.",
      "17": "Not affected per the bulletin. At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Apply in order: a09b6451c99f (revert) first, then 37a4df78c7e1 (its context depends on the revert's startUser code), then 3b04c948727c (AIDL). The revert's message requires a companion fix in WindowManagerService.lockDeviceNow() that is not in this material, so verify it is present in the target tree.",
      "The AIDL change alters IUserSwitchObserver binder semantics for every registered observer (SystemUI per the commit message). Check other observer implementers in the fork, such as Car services (per index search only CarUserServiceTest user-switch events are shown). Later Android 15 (r17) uses onBeforeUserSwitching(int, IRemoteCallback reply) on a oneway interface, so pick one consistent approach."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact security consequence is only referenced via b/360838273#comment22 and is not described in the material.",
      "The companion WindowManagerService.lockDeviceNow() fix mentioned by the revert is not included.",
      "Whether the dispatchOnBeforeUserSwitching(userId) call still exists elsewhere in startUser at android-security-15.0.0_r17, and which later change introduced the IRemoteCallback-based onBeforeUserSwitching.",
      "Which Android majors ever carried commit ad64f73046717374d4f9bc613307d01e93e19ebe, and which patched-region line differs in 15 r17 and 17 r1."
     ],
     "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-0087": {
     "cve": "CVE-2025-0087",
     "summary": "UninstallerActivity now only accepts an uninstall request for a different user when the calling user is that user's profile parent (UserManager.getProfileParent), instead of accepting any user contained in UserManager.getUserProfiles().",
     "component": "platform/frameworks/base, packages/PackageInstaller (UninstallerActivity)",
     "weakness": "Improper authorization / insufficient cross-user check (target user validated against getUserProfiles() membership instead of the parent-to-child relationship)",
     "root_cause": "In UninstallerActivity, when the intent supplied a target user (mDialogInfo.user != null), the code only checked that this user was contained in userManager.getUserProfiles(). The commit message states the intended rule: only the parent profile can uninstall an app that is in its child profiles. A membership test on getUserProfiles() does not enforce that parent-to-child direction, so a target user could pass the check without the caller being its profile parent.",
     "fix_analysis": [
      "packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java (user validation block): the getUserProfiles().contains(mDialogInfo.user) check is replaced. If mDialogInfo.user equals Process.myUserHandle(), the request is accepted.",
      "Otherwise the code computes isCurrentUserProfileOwner = Process.myUserHandle().equals(userManager.getProfileParent(mDialogInfo.user)). If that is false, it logs the error, calls showUserIsNotAllowed() and returns, the same way the previous rejection path did.",
      "The now-unused java.util.List import is removed. The Android 15 commit also reorders imports, with no functional effect."
     ],
     "completeness": [
      "The fix covers the one user validation site in UninstallerActivity that the diff changes: the caller's own user is allowed, a target whose profile parent is the caller is allowed, and every other target user is rejected.",
      "The branch where mDialogInfo.user == null (defaults to the calling user) is unchanged.",
      "The material shows no other place in PackageInstaller that does the same getUserProfiles()-based user check (for example the v2 UninstallLaunch UI imported in this file), so whether equivalent logic exists elsewhere cannot be seen here."
     ],
     "behavior_changes": [
      "Uninstall requests that target a user other than the caller, where the caller is not that user's profile parent, are now rejected with the existing showUserIsNotAllowed() error UI and a Log.e entry, even if the target user is in getUserProfiles().",
      "Requests that target the caller's own user, or a profile whose parent is the caller, are still accepted.",
      "The check now calls userManager.getProfileParent(user) instead of getUserProfiles()."
     ],
     "preconditions": [
      "UninstallerActivity must be launched with an intent that specifies a target user (mDialogInfo.user != null) different from the user the activity runs in.",
      "The device must have more than one user or profile, so that a target user other than the caller can be in getUserProfiles()."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 61762eea6f1e in android-security-14.0.0_r18, with the same logic change (the diff differs only in the surrounding import context: no v2 UninstallLaunch import). 8/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 4c269d7b0ec7 in android-security-15.0.0_r6 (RESTRICT AUTOMERGE cherry-pick, Change-Id Id4eb5484563fdec530d5fc89a2c5973c351fdab8). 8/8 lines are 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": [
      "This is a single self-contained commit. The only functional hunk is the user check in UninstallerActivity. On a fork branch, apply it where the getUserProfiles().contains(mDialogInfo.user) block exists, and remove the java.util.List import only if nothing else in the file uses it.",
      "UserManager.getProfileParent(UserHandle) is a system API requiring MANAGE_USERS or INTERACT_ACROSS_USERS, which the PackageInstaller system app holds (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The Android 13 fix commit and code are not in the material, so their exact form on that branch is unknown.",
      "The enclosing method of the changed check is not shown in the material.",
      "The material does not show how the target user reaches mDialogInfo.user (the intent extra parsing), nor which callers can launch UninstallerActivity with a foreign user.",
      "The material does not show exactly which users getUserProfiles() returned for a given caller, so the precise set of requests that the old check accepted is not shown.",
      "Whether the v2 PackageInstaller UI (UninstallLaunch, gated by usePiaV2) does its own user check is not shown.",
      "How the patched code came to be present on Android 16 and 17 is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-26421": {
     "cve": "CVE-2025-26421",
     "summary": "Adds a device-configurable list of \"biometric protected\" packages (frameworks/base resource config_biometric_protected_package_names) and makes Settings require a lock-screen confirmation (AppInfoDashboardFragment.showLockScreen) before disabling, force-stopping or uninstalling such a package from the App info UI, in both the legacy AppButtonsPreferenceController and the SPA PackageInfoPresenter.",
     "component": "platform/packages/apps/Settings (App info: AppButtonsPreferenceController, spa PackageInfoPresenter, Utils) plus platform/frameworks/base core/res config resource",
     "weakness": "Missing user authentication step before a sensitive action (Settings disables, force-stops or uninstalls designated protected packages without re-authentication)",
     "root_cause": "In Settings, the App info actions Disable, Force stop and Uninstall ran directly once the button or confirmation dialog was clicked. AppButtonsPreferenceController.handleDialogClick ran DisableChangerRunnable / forceStopPackage immediately, uninstallPkg started ACTION_UNINSTALL_PACKAGE immediately, and PackageInfoPresenter.disable/forceStop/startUninstallActivity called setApplicationEnabledSetting / forceStopPackageAsUser / startUninstallActivity immediately. There was no notion of a package whose modification the device owner must confirm through the keyguard, so the unlocked Settings UI alone was enough to modify such packages (bulletin: EoP, High).",
     "fix_analysis": [
      "platform/frameworks/base b66817d2ed3b: core/res/res/values/config.xml adds an empty, non-translatable string-array config_biometric_protected_package_names, documented as the list of protected packages that require biometric authentication for modification (Disable, force-stop, uninstalling updates). core/res/res/values/symbols.xml exports it as a java-symbol of type array, so it is reachable as com.android.internal.R.array.config_biometric_protected_package_names.",
      "platform/packages/apps/Settings f16fa58405ed, src/com/android/settings/Utils.java: new static isProtectedPackage(Context, String). It reads the array via context.getResources().getStringArray(...) and returns true if the array contains packageName.",
      "AppButtonsPreferenceController.java: new private requireAuthAndExecute(Runnable). When Utils.isProtectedPackage(mContext, mAppEntry.info.packageName) is true, it routes the action through AppInfoDashboardFragment.showLockScreen(mContext, ...); otherwise it runs the action directly. It wraps four paths: handleDialogClick DISABLE (DisableChangerRunnable with COMPONENT_ENABLED_STATE_DISABLED_USER), handleDialogClick FORCE_STOP (forceStopPackage), uninstallPkg (ACTION_UNINSTALL_PACKAGE intent), and the system-app button path. In that last path the former combined enable/disable branch is split: the enabled && isDisabledUntilUsed() case (logged as ACTION_SETTINGS_DISABLE_APP, sets COMPONENT_ENABLED_STATE_DEFAULT) is gated, and the pure enable case stays ungated.",
      "PackageInfoPresenter.kt: new private requireAuthAndExecute(action) with the same logic (Utils.isProtectedPackage(context, packageName) -> AppInfoDashboardFragment.showLockScreen(context) { action() }). It wraps the bodies of disable(), startUninstallActivity(forAllUsers) and forceStop(). enable() and clearInstantApp() are not wrapped.",
      "Tests: ShadowUtils gains an isProtectedPackage shadow and AppButtonsPreferenceControllerTest registers it. PackageInfoPresenterTest statically mocks Utils.isProtectedPackage and mocks a KeyguardManager, and adds *_protectedPackage tests for disable, uninstall and force-stop."
     ],
     "completeness": [
      "Covers the three modification actions (disable, force stop, uninstall) in both Settings App info implementations (legacy preference controller and SPA presenter).",
      "The protection is opt-in by configuration. The AOSP default array is empty, so Utils.isProtectedPackage returns false for every package unless the device overlays config_biometric_protected_package_names. On an unconfigured device, behavior is unchanged.",
      "The gate relies on AppInfoDashboardFragment.showLockScreen, which this diff does not modify. The PackageInfoPresenterTest helper setAuthPassesAutomatically makes KeyguardManager.isKeyguardSecure return false and then expects the action to run. This suggests that the action may proceed without authentication when there is no secure keyguard, but the material does not include the showLockScreen code that would confirm it.",
      "Only the Settings UI paths are gated. The underlying PackageManager.setApplicationEnabledSetting / ActivityManager.forceStopPackageAsUser / ACTION_UNINSTALL_PACKAGE mechanisms are unchanged.",
      "In PackageInfoPresenter, the logAction(...) metrics call still runs before requireAuthAndExecute, so the action is logged even if authentication is not completed (cosmetic).",
      "Per index search, AppInfoDashboardFragment.uninstallPkg in Settings builds the same ACTION_UNINSTALL_PACKAGE intent without a requireAuthAndExecute wrapper. The material does not show whether that path is reachable for protected packages (see potential_variants)."
     ],
     "behavior_changes": [
      "For packages listed in config_biometric_protected_package_names, Settings shows the lock screen (AppInfoDashboardFragment.showLockScreen) before disabling, force-stopping or uninstalling, and runs the action from the callback passed to showLockScreen.",
      "New framework resource com.android.internal.R.array.config_biometric_protected_package_names (empty by default), available for OEM overlay.",
      "In AppButtonsPreferenceController, the system-app button path now logs ACTION_SETTINGS_DISABLE_APP and ACTION_SETTINGS_ENABLE_APP from separate branches instead of a ternary. The functional result for unprotected packages is unchanged.",
      "No behavior change on devices that do not populate the list."
     ],
     "preconditions": [
      "Access to the Settings App info page for the target package on an unlocked device.",
      "The target package must be listed in config_biometric_protected_package_names for the new gate to apply. The material does not show which packages any device lists.",
      "Reachable via AppButtonsPreferenceController (handleDialogClick, uninstallPkg, system-app button path) and via spa PackageInfoPresenter.disable/forceStop/startUninstallActivity."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin, but the material includes no fix commit or tag for it; status unknown.",
      "14": "Adapted fix: platform/packages/apps/Settings 38fefded38a4 in android-security-14.0.0_r20 (8/8 lines present at android-security-14.0.0_r29). The logic is the same and the differences are contextual: the handleDialogClick DISABLE metric has no getPackageNameForMetric argument; PackageInfoPresenter.disable/forceStop keep reloadPackageInfo() and have no appRestrictionsApi block; ShadowUtils lives under tests/robotests/src/ instead of tests/robotests/testutils/; and the Kotlin test uses runTest/MockitoSession with @Mock fields. The material does not show the companion frameworks/base resource commit for 14.",
      "15": "Same fix: platform/frameworks/base b66817d2ed3b (resource and symbol) and platform/packages/apps/Settings f16fa58405ed, both in android-security-15.0.0_r8. 8/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin). 4/5 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). 4/5 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Two-repo dependency: the Settings change references com.android.internal.R.array.config_biometric_protected_package_names, so the frameworks/base config.xml and symbols.xml commit must be in the same build. The Settings change also calls AppInfoDashboardFragment.showLockScreen without adding it, so that method must already exist in the target branch.",
      "The fix does nothing unless the device overlays the string-array, which is empty by default. To get any protection, populate it through a product overlay with the packages you intend to protect."
     ],
     "confidence": "high",
     "unknowns": [
      "Which packages Google or OEMs place in config_biometric_protected_package_names (the AOSP default is empty).",
      "The material does not include the implementation of AppInfoDashboardFragment.showLockScreen, so it does not show what authentication it performs or how it behaves when no secure keyguard is set.",
      "Whether and how the fix was applied to Android 13, and whether the frameworks/base resource commit was applied to Android 14.",
      "Whether uninstalling updates of system apps goes through AppButtonsPreferenceController.uninstallPkg or another path.",
      "The concrete scenario that bugs 352504490 and 344865740 describe."
     ],
     "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-26422": {
     "cve": "CVE-2025-26422",
     "summary": "Moves the DumpUtils.checkDumpPermission check in WindowManagerService from the private doDump helper into the Binder-exposed dump() entry point, before PriorityDump.dump is called, so that the --high-priority dump (named in the commit title) is permission-gated.",
     "component": "platform/frameworks/base, WindowManagerService (services/core/java/com/android/server/wm)",
     "weakness": "missing permission check on one code path (the check sat in doDump, after PriorityDump routing)",
     "root_cause": "WindowManagerService.dump(FileDescriptor, PrintWriter, String[]) passed control straight to PriorityDump.dump(mPriorityDumper, fd, pw, args) without checking the caller. The DumpUtils.checkDumpPermission(mContext, TAG, pw) check was only inside the private doDump(...) helper. According to the commit title ('Check dump permissions before dumping --high-priority'), the high-priority dump was produced without first passing that check. The material does not include the mPriorityDumper implementation, so the exact unchecked route is inferred from the commit title.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowManagerService.java:dump(FileDescriptor, PrintWriter, String[]) now calls DumpUtils.checkDumpPermission(mContext, TAG, pw) first and returns early if it fails, before PriorityDump.dump(mPriorityDumper, fd, pw, args) is called.",
      "services/core/java/com/android/server/wm/WindowManagerService.java:doDump(...) no longer contains the checkDumpPermission call. Net change: 1 insertion, 1 deletion."
     ],
     "completeness": [
      "Covers every path that enters through WindowManagerService.dump(), whatever the arguments, because the check runs before PriorityDump.dump.",
      "doDump no longer has its own check. This is safe only if every caller of doDump goes through dump(). The material does not show the callers of doDump.",
      "The fix does not change PriorityDump or any other service. It is local to WindowManagerService."
     ],
     "behavior_changes": [
      "Callers that fail DumpUtils.checkDumpPermission now get no window manager dump output on any path. According to the commit title, the high-priority dump was the path that was previously reachable without the check.",
      "For callers that pass the check, the dump is unchanged. The check just runs earlier."
     ],
     "preconditions": [
      "Ability to call Binder dump on the window service, e.g. via dumpsys (frameworks/native/cmds/dumpsys/dumpsys.cpp calls service->dump, per index search) or a direct IBinder.dump call such as CarPackageManagerService.dumpWindows (per index search).",
      "A caller without dump permission that requests the high-priority dump (per the commit title). The exact argument form is not shown in the material."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin). The patched region (6/6 lines) is present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 63ae78949939 in android-security-15.0.0_r8 (cherry-picked from internal commit c617d697cdbe, Change-Id Ie4b4e0001430924674b08eabf28c656a46527727). The patched region is fully present at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin). The patched region (6/6 lines) is present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). The patched region (6/6 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single one-line move with no dependencies. In downstream trees, check that doDump is reached only through the checked dump() and that any OEM-added dump entry points also go through dump()."
     ],
     "confidence": "medium",
     "unknowns": [
      "The mPriorityDumper implementation and how PriorityDump routes arguments are not in the material, so the dumper method that skipped doDump's check is inferred only from the commit title.",
      "Whether Android 14, 16 or 17 ever had the vulnerable code is not shown. The material only shows that the patched region is present in their latest tags.",
      "The text DumpUtils.checkDumpPermission writes to pw when the check fails is not shown.",
      "Whether CarPackageManagerService and other in-tree callers of the window manager's dump hold the dump permission is not shown.",
      "The CTS test mentioned in the commit message is not included."
     ],
     "meta": {
      "generated": "2026-10-03",
      "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-26423": {
     "cve": "CVE-2025-26423",
     "summary": "Adds size and validity checks to WifiConfigurationUtil.validateIpConfiguration so that a WifiConfiguration whose IpConfiguration carries an oversized static DNS server list, domain string, proxy host, or proxy exclusion list, or an invalid ProxyInfo, is rejected by validate().",
     "component": "platform/packages/modules/Wifi, Wifi service (WifiConfigurationUtil, IP configuration validation)",
     "weakness": "Missing bounds/size check on caller-supplied configuration data (improper input validation)",
     "root_cause": "In the context shown by the diff, WifiConfigurationUtil.validateIpConfiguration checks that a static IP configuration has a non-null IP address, but sets no limit on the number of DNS servers or on the length of the domains string in StaticIpConfiguration. It also does not inspect the IpConfiguration's ProxyInfo (no validity check, no bound on host length, no bound on the number or total length of exclusion list entries). A WifiConfiguration with arbitrarily large IP configuration fields could therefore pass this validation. The material does not show what happens downstream when such data is accepted; the bulletin rates the issue High severity EoP.",
     "fix_analysis": [
      "service/java/com/android/server/wifi/WifiConfigurationUtil.java: adds import android.net.ProxyInfo and a new constant MAX_ENTRY_SIZE = 100 next to the existing MAX_STRING_LENGTH = 512.",
      "WifiConfigurationUtil.validateIpConfiguration, inside the existing static IP branch (after the null static IP address check): rejects the config if staticIpConfig.getDnsServers() has more than MAX_ENTRY_SIZE (100) entries, or if staticIpConfig.getDomains() is longer than MAX_STRING_LENGTH (512) characters. Each failure calls Log.e with a 'validateIpConfiguration failed: ...' message and returns false.",
      "WifiConfigurationUtil.validateIpConfiguration, new block after the static IP branch: gets ipConfig.getHttpProxy(); if it is non-null, rejects the config when !proxyInfo.isValid(), when proxyInfo.getHost() is longer than 512, when proxyInfo.getExclusionList() has more than 100 entries, or when the combined length of all exclusion list strings exceeds 512 (a running sum is checked after each entry).",
      "service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java: adds testInvalidStaticIpConfig (domains of 513 chars, built from a static IP config with a PAC proxy) and testInvalidProxyInfo (a direct proxy with a 513-char exclusion list entry). Both assert that WifiConfigurationUtil.validate(..., VALIDATE_FOR_ADD) returns false."
     ],
     "completeness": [
      "Covers: DNS server count and domains length for StaticIpConfiguration; ProxyInfo validity, host length, exclusion list entry count and total exclusion list length. The proxy block is placed after the static IP branch closes; whether earlier code in the function returns early for non-static configs is not shown in the material.",
      "The static IP checks are inside the existing staticIpConfig branch, so they only run when a StaticIpConfiguration is present.",
      "Not bounded by this diff: the ProxyInfo PAC file URL length (only proxyInfo.isValid() is consulted, and the material does not show what isValid enforces), individual DNS server entries (count only), and the StaticIpConfiguration gateway/ipAddress fields beyond the existing null check.",
      "The checks live in validateIpConfiguration only. The material does not show which validate() modes or other Wifi service entry points call it, so it cannot confirm that every path accepting an IpConfiguration goes through this function."
     ],
     "behavior_changes": [
      "WifiConfigurationUtil.validate now returns false (config rejected) for static IP configs with more than 100 DNS servers or a domains string longer than 512 characters.",
      "WifiConfigurationUtil.validate now returns false for an IpConfiguration whose ProxyInfo fails isValid(), has a host longer than 512 characters, has more than 100 exclusion list entries, or has exclusion list strings totalling more than 512 characters. The isValid() check is new and may reject proxies that were accepted before regardless of size.",
      "New Log.e messages prefixed 'validateIpConfiguration failed:' for each rejection reason."
     ],
     "preconditions": [
      "A caller must be able to submit a WifiConfiguration containing an IpConfiguration (static IP and/or HTTP proxy) to the Wifi service so that WifiConfigurationUtil.validate -> validateIpConfiguration is reached. The tests call validate(config, SUPPORTED_FEATURES_ALL, VALIDATE_FOR_ADD).",
      "The material does not show the specific API entry point or the caller permissions required."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin, but the material has no fix commit or patched-region status for Android 13; unknown.",
      "14": "Same fix: platform/packages/modules/Wifi a51669fbf6f6 in android-security-14.0.0_r20, identical hunk content (main hunk at line 694 instead of 721); 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Same fix: platform/packages/modules/Wifi 01e708a7a9af in android-security-15.0.0_r8 (cherry-pick, Change-Id I761dbb38ffa2e4dcebad2a6abb8d37d45a3b1fd8); 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin); patched region 8/8 lines present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin); patched region 8/8 lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit: only validateIpConfiguration and a new constant change. The Android 14 and 15 diffs have identical hunk content apart from line offsets, so it should apply to a branch where validateIpConfiguration already has the 'null static ip Address' check and the MAX_STRING_LENGTH constant.",
      "The new tests call the helpers createStaticIpConfigurationWithPacProxy and createStaticIpConfigurationWithStaticProxy in WifiConfigurationTestUtil; these must exist on the target branch for the tests to compile. The material does not show them."
     ],
     "confidence": "high",
     "unknowns": [
      "What concretely goes wrong downstream when oversized IP configuration data is accepted, and how that leads to the EoP rated by the bulletin.",
      "Which Wifi service APIs call WifiConfigurationUtil.validate/validateIpConfiguration, and what permissions they require.",
      "What ProxyInfo.isValid() enforces (for example, whether it bounds the PAC URL).",
      "Whether code earlier in validateIpConfiguration returns before the new proxy block for some IpConfiguration types.",
      "Status of the fix on 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": 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-26424": {
     "cve": "CVE-2025-26424",
     "summary": "Adds NETWORK_STACK / MAINLINE_NETWORK_STACK permission enforcement to the four VPN profile store binder methods (get/put/remove/list) in VpnManagerService, which previously had no caller permission check.",
     "component": "platform/frameworks/base, VpnManagerService (system_server, IVpnManager binder)",
     "weakness": "missing permission check on binder-exposed methods",
     "root_cause": "In services/core/java/com/android/server/VpnManagerService.java the IVpnManager.Stub methods getFromVpnProfileStore, putIntoVpnProfileStore, removeFromVpnProfileStore and listFromVpnProfileStore forwarded directly to mVpnProfileStore.get/put/remove/list without any permission or caller check. Per the commit message these methods are meant only for privileged callers or the owner apps and should not be accessible to apps without the correct permission. Before the fix, any caller able to reach the IVpnManager binder could reach the VPN profile store operations. The bulletin classifies this as information disclosure (ID), High.",
     "fix_analysis": [
      "VpnManagerService.java: adds static import of com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission.",
      "VpnManagerService.getFromVpnProfileStore: calls enforceNetworkStackPermission(mContext) before mVpnProfileStore.get(name).",
      "VpnManagerService.putIntoVpnProfileStore: calls enforceNetworkStackPermission(mContext) before mVpnProfileStore.put(name, blob).",
      "VpnManagerService.removeFromVpnProfileStore: calls enforceNetworkStackPermission(mContext) before mVpnProfileStore.remove(name).",
      "VpnManagerService.listFromVpnProfileStore: calls enforceNetworkStackPermission(mContext) before mVpnProfileStore.list(prefix).",
      "Each check carries a TODO(b/307903113) saying the NETWORK_STACK permission should be replaced by a proper permission; the commit message describes the restriction as applied because the methods are not yet fully deployed."
     ],
     "completeness": [
      "All four profile-store entry points on the IVpnManager binder surface (get, put, remove, list) are gated; no profile-store method in the diff is left unguarded.",
      "The gate is a coarse privileged-caller check (NETWORK_STACK or the mainline network stack permission); the commit's TODO states that a proper permission is still to be adopted.",
      "No validation of the 'name' or 'prefix' arguments is added; only the caller is restricted.",
      "Other VpnManagerService methods are not touched by this commit; the material does not show whether they already have checks."
     ],
     "behavior_changes": [
      "Callers of getFromVpnProfileStore, putIntoVpnProfileStore, removeFromVpnProfileStore and listFromVpnProfileStore that do not hold NETWORK_STACK or PERMISSION_MAINLINE_NETWORK_STACK are now rejected by enforceNetworkStackPermission instead of getting the stored data or result; such enforce helpers throw SecurityException (general platform knowledge).",
      "The VpnManager.putIntoVpnProfileStore wrapper in frameworks/base/core/java/android/net/VpnManager.java (per index search) forwards to the service, so unprivileged callers of it are now rejected.",
      "VpnManagerServiceTest calls mService.putIntoVpnProfileStore / removeFromVpnProfileStore directly (per index search); these tests may need the permission granted in their setup."
     ],
     "preconditions": [
      "Caller can invoke the IVpnManager binder interface; the VpnManager wrappers forward to it, per index search.",
      "Before the fix, no permission was required; after the fix the caller must hold NETWORK_STACK or the mainline network stack permission."
     ],
     "per_major": {
      "14": "not affected per the bulletin; at android-security-14.0.0_r29 only 4/8 lines of the patched region are present, so the code differs (the material does not show whether the methods exist there or are already gated).",
      "15": "same fix, cherry-picked into android-security-15.0.0_r8 (commit 1b4ee554c823, Merged-In If6fd058f40da88a4801b59187631e0c68b236ff5); 8/8 lines present at android-security-15.0.0_r17.",
      "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": [
      "Single-commit fix; it uses com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission, from the same class that already provides the enforceAnyPermissionOf import in this file.",
      "For Android 14 (partial 4/8 region match), check directly whether the four *VpnProfileStore methods exist and whether they lack the permission check; the material does not show this."
     ],
     "confidence": "high",
     "unknowns": [
      "Why Android 14 is not listed: the material does not show whether the profile-store methods exist in the 14 branch or are already protected there.",
      "Whether other VpnManagerService / IVpnManager methods beyond these four lack permission checks.",
      "Whether the follow-up permission referenced by TODO(b/307903113) was landed later.",
      "How the VpnManagerServiceTest setup grants permissions after the fix."
     ],
     "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-26425": {
     "cve": "CVE-2025-26425",
     "summary": "Adds an SdkLevel.isAtLeastU() precondition at the top of RoleService's getDefaultApplicationAsUser and setDefaultApplicationAsUser. On platforms below Android 14 (API 34), these binder methods now throw IllegalStateException instead of running.",
     "component": "platform/packages/modules/Permission, role service (com.android.role.RoleService, RoleManager default application APIs)",
     "weakness": "Missing API-level precondition: the method is guarded by a permission that, per the fix comment, exists only on U+ (improper authorization)",
     "root_cause": "RoleService.getDefaultApplicationAsUser and setDefaultApplicationAsUser restrict access with getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_DEFAULT_APPLICATIONS, ...). Per the fix comment, MANAGE_DEFAULT_APPLICATIONS is only available on Android U and above. The new tests (S..TIRAMISU, 'permission module has been updated') show that the Permission module containing these entry points can run on older platforms. Before the fix, nothing checked the platform level, so on those platforms the methods relied on a permission the platform does not provide and then went on to the role holder lookup or change.",
     "fix_analysis": [
      "service/java/com/android/role/RoleService.java:getDefaultApplicationAsUser: inserts Preconditions.checkState(SdkLevel.isAtLeastU(), \"This API is only available on Android 34 and above\") as the first statement, before UserUtils.enforceCrossUserPermission and the MANAGE_DEFAULT_APPLICATIONS enforcement. On pre-U platforms the call now fails with IllegalStateException before the role holder lookup.",
      "service/java/com/android/role/RoleService.java:setDefaultApplicationAsUser: inserts the identical Preconditions.checkState(SdkLevel.isAtLeastU(), ...) as the first statement, before any user or permission checks.",
      "tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt (new): adds cannotGetDefaultApplicationOnOlderSdk and cannotSetDefaultApplicationOnOlderSdk. Both are limited to S..TIRAMISU via @SdkSuppress and tagged @AsbSecurityTest(cveBugId = [379362792]). They accept NoSuchMethodError or IllegalStateException and fail on any other Throwable."
     ],
     "completeness": [
      "Both patched methods (getDefaultApplicationAsUser, setDefaultApplicationAsUser) get the precondition as their first statement. The material does not show whether other RoleService methods depend on MANAGE_DEFAULT_APPLICATIONS.",
      "The fix is a hard SDK gate. On U+ platforms the code path is unchanged and still relies on enforceCallingOrSelfPermission(MANAGE_DEFAULT_APPLICATIONS).",
      "clearRoleHoldersAsUser, shown in the surrounding code, uses MANAGE_ROLE_HOLDERS and is not touched.",
      "The client-side RoleManager (framework-s) is not modified. Enforcement is entirely in the service."
     ],
     "behavior_changes": [
      "On platforms below API 34, getDefaultApplicationAsUser/setDefaultApplicationAsUser (reached via RoleManager.getDefaultApplication/setDefaultApplication) now throw IllegalStateException with the message \"This API is only available on Android 34 and above\" instead of proceeding.",
      "On pre-U platforms, setDefaultApplicationAsUser throws before the controller is reached, so the RemoteCallback is not invoked.",
      "No behavior change on U+ platforms."
     ],
     "preconditions": [
      "The Permission module containing these methods runs on a platform below Android 14 (API 34). The new tests target S through TIRAMISU.",
      "The caller reaches RoleService via RoleManager.getDefaultApplication/setDefaultApplication or via the IRoleManager binder directly. Per index search, these are system APIs annotated @RequiresPermission(MANAGE_DEFAULT_APPLICATIONS) and @RequiresApi(UPSIDE_DOWN_CAKE).",
      "The target user must exist and roleName must be in DEFAULT_APPLICATION_ROLES for the body to execute."
     ],
     "per_major": {
      "14": "same fix: commit 8ae31de10c7c (RESTRICT AUTOMERGE) in android-security-14.0.0_r20 adds the same two Preconditions.checkState statements plus the import com.android.modules.utils.build.SdkLevel to RoleService.java. The diff shown covers only RoleService.java. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 850ce9ea3ac7 in android-security-15.0.0_r8 (cherry-pick of f21634af4686) adds the two preconditions and the new RoleManagerSecurityTest.kt. 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin); 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": [
      "The Android 14 variant needed an explicit import of com.android.modules.utils.build.SdkLevel in RoleService.java. The Android 15 diff does not add it.",
      "Insert the precondition as the first statement in each method. Apply just the inserted lines, because the surrounding enforceCrossUserPermission call differs between versions: the Android 15 r36 index snippet shows an extra enforceForProfileGroup argument."
     ],
     "confidence": "high",
     "unknowns": [
      "What concretely happens on pre-U platforms when enforceCallingOrSelfPermission is evaluated for MANAGE_DEFAULT_APPLICATIONS. The material only shows that the fix treats the API as unavailable there.",
      "Whether the CTS test file was also added on the Android 14 branch (the 14 diff shown lists only RoleService.java).",
      "Content of the 2 non-matching lines of the patched region on Android 16/17.",
      "Whether other RoleService methods depend on MANAGE_DEFAULT_APPLICATIONS."
     ],
     "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-26426": {
     "cve": "CVE-2025-26426",
     "summary": "The fix removes the blanket exemption that let any app process pass \"android\" as the calling package when registering a broadcast receiver in ActivityManagerService. A follow-up commit then widens the uid exemption from SYSTEM_UID to all core uids, so legitimate system components in core-uid processes can still register as \"android\".",
     "component": "platform/frameworks/base, ActivityManagerService broadcast receiver registration (services/core/java/com/android/server/am/ActivityManagerService.java)",
     "weakness": "Improper caller identity validation (incorrect authorization check on a caller-supplied package name)",
     "root_cause": "In ActivityManagerService.registerReceiverWithFeatureTraced, the check that the caller-supplied callerPackage actually belongs to the calling process (callerApp.getPkgList().containsKey(callerPackage)) was skipped in two cases: when callerApp.info.uid == SYSTEM_UID, or when callerPackage equalled the literal string \"android\". The second clause applied to any uid. So a non-system app process could supply \"android\" as its calling package and the receiver registration went ahead with that package name instead of being rejected with a SecurityException. That name is then passed to handleIncomingUser, isInstantApp and the rest of registration.",
     "fix_analysis": [
      "Commit 475f9914f716 (ActivityManagerService.java:registerReceiverWithFeatureTraced) drops the `&& !\"android\".equals(callerPackage)` clause. The pkgList ownership check is now bypassed only for SYSTEM_UID, so a non-system process naming \"android\" (not in its own pkgList) now hits SecurityException(\"Given caller package ... is not running in process ...\").",
      "Commit 99aae825ded2 (same function, Bug 387930030 + 310632322) changes `callerApp.info.uid != SYSTEM_UID` to `!UserHandle.isCore(callerApp.info.uid)`. System components may run in any core-uid process and register receivers as \"android\". This change restores that behaviour for core uids and keeps the rejection for ordinary app uids.",
      "Net effect of both commits: the condition becomes `!UserHandle.isCore(callerApp.info.uid) && !callerApp.getPkgList().containsKey(callerPackage)`, as shown at lines 14706-14710 of android-security-15.0.0_r17."
     ],
     "completeness": [
      "Covers the single registerReceiverWithFeatureTraced path in ActivityManagerService. This is the only place the diff touches, and the only place in the material where the \"android\" exemption appears.",
      "After the follow-up relaxation, any core uid (not only SYSTEM_UID) can pass an arbitrary callerPackage without the pkgList check. The material does not show any further validation of callerPackage for core uids downstream.",
      "The referenced test (BroadcastRegistrationTest in BroadcastsSecurityTest) is not in the diff, so test coverage cannot be checked from the material."
     ],
     "behavior_changes": [
      "Receiver registration from a non-core uid with callerPackage == \"android\" now throws SecurityException instead of succeeding. Other packages not in the caller's pkgList were already rejected before the fix.",
      "Processes under a core uid other than SYSTEM_UID are now exempt from the pkgList ownership check (before, only SYSTEM_UID was). Between the two commits, such processes would have been rejected when registering as \"android\"; the second commit fixes that."
     ],
     "preconditions": [
      "Caller is a non-isolated app process with a ProcessRecord (getRecordForAppLOSP(caller) != null) and a non-core uid.",
      "Caller invokes receiver registration (the registerReceiverWithFeature path) and passes \"android\" as callerPackage, a package not in its own pkgList."
     ],
     "per_major": {
      "14": "Same fix, at a different location: commit 93c036bb67be in android-security-14.0.0_r20 applies the SYSTEM_UID -> UserHandle.isCore change at line ~13810. Its pre-image already lacks the \"android\" exemption, so the first change was already present on 14 (that commit is not shown in the material). The latest tag, 14.0.0_r29, has 4/5 patched-region lines present.",
      "15": "Fixed by two commits in android-security-15.0.0_r8: 475f9914f716 (removes the \"android\" exemption), then 99aae825ded2 (SYSTEM_UID -> isCore). android-security-15.0.0_r17 shows the final condition at lines 14706-14710.",
      "16": "Not affected (not listed by the bulletin). android-security-16.0.0_r8 has 3/5 patched-region lines present; the code is not shown in the material.",
      "17": "Not affected (not listed by the bulletin). android-security-17.0.0_r1 has 3/5 patched-region lines present; the code is not shown in the material."
     },
     "backport_notes": [
      "Apply both changes together (or squash them): first remove the `!\"android\".equals(callerPackage)` clause, then replace `callerApp.info.uid != SYSTEM_UID` with `!UserHandle.isCore(callerApp.info.uid)`. Shipping only the first commit breaks system components running in core uids other than SYSTEM_UID (Bug 387930030). On Android 14 only the second change is shown (around line 13810); its pre-image already lacks the \"android\" clause."
     ],
     "confidence": "high",
     "unknowns": [
      "What a receiver registered under the spoofed \"android\" package name could achieve. The bulletin only rates it High EoP, and the material shows no downstream consumers of callerPackage beyond handleIncomingUser and isInstantApp.",
      "Whether the first commit (removing the \"android\" exemption) was cherry-picked separately to Android 13 and 14; only the second commit is shown for 14.",
      "The Android 14 method name and surrounding code at the patch site.",
      "The exact state of the patched region on Android 16/17 (only line-count figures are 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-26428": {
     "cve": "CVE-2025-26428",
     "summary": "LockTaskController.startLockTaskMode now returns early without showing a screen pinning request when a non-system caller asks to lock a LOCK_TASK_AUTH_PINNABLE task that is already in mLockTaskModeTasks.",
     "component": "platform/frameworks/base, WindowManager lock task / screen pinning (services/core/java/com/android/server/wm/LockTaskController.java)",
     "weakness": "Missing state check: a request to lock a task that was already locked was not rejected, so an app could trigger repeated pinning requests for a task that is already pinned.",
     "root_cause": "In LockTaskController.startLockTaskMode, a non-system caller (isSystemCaller == false) whose task has mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE (app not on the lock task allowlist) always had task.mLockTaskUid set to callingUid, and StatusBarManagerInternal.showScreenPinningRequest(task.mTaskId) was always called. The code never checked whether the task was already locked (present in mLockTaskModeTasks), so an app whose task was already pinned could trigger the pinning request flow again. Per the in-code comment, accepting the request returns to this method with isSystemCaller true. The bulletin rates this as a High severity EoP. The material does not describe the concrete consequence.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/LockTaskController.java:startLockTaskMode: inside the !isSystemCaller branch, in the task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE case, the fix adds `if (mLockTaskModeTasks.contains(task)) { Slog.i(TAG_LOCKTASK, \"Already locked.\"); return; }` before the existing ProtoLog and showScreenPinningRequest call. A repeat request for an already locked task is now dropped.",
      "services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java:testStartLockTaskMode_pinnedBySystem: the test is extended. After the system has pinned the task, it calls startLockTaskMode(tr, false, TEST_UID) and verifies that showScreenPinningRequest is never invoked."
     ],
     "completeness": [
      "The fix covers only the non-system, LOCK_TASK_AUTH_PINNABLE path. The early return comes after task.mLockTaskUid = callingUid, so that field is still overwritten with the caller's uid for an already locked task before the request is rejected.",
      "The sibling branch for allowlisted apps (else if mLockTaskModeState == LOCK_TASK_MODE_PINNED, which calls stopLockTaskMode and goes on to setLockTaskMode) is unchanged. The system-caller path (isSystemCaller == true) is also unchanged, and the shown code has no mLockTaskModeTasks check before setLockTaskMode on that path.",
      "The check looks up the specific Task in mLockTaskModeTasks. It does not consider other tasks of the same uid or any other lock state."
     ],
     "behavior_changes": [
      "When an app asks to start lock task on its own task and that task is already locked or pinned, the call now logs \"Already locked.\" under TAG_LOCKTASK and returns silently. No screen pinning request dialog is shown.",
      "There is no new exception or error code; the request is simply a no-op."
     ],
     "preconditions": [
      "The caller is a non-system app that reaches LockTaskController.startLockTaskMode with isSystemCaller == false (the in-code comment refers to startLockTask() called by an app).",
      "The calling task has mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE, meaning the app is not on the lock task allowlist.",
      "The task is already in mLockTaskModeTasks, for example because the system pinned it earlier (as in testStartLockTaskMode_pinnedBySystem)."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/base d4cd532ad022 in android-security-14.0.0_r20, with identical hunks (the hunk starts at line 652 instead of 653). 8/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: platform/frameworks/base afd05dc17e02 in android-security-15.0.0_r8 (Change-Id Ic5d2125d39e5e98301d79c8f5aa8986a44e06794). 8/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Not affected (the bulletin does not list it). 8/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "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; the material does not show which line differs."
     },
     "backport_notes": [
      "This is a single self-contained commit. The diff adds no imports or fields: it uses mLockTaskModeTasks, TAG_LOCKTASK and Slog, which already exist in the shown Android 15 code. Place the check before the showScreenPinningRequest call inside the LOCK_TASK_AUTH_PINNABLE branch of startLockTaskMode."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not describe the concrete privilege escalation consequence of repeated pinning requests on an already locked task.",
      "The material does not show what happens when a pinning request for an already locked task is accepted and startLockTaskMode is re-entered with isSystemCaller true.",
      "Whether Android 13 received the same patch: the bulletin lists 13 as affected, but no 13 commit is provided.",
      "Which line differs in the Android 17 patched region (7/8 lines present)."
     ],
     "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-26429": {
     "cve": "CVE-2025-26429",
     "summary": "Adds a per-call cap (NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000) on the total number of attributed op entries that AppOpsService puts into the OpEntry list returned to IAppOpsService#getPackagesForOpsForDevice callers. Once the cap is passed, the result is cut short and a warning is logged, instead of building a reply that exceeds the binder transaction limit.",
     "component": "platform/frameworks/base, AppOps (services/core/java/com/android/server/appop/AppOpsService.java)",
     "weakness": "missing upper bound on the size of a binder reply (unbounded result list built from app-controlled attribution tags, leading to TransactionTooLargeException)",
     "root_cause": "A helper in AppOpsService.java builds the List<OpEntry> result (resOps) from a package's Ops (pkgOps). Per the commit message it serves IAppOpsService#getPackagesForOpsForDevice. It added one OpEntry per op via getOpEntryForResult(curOp, persistentDeviceId), with no limit on how many attributed op entries piled up. When an app uses many attribution tags across many ops, the PackageOps parcel can grow past the binder transaction limit and the call fails with TransactionTooLargeException (DoS per the bulletin).",
     "fix_analysis": [
      "AppOpsService.java (class constants): adds private static final int NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000, documented as the upper limit of attributed op entries returned in one binder transaction. Per the commit message, the value assumes each attribution tag is 50 bytes long.",
      "AppOpsService.java, result-building helper (takes pkgOps, ops, persistentDeviceId; builds resOps): adds a local counter totalAttributedOpEntryCount = 0 before the two loops.",
      "In the ops == null branch (all ops of the package): before adding each entry, the loop breaks if totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD. Otherwise it adds the OpEntry from getOpEntryForResult(curOp, persistentDeviceId) to resOps and increases the counter by opEntry.getAttributedOpEntries().size().",
      "In the else branch (explicit ops[] filter): the same check, break, add and counter increment are applied inside the per-op loop.",
      "After the loops, if the counter passed the threshold, Slog.w(TAG, ...) logs that the result was throttled and may be due to a DoS attack. The truncated resOps is returned normally; the caller gets no exception."
     ],
     "completeness": [
      "Both branches of the helper (all ops and filtered ops) get the same check. The restricted-op skip (opRestrictsRead) still runs before the threshold check.",
      "The check runs before an entry is added and uses '>' on the count so far, so an OpEntry is always added while the count is at or below 2000. The final total can therefore exceed 2000 by the size of the last OpEntry's attributed entries. A single op with a very large number of attribution tags is not bounded by this change, and the material shows no per-op cap on attributed entries.",
      "The counter is local to the helper, so the threshold applies to each call of the helper (one pkgOps), not necessarily to the whole binder reply. Whether the outer path combines several packages into one transaction is not shown in the material.",
      "This commit does not touch any other IAppOpsService reply path that might serialize attributed op entries. The material does not show whether such paths exist."
     ],
     "behavior_changes": [
      "Results of IAppOpsService#getPackagesForOpsForDevice (and the matching Android 14 path) are cut short once more than 2000 attributed op entries have been accumulated. The remaining ops are left out without any error to the caller.",
      "A new Slog.w message tagged AppOps ('The number of attributed op entries has exceeded the threshold...') is logged when throttling happens.",
      "No new exception or error code: callers get a shorter list instead of a TransactionTooLargeException."
     ],
     "preconditions": [
      "A package whose Ops hold a very large number of attributed op entries (many attribution tags across many ops), as the commit message describes. The material shows no limit on attribution tags.",
      "A caller invoking IAppOpsService#getPackagesForOpsForDevice (per the commit message), so that the affected helper serializes that package's ops. The only permission in the diff context is GET_APP_OPS_STATS, and it only controls whether read-restricted ops are included. The material does not show what permission, if any, the entry point requires."
     ],
     "per_major": {
      "14": "adapted fix: commit 22a375717e61 in android-security-14.0.0_r20. Same logic, but getOpEntryForResult(curOp) has no persistentDeviceId parameter, and the filtered branch keeps its existing 'if (resOps == null) resOps = new ArrayList<>()' lazy init. 8/8 lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 0f3c01c326b6 in android-security-15.0.0_r8 (cherry-pick, Change-Id I43cd4b9774dbe554edcec296c4b8a3d7fc60c85c). 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-file, self-contained change. The only version difference shown is the getOpEntryForResult signature: 15 passes persistentDeviceId, 14 does not. The bulletin lists Android 13 as affected, but the material includes no 13 commit; compare the 13 code against the 14 variant."
     ],
     "confidence": "high",
     "unknowns": [
      "The diff hunk headers do not show the name of the patched helper method, only the class. The commit message names the affected binder call as IAppOpsService#getPackagesForOpsForDevice.",
      "Whether the binder entry point itself requires a permission, and which system components call it (and would hit the TransactionTooLargeException).",
      "Whether any limit on the number of attribution tags per app exists elsewhere, which would bound how far one OpEntry can push the count past the threshold.",
      "Whether the outer call path combines several packages' results into one binder reply.",
      "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": 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-26430": {
     "cve": "CVE-2025-26430",
     "summary": "SpaAppBridgeActivity.getDestinationForApp now validates that the package name taken from the incoming \"package:\" intent URI matches a package-name regex before interpolating it into the SPA navigation route, returning null otherwise.",
     "component": "platform/packages/apps/Settings, SPA bridge activity (SpaAppBridgeActivity) / app navigation",
     "weakness": "Improper input validation: untrusted intent URI data interpolated unvalidated into a navigation route string (route/path injection)",
     "root_cause": "In src/com/android/settings/spa/SpaAppBridgeActivity.kt, getDestinationForApp() took intent.data?.schemeSpecificPart from the bridge activity's intent (the bridge sits behind an activity-alias so SpaActivity need not be exported) and directly built the SPA destination string \"$destinationPrefix/$packageName/${UserHandle.myUserId()}\". The scheme-specific part was not checked to be a well-formed package name, so a value containing route-significant characters such as \"/\" or \"#\" (as in the new test \"package:package.name/10#\") would alter the structure of the destination route passed to startSpaActivityFromBridge instead of being a single package-name segment.",
     "fix_analysis": [
      "src/com/android/settings/spa/SpaAppBridgeActivity.kt:getDestinationForApp: the schemeSpecificPart is filtered with takeIf { Regex(\"^([a-zA-Z]\\\\w*\\\\.)*[a-zA-Z]\\\\w*$\").matches(it) }; only dot-separated identifiers (word characters, each segment starting with a letter) are accepted. Anything else makes the expression null and the function returns null, so the startSpaActivityFromBridge lambda in onCreate yields a null destination; onCreate then calls finish() as before.",
      "tests/spa_unit/src/com/android/settings/spa/SpaAppBridgeActivityTest.kt: adds getDestinationForApp_hasMalformedPackageName asserting that \"package:package.name/10#\" produces a null destination."
     ],
     "completeness": [
      "Covers the single place in SpaAppBridgeActivity where the URI scheme-specific part is interpolated into the route; the package segment is now validated and the trailing user-id segment comes from UserHandle.myUserId().",
      "The whole-string regex rejects any '/', '#', '?', ':' or whitespace, so the package name cannot span or terminate route segments. It does not check that the package exists.",
      "Only this bridge activity is changed. Other SPA bridge/route-building paths (e.g. SpaBridgeActivity.startSpaActivityFromBridge itself, or other destination builders) are not touched by this commit; the material does not show whether they take untrusted route components."
     ],
     "behavior_changes": [
      "For intents to the SPA app bridge whose package: URI does not match the regex, getDestinationForApp now returns null instead of a route containing the raw string; the activity still calls finish()."
     ],
     "preconditions": [
      "A caller can start the activity-alias that targets SpaAppBridgeActivity (per the class comment, it exists so SpaActivity itself need not be exported) with an intent whose data URI uses the package: scheme.",
      "The scheme-specific part is caller-controlled and previously was used verbatim in the destination route."
     ],
     "per_major": {
      "14": "not affected per the bulletin; at android-security-14.0.0_r29 only 1/6 lines of the patched region are present.",
      "15": "same fix: 484b4be8f363 in android-security-15.0.0_r8 (Change-Id Ife615c78c48a2d6f5b938c880c9c2128c5551644); the regex line is present in the android-security-15.0.0_r17 code (5/6 lines of patched region).",
      "16": "not affected per the bulletin (not listed); 5/6 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); 5/6 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit, self-contained change to one Kotlin line plus a unit test; it applies wherever SpaAppBridgeActivity.getDestinationForApp exists with the schemeSpecificPart interpolation."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how startSpaActivityFromBridge / the SPA navigation layer parses the destination string or handles a null destination, so the concrete effect of an injected route is not shown.",
      "The manifest activity-alias entries that export this bridge and their intent filters are not in the material.",
      "Whether the 16 and 17 trees contain the exact regex line (5/6 match) 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-26435": {
     "cve": "CVE-2025-26435",
     "summary": "The Settings content protection toggle controller now disables its main switch bar when the current user is a guest user, so a guest can no longer flip the content protection switch from that screen.",
     "component": "platform/packages/apps/Settings, security settings (ContentProtectionTogglePreferenceController)",
     "weakness": "missing user-type (guest) restriction on a settings control (missing authorization check)",
     "root_cause": "In src/com/android/settings/security/ContentProtectionTogglePreferenceController.java, the switch bar state logic only had an admin-enforcement branch: setDisabledByAdmin(mEnforcedAdmin), under a condition that includes mContentProtectionPolicy != CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY. It never checked whether the current user is a guest, so the content protection switch stayed enabled for guest users.",
     "fix_analysis": [
      "src/com/android/settings/security/ContentProtectionTogglePreferenceController.java, in the switch-bar state update path reached from updateState(): an explicit `return;` is added after the existing admin-enforcement branch, so the admin path exits before the new check.",
      "Same method: a new block gets UserManager via mContext.getSystemService(UserManager.class). If it is non-null, userManager.isGuestUser() is true and mSwitchBar is non-null, the block calls mSwitchBar.setEnabled(false).",
      "tests/robotests/.../ContentProtectionTogglePreferenceControllerTest.java: adds ShadowUserManager to the shadows config and resets guest state to false in setUp. It adds updateState_flagEnabled_noEnforcedAdmin_guestUser_switchBarDisabled and a non-guest counterpart, and asserts that setEnabled(false) is never called in the existing non-guest cases."
     ],
     "completeness": [
      "Covers only the UI enablement of the switch bar in this controller. The guest check runs only when mSwitchBar is non-null and UserManager can be obtained.",
      "The guest check is skipped when the admin branch returns early. In that case setDisabledByAdmin(mEnforcedAdmin) is called instead. The material does not show whether that always makes the switch non-operable.",
      "The diff does not change isChecked/setChecked or the underlying setting write path (the class imports ContentResolver, Settings and UserHandle). The material does not show whether a guest can change the setting through another path.",
      "Only the guest user type is restricted. This change considers no other user types or user restrictions."
     ],
     "behavior_changes": [
      "For a guest user, the content protection main switch in Settings is shown disabled (setEnabled(false)) when the admin-enforcement branch was not taken.",
      "When the admin-enforcement branch is taken, the new return means the guest check does not run after setDisabledByAdmin.",
      "No change for non-guest users."
     ],
     "preconditions": [
      "Device running Android 15 Settings with the content protection toggle preference available (ContentProtectionTogglePreferenceController present).",
      "The current user is a guest user and opens the content protection settings screen.",
      "The admin-enforcement branch is not taken."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin. ContentProtectionTogglePreferenceController.java and its test are absent at android-security-14.0.0_r29.",
      "15": "same fix: commit 9dc0dd2c50ce in android-security-15.0.0_r8 (cherry-picked from baf503050fc6). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "not affected: not listed by the bulletin. 8/8 patched lines present at android-security-16.0.0_r8.",
      "17": "not affected: not listed by the bulletin. 8/8 patched lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit, self-contained change. The production side needs only UserManager. The test relies on com.android.settings.testutils.shadow.ShadowUserManager with getShadow() and setGuestUser(), which must exist in the target tree's robotests testutils."
     ],
     "confidence": "high",
     "unknowns": [
      "The hunk header does not give the exact name of the patched method. The tests show it is reached via updateState().",
      "The full condition of the admin-enforcement branch is not shown.",
      "The material does not show what the toggle writes (which Settings key, which user) or the concrete consequence of a guest changing it.",
      "The material does not show whether other entry points (setChecked, onCheckedChanged, or other UIs) let a guest change the same setting."
     ],
     "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-26436": {
     "cve": "CVE-2025-26436",
     "summary": "PendingIntentRecord.clearAllowBgActivityStarts now also downgrades the token's temp-allowlist duration entry from TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED to TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, so clearing a PendingIntent's background-start privileges no longer leaves the duration-based entry with FGS-allowed type; a companion commit adds equals/hashCode to BackgroundStartPrivileges for the tests.",
     "component": "platform/frameworks/base, ActivityManager / PendingIntentRecord (background activity start and temp allowlist privileges)",
     "weakness": "Incomplete revocation of a granted privilege (clear path removed only token-based entries and left duration-based allowlist entries intact)",
     "root_cause": "In services/core/java/com/android/server/am/PendingIntentRecord.java, clearAllowBgActivityStarts(IBinder token) only removed the token from mAllowBgActivityStartsForActivitySender, mAllowBgActivityStartsForBroadcastSender and mAllowBgActivityStartsForServiceSender. It did not touch mAllowlistDuration, so a TempAllowListDuration entry previously registered for the same token via setAllowlistDurationLocked with type TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED survived the clear. Per the commit message, clearAllowBgActivityStarts is used exclusively by SystemUI (in NotificationManagerService), and clearing only the tokens while keeping duration-based entries is described as part of a security vulnerability.",
     "fix_analysis": [
      "Commit bba26504af51 (test support): core/java/android/app/BackgroundStartPrivileges.java makes the class final and adds equals(Object) and hashCode() comparing mAllowsBackgroundActivityStarts, mAllowsBackgroundForegroundServiceStarts and mOriginatingToken (via Objects.equals/Objects.hash). The commit message states it is for now only used in tests; it enables assertEquals on BackgroundStartPrivileges values in the new tests. BackgroundStartPrivilegesTest gains backgroundStartPrivilege_equals_works.",
      "Commit ccba6717779f (the actual fix): services/core/java/com/android/server/am/PendingIntentRecord.java:clearAllowBgActivityStarts adds, after the three set removals, a lookup of mAllowlistDuration.get(token) (guarded by mAllowlistDuration != null); if an entry exists and its type is TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, the type is changed in place to TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED. The entry itself and its duration are kept.",
      "Same commit adds @VisibleForTesting getAllowlistDurationLocked(IBinder) and widens getBackgroundStartPrivilegesForActivitySender from private to @VisibleForTesting package visibility, and adds PendingIntentControllerTest.testClearAllowBgActivityStartsClearsToken and testClearAllowBgActivityStartsClearsDuration, the latter asserting the entry remains non-null with duration 1000 while the type becomes FOREGROUND_SERVICE_NOT_ALLOWED after clearAllowBgActivityStarts.",
      "Static imports for the two TEMPORARY_ALLOWLIST_TYPE_* constants from android.os.PowerWhitelistManager are added to PendingIntentRecord."
     ],
     "completeness": [
      "Covers only the clearAllowBgActivityStarts(token) path: the duration entry keyed by exactly that token is downgraded. Entries under other tokens for the same PendingIntentRecord are not touched (consistent with the token-scoped semantics of the three set removals).",
      "The fix does not remove the TempAllowListDuration entry; it only flips type from FOREGROUND_SERVICE_ALLOWED to FOREGROUND_SERVICE_NOT_ALLOWED. The test asserts the entry is still present with duration 1000, so retaining the entry appears intentional; how the retained entry is applied at send time is not shown.",
      "Entries whose type is already something other than TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED are left unchanged.",
      "The code shown does not reveal how mAllowlistDuration is consumed at send time, so whether other consumers of the duration entry derive start privileges is not visible in the material.",
      "The commit message states the change is not flag guarded, so it is always active once applied."
     ],
     "behavior_changes": [
      "After clearAllowBgActivityStarts(token), a matching temp-allowlist duration entry of type FOREGROUND_SERVICE_ALLOWED becomes FOREGROUND_SERVICE_NOT_ALLOWED; the entry and its duration persist.",
      "BackgroundStartPrivileges is now final and has value-based equals/hashCode instead of the default identity-based ones; per the commit message this is for now only used in tests.",
      "getBackgroundStartPrivilegesForActivitySender is no longer private (package-visible, @VisibleForTesting)."
     ],
     "preconditions": [
      "A PendingIntentRecord must have had a TempAllowListDuration registered for a token via setAllowlistDurationLocked with type TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED (the test uses REASON_NOTIFICATION_SERVICE / \"NotificationManagerService\").",
      "clearAllowBgActivityStarts(token) must then be invoked for that token; per the commit message this is done exclusively by SystemUI (in NotificationManagerService).",
      "Exact downstream consumption of the retained duration entry when the PendingIntent is sent is not shown in the material."
     ],
     "per_major": {
      "14": "Resolved per bulletin at android-security-14.0.0_r20. The material shows the same 'Add equals method' commit (88d7046ea668) applied with identical content at a slightly different line offset; the PendingIntentRecord change for 14 is not shown in the material, but the patched region is reported present (7/8 lines) at android-security-14.0.0_r29.",
      "15": "Same fix: both commits (bba26504af51 and ccba6717779f, the latter marked RESTRICT AUTOMERGE) in android-security-15.0.0_r8; patched region present (7/8 lines) at android-security-15.0.0_r17, and the code context there shows the TEMPORARY_ALLOWLIST_TYPE_* imports in PendingIntentRecord.",
      "16": "Not affected (not listed by the bulletin); region check reports 7/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin); region check reports 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply bba26504af51 (equals/hashCode on BackgroundStartPrivileges) before ccba6717779f: the new PendingIntentControllerTest uses assertEquals on BackgroundStartPrivileges and relies on value equality. Making the class final will break any downstream subclass of BackgroundStartPrivileges.",
      "The PendingIntentRecord hunk depends on mAllowlistDuration (map of TempAllowListDuration with a mutable 'type' field) and the PowerWhitelistManager TEMPORARY_ALLOWLIST_TYPE_* constants existing in the target branch."
     ],
     "confidence": "high",
     "unknowns": [
      "How mAllowlistDuration entries are consumed at PendingIntent send time (which code reads TempAllowListDuration.type) is not included in the material.",
      "The Android 14 variant of the PendingIntentRecord commit is not shown; only its presence is inferred from the per-major region check.",
      "The precise user-visible effect (which privilege an app could retain) is not described in the material beyond the bulletin's EoP / High rating.",
      "Why Android 16 and 17 are not listed by the bulletin is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-26438": {
     "cve": "CVE-2025-26438",
     "summary": "Adds a check in smp_process_secure_connection_oob_data that stops LE Secure Connections OOB pairing with SMP_OOB_FAIL when the peer flags OOB data as present but no local OOB data has been stored. Before the fix, pairing continued with a zeroed local randomizer.",
     "component": "platform/packages/modules/Bluetooth, system/stack/smp (LE Security Manager Protocol, Secure Connections OOB association model)",
     "weakness": "Missing validation of peer-asserted authentication state (authentication bypass: OOB pairing proceeds with a known zero randomizer when local OOB data is absent)",
     "root_cause": "In system/stack/smp/smp_act.cc, smp_process_secure_connection_oob_data() sets p_cb->local_random = {0} when no local OOB data is available. It then continues the Secure Connections OOB flow even if the peer claims to have OOB data. Per the commit message, a malicious peer can bypass SMP authentication by claiming to have OOB data and proceeding with pairing, because the randomizer is zero. smp_start_secure_connection_phase1() sets p_cb->sec_level = SMP_SEC_AUTHENTICATED for every model other than JustWorks, and for SMP_MODEL_SEC_CONN_OOB it calls smp_process_secure_connection_oob_data(). So the pairing is marked authenticated even though no real OOB data was exchanged.",
     "fix_analysis": [
      "system/stack/smp/smp_act.cc:smp_process_secure_connection_oob_data: a new check is added after the existing block that zeroes p_cb->local_random. It tests p_cb->peer_oob_flag == SMP_OOB_PRESENT && !p_sc_oob_data->loc_oob_data.present.",
      "If the condition holds, the code logs a warning, builds a tSMP_INT_DATA with status = SMP_OOB_FAIL, and posts SMP_AUTH_CMPL_EVT to the SMP state machine through smp_sm_event(). It then returns before the peer OOB data handling and the rest of the OOB processing run.",
      "Result: when the peer says it has local OOB data that was never stored, pairing ends instead of being accepted with a zero randomizer."
     ],
     "completeness": [
      "The check covers only the LE Secure Connections OOB path in smp_process_secure_connection_oob_data(). This function is reached through the SMP_MODEL_SEC_CONN_OOB case of smp_start_secure_connection_phase1().",
      "The check uses p_cb->peer_oob_flag and p_sc_oob_data->loc_oob_data.present. The opposite mismatch (local OOB data present, peer flag absent) is unchanged, and the existing zeroing of p_cb->local_random before the check is kept.",
      "The material shows no change to OOB handling in legacy (non-SC) pairing or to the code that selects the association model. Whether those paths needed changes is not shown."
     ],
     "behavior_changes": [
      "SC OOB pairing now fails with SMP_OOB_FAIL (delivered through SMP_AUTH_CMPL_EVT) when the peer flags OOB data as present but the local stack has no stored local OOB data. Before the fix, pairing continued.",
      "A new warning log line is written in that case.",
      "Nothing changes when local OOB data is present or when the peer does not flag OOB data as present."
     ],
     "preconditions": [
      "LE Secure Connections pairing that reaches the SMP_MODEL_SEC_CONN_OOB case, with the peer's OOB flag set (p_cb->peer_oob_flag == SMP_OOB_PRESENT).",
      "No local OOB data stored on the Android device (p_sc_oob_data->loc_oob_data.present is false).",
      "Code path: smp_start_secure_connection_phase1() -> smp_process_secure_connection_oob_data() in system/stack/smp/smp_act.cc (from the provided code context). Per index search, the only other reference is the test mock in system/test/mock/mock_stack_smp_act.h."
     ],
     "per_major": {
      "14": "Adapted fix: commit 022ac90c296b in android-security-14.0.0_r20, with the same logic and placement. Logging uses SMP_TRACE_WARNING (with a leftover __func__ argument) instead of log::warn. 4/4 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix: commit e6130675c047 in android-security-15.0.0_r8 (cherry-pick of internal faea50382d2b). 4/4 patched lines present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin (not listed). The patched region is present (4/4 lines) at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). The patched region is present (4/4 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix is a single self-contained hunk. Between the 14 and 15 diffs, the only difference is the logging call (log::warn on 15, SMP_TRACE_WARNING on 14). Check that the branch has p_cb->peer_oob_flag and p_sc_oob_data->loc_oob_data.present, and insert the check after the local_random zeroing block and before the peer_oob_data handling."
     ],
     "confidence": "high",
     "unknowns": [
      "Where p_sc_oob_data is obtained in smp_process_secure_connection_oob_data and whether it can be null (not in the provided context).",
      "How the association model is selected from the peer's OOB flag (the code is not shown).",
      "Whether legacy (non-Secure Connections) OOB pairing also uses a zero randomizer; the material does not show that code.",
      "The commit includes no test changes (its Test line is only 'm com.android.btservices')."
     ],
     "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-26442": {
     "cve": "CVE-2025-26442",
     "summary": "NotificationAccessConfirmationActivity now checks that the exact requested ComponentName (not just some service in the same package) resolves for the NotificationListenerService intent and declares BIND_NOTIFICATION_LISTENER_SERVICE before it shows the notification access confirmation dialog.",
     "component": "platform/packages/apps/Settings, notification access confirmation (CDM flow per commit title), NotificationAccessConfirmationActivity",
     "weakness": "Improper validation / incorrect comparison key: service eligibility checked by package name instead of full component name",
     "root_cause": "In NotificationAccessConfirmationActivity, the code queried every service matching NotificationListenerService.SERVICE_INTERFACE via queryIntentServicesAsUser and looked for a match against the requested mComponentName. The match was service.serviceInfo.packageName.equals(mComponentName.getPackageName()), so only the package name was compared. If any service in the target package resolved for the NLS intent, the loop matched it for any requested component in that package. The permission check (requiredPermission.equals(service.serviceInfo.permission)) was then run against that package-matching service, not the requested component. The confirmation dialog could therefore be shown for a component that did not itself declare the NLS intent filter or the required permission.",
     "fix_analysis": [
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java (NLS service info check loop over matchedServiceList): the match now uses service.serviceInfo.getComponentName().equals(mComponentName) instead of comparing only package names. The permission check and the hasNLSIntentFilter flag are therefore evaluated for the exact requested component. A component that does not itself resolve for the NLS intent now hits the existing 'lacks an intent-filter action' path and the activity finishes. A component that resolves but lacks the permission hits the existing 'lacks permission' path.",
      "The second hunk only removes trailing whitespace on getWindow().setCloseOnTouchOutside(false); and has no functional effect."
     ],
     "completeness": [
      "In the NLS service info loop, the match now uses the full ComponentName, so both the intent-filter presence and the permission requirement are tied to the requested component.",
      "The checks that run earlier in the shown code are unchanged: null component, package name present, flattened name length, resolvable ApplicationInfo, non-empty label.",
      "The fix only changes Settings' confirmation activity. The material does not show whether the grant path (what onAllow calls) does any independent component validation."
     ],
     "behavior_changes": [
      "A request naming a component that does not itself resolve for the NotificationListenerService intent is now rejected (the activity finishes and logs 'lacks an intent-filter action'), even if another service in the same package does resolve.",
      "The BIND_NOTIFICATION_LISTENER_SERVICE permission check is now evaluated against the requested component, not the first same-package service that matched the NLS intent.",
      "Well-formed requests, where the named component is itself a properly declared, permission-protected NotificationListenerService, are not expected to change."
     ],
     "preconditions": [
      "NotificationAccessConfirmationActivity in Settings must be launched with a component name (mComponentName) and a user id (mUserId). The '[CDM]' commit title points to the Companion Device Manager flow.",
      "Before the fix, the named component's package had to contain at least one service that resolves for android.service.notification.NotificationListenerService and declares BIND_NOTIFICATION_LISTENER_SERVICE. The named component itself did not need to.",
      "The dialog shows the package's app label, and its positive button calls onAllow(). What onAllow grants is not shown."
     ],
     "per_major": {
      "14": "same fix: commit a395a242270c in android-security-14.0.0_r20, the same one-line change plus the whitespace hunk at a one-line offset. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "same fix: commit 63f8849244d1 in android-security-15.0.0_r8 (cherry-pick of f5ed187ffb6f). At android-security-15.0.0_r17, line 126 uses getComponentName().equals(mComponentName) (7/8 patched-region lines present).",
      "16": "not affected (not listed by the bulletin). 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": [
      "The fix is a single one-line logic change in NotificationAccessConfirmationActivity.java plus a whitespace-only hunk. The bulletin also lists Android 13 as affected, but no Android 13 commit is in the material."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how the activity is launched (the CDM caller and which extras supply mComponentName and mUserId) or what onAllow does to grant access.",
      "The Android 13 fix commit is not in the material, although the bulletin lists 13 as affected.",
      "Whether the grant path outside Settings does its own component validation is not shown.",
      "Which patched-region line differs at the latest 14, 16 and 17 tags (7/8 present) is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "16": {
   "counts": {
    "announced": 0,
    "certain": 0,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [],
   "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": {}
  },
  "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-05-01",
  "latest_version": "1.1",
  "latest_revision": {
   "version": "1.1",
   "date": "May 6, 2025",
   "notes": "AOSP links added"
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "May 5, 2025",
    "notes": "Bulletin Published"
   },
   {
    "version": "1.1",
    "date": "May 6, 2025",
    "notes": "AOSP links added"
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [],
  "out_of_scope_rows": {
   "Arm components": 2,
   "Imagination Technologies": 8,
   "MediaTek components": 1,
   "Qualcomm closed-source components": 6,
   "Qualcomm components": 5
  },
  "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."
 }
}