{
 "month": "2025-01",
 "generated": "2026-10-06T12:05+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 20,
    "certain": 20,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2024-43096",
     "osv_id": "ASB-A-323850943",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an mtu == 0 rejection at the top of build_read_multi_rsp in system/stack/gatt/gatt_sr.cc and clamps the EATT channel tx MTU to at least EATT_MIN_MTU_MPS in EattChannel::EattChannelSetTxMTU, so the Read Multiple response buffer is never allocated with zero payload room before the opcode byte is written.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, two independent additive hunks (8 lines). The gatt_sr.cc check uses LOG(ERROR) (already used in that file) and GATT_ILLEGAL_PARAMETER; the eatt.h clamp depends on EATT_MIN_MTU_MPS being defined in the target tree. Android 12/12L/13 are listed as affected by the bulletin but no per-major fix data is provided in the material; verify EattChannelSetTxMTU and build_read_multi_rsp exist in that form before applying."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "bfe316cf9f026d5b48bcfb2f457685b537baa9a3",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43770",
     "osv_id": "ASB-A-364026473",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds early returns in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size_tx() returns 0 (channel already closed), so response buffers are no longer allocated with a zero payload size and written past their end.",
      "confidence": "high",
      "backport_notes": [
       "The four hunks are self-contained additive checks. The only branch difference seen is the helper name (gatt_tcb_get_payload_size_tx on 14, gatt_tcb_get_payload_size on 15), so use whatever name the target branch uses at each of the four sites."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "c7468e64bb5e821563a910ccd8e5693c179c9da4",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43771",
     "osv_id": "ASB-A-364027949",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds early-return checks for a zero payload size (returned by gatt_tcb_get_payload_size_tx when the channel is already closed) in four ATT server request handlers in system/stack/gatt/gatt_sr.cc. This stops undersized response buffers from being allocated and written out of bounds.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. The only difference between branches is the helper name: gatt_tcb_get_payload_size_tx on 14 and gatt_tcb_get_payload_size on 15. In each of the four handlers, put the check right after whichever call the target branch uses."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "c7468e64bb5e821563a910ccd8e5693c179c9da4",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-49747",
     "osv_id": "ASB-A-364027038",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when the transmit payload size returned for the channel is 0 (channel already closed), so a response buffer sized from that zero length is never allocated and written past.",
      "confidence": "high",
      "backport_notes": [
       "The added lines are identical across branches. Only the existing accessor name differs (gatt_tcb_get_payload_size_tx on 14, gatt_tcb_get_payload_size on 15). Place each check directly after the payload_size assignment and before the buffer length computation in the four functions."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "c7468e64bb5e821563a910ccd8e5693c179c9da4",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-49748",
     "osv_id": "ASB-A-364025411",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when the per-channel transmit payload size is 0 (channel already closed). As a result, a response buffer with effectively zero payload space is no longer allocated and written into.",
      "confidence": "high",
      "backport_notes": [
       "The two branch fixes differ only in the helper name (gatt_tcb_get_payload_size_tx on Android 14, gatt_tcb_get_payload_size on Android 15) and in line offsets. Insert the four checks directly after the payload_size assignment and before any buffer length computation."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "c7468e64bb5e821563a910ccd8e5693c179c9da4",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-34722",
     "osv_id": "ASB-A-251514170",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a new SMP control-block flag, SMP_PAIR_FLAGS_CMD_CONFIRM_SENT, which is set when the local Pairing Confirm is sent. smp_proc_init and smp_proc_rand now reject a peer rand received in BLE legacy pairing before the local confirm was sent, and fail the pairing with SMP_INVALID_PARAMETERS.",
      "confidence": "high",
      "backport_notes": [
       "Apply 317b4e293cef first, because it defines SMP_PAIR_FLAGS_CMD_CONFIRM_SENT in smp_int.h and sets it in smp_send_confirm. Then apply 6ad3d749f7f6, which depends on that define. On any branch where smp_act.cc has smp_proc_init and smp_proc_rand as separate functions, both need the check, as the second commit documents.",
       "On customized trees, make sure bit (1 << 9) does not collide with any vendor-added SMP_PAIR_FLAG* bits in smp_int.h. p_cb->flags is uint16_t."
      ],
      "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_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "317b4e293cefe18fe8c58c1de0c4a6741bd05629",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "6ad3d749f7f632787f29710ce23736e10d2969bf",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 184,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-34730",
     "osv_id": "ASB-A-308429049",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a per-device \"reconnect_allowed\" flag to the HID host btif layer (persisted as HidReConnectAllowed) and makes hh_open_handler in system/btif/src/btif_hh.cc reject incoming HID connections unless the device is an added device with reconnection allowed or an outgoing connection to it is pending.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit, but it changes the bthh_interface_t.disconnect HAL signature, so all callers must be updated together: JNI (com_android_bluetooth_hid_host.cpp plus the sMethods table), HidHostService.java native declaration, and the Rust topshim/Floss callers if present in the target branch. The android-security-14.0.0_r29 context shows these carry the new signature.",
       "For Android 12/12L the Bluetooth stack lived in system/bt and the app in packages/apps/Bluetooth, before it became the packages/modules/Bluetooth mainline module in Android 13 (general platform knowledge); paths differ there."
      ],
      "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_r13",
      "date": "2024-10-07",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r13",
      "date": "2024-10-07",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "9529bb85c051596d8cc34ddac5fd5318e9b6d009",
       "tag": "android-security-14.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 86,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43095",
     "osv_id": "ASB-A-340480881",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PermissionController now treats a platform permission group as granted (and eligible for whole-group auto-grant) only when a permission in that group defined by the platform or a system app is granted, or when all permissions in the group were auto-granted (REVOKE_WHEN_REQUESTED). Grants of permissions defined by other apps that sit in a platform group no longer count.",
      "confidence": "high",
      "backport_notes": [
       "The security-relevant pieces are LightPermInfo.isSystem, the population of isSystem in PermGroupLiveData.onUpdate and LightPackageInfo, LightPermission.isPlatformOrSystem/allowFullGroupGrant, AppPermSubGroup.isGranted/allowFullGroupGrant, and the two GrantPermissionsViewModel changes. The LightPermInfo constructor change must be propagated to every caller, including GrantRevokeTests. If the isGrantedIncludingAppOp to isGranted rename is skipped, the new code that references isGranted must be adjusted to match.",
       "On Android 15, the grantPermissions/*GrantBehavior.kt classes also use isGrantedExcludingRWROrAllRWR and must be switched to allowFullGroupGrant, as f677d33568d9 does. On Android 14 the logic lives in GrantPermissionsViewModel."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "ea7b26fe98c142d0a87d61c3a1c82335ea29c6d2",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2024-43763",
     "osv_id": "ASB-A-356886209",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes the early-exit condition in build_read_multi_rsp (system/stack/gatt/gatt_sr.cc) so a zero-length attribute value is encoded into the GATT Read Multiple (Variable) response instead of terminating the response builder and producing a truncated or empty packet.",
      "confidence": "high",
      "backport_notes": [
       "Single one-line change with no dependencies. The only per-branch difference is the logging macro (VLOG(1) on 14, log::verbose on 15). The new condition relies on total_len <= mtu, which the `if (total_len > mtu) break;` check before it guarantees. Confirm that check exists in the target tree."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "ec76dc86a8e9a7c084abde140356d024c94109d3",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43765",
     "osv_id": "ASB-A-233605527",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DocumentsUI now declares android.permission.HIDE_OVERLAY_WINDOWS and calls Window.setHideOverlayWindows(true) on every BaseActivity window and on the ConfirmFragment dialog window (on S+), so non-system overlay windows are hidden over the picker UI.",
      "confidence": "high",
      "backport_notes": [
       "The change uses com.android.modules.utils.build.SdkLevel. The manifest permission and the setHideOverlayWindows calls belong to the same commit and should land together."
      ],
      "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_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r15",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/DocumentsUI",
       "sha": "6a1dd25117d5406d3befe404335202623778736a",
       "tag": "android-security-14.0.0_r15",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2024-49724",
     "osv_id": "ASB-A-369351375",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens AccountManagerService's validation of an authenticator-supplied KEY_INTENT. After the target activity passes the signature/exported-system check, the intent is explicitly pinned to the resolved component and written back into the result bundle. The exact Intent class check in checkKeyIntentParceledCorrectly now applies to both the original and the re-parceled intent.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, self-contained change in AccountManagerService.java. The 14 and 15 diffs are textually identical apart from line offsets, and both rely on the existing targetActivityInfo signature-capability check and the checkKeyIntentParceledCorrectly helper (present as diff context). The material does not show the code on the older affected branches (12, 12L, 13)."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "985bdc676ac5ea4f35be4b56b74f723afe5b2af3",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 985bdc676ac5",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49733",
     "osv_id": "ASB-A-365738306",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ServiceListing.reload() in SettingsLib now always includes every component already approved in the backing Settings.Secure setting (queried explicitly by ComponentName), instead of only listing services that currently match the intent action, declared permission and validator, so previously approved services no longer disappear from the list given to callbacks.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches only ServiceListing.reload() and its Robolectric test. The same diff was used on 14 and 15. For other branches, check that reload() still has the single-loop permission/validator structure before applying (ServiceInfo.getComponentName() and Intent.setComponent are long-standing APIs (general platform knowledge)).",
       "The new test uses Mockito argThat with Intent.filterEquals to tell the action query apart from the component query. Older branches need a ServiceListingTest with mPm mocked the same way for the test to carry over."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8516dfb89f99fd119fa12045e5c88633ce7478b8",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49734",
     "osv_id": "ASB-A-193031925",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ConnectivityService now installs ingress discard rules (via mBpfNetMaps.setIngressDiscardRule) so that packets to an address used only by a VPN interface are dropped when they arrive on a non-VPN interface. Two frameworks/libs/net changes accompany it and keep IPv4-mapped IPv6 addresses as Inet6Address.",
      "confidence": "medium",
      "backport_notes": [
       "Take all three commits (two in frameworks/libs/net, one in packages/modules/Connectivity) plus follow-up c16addaec604 so legacy VPNs keep working. The ConnectivityService change relies on BpfNetMaps.setIngressDiscardRule/removeIngressDiscardRule, getVpnType, CompareOrUpdateResult and mDeps.isAtLeastT() existing in the target tree.",
       "The libs/net commits carry Merged-In to block automerge to udc-mainline-prod, where frameworks/libs/net does not exist."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/libs/net",
       "sha": "b20d67cbfc24a54dfe1dc9854bac61fdf52f7913",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/libs/net",
       "sha": "e72c61380c52a4450970556e5936c5ec03fd66fb",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "1a0dd8ccde71c2252132d60e5b897fa2f569cc76",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "4e80ab9ca5b9e7db8185cdde240ab0f01a2c0611",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "9eb0da27c4ecff37f7b6d2328b8dee6ba766ad1a",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 8,
     "projects": [
      "platform/frameworks/libs/net",
      "platform/packages/modules/Connectivity"
     ]
    },
    {
     "cve": "CVE-2024-49736",
     "osv_id": "ASB-A-316578327",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings' factory reset screen (MainClear) now checks DynamicSystemManager.isInUse() before keyguard confirmation. If a Dynamic System Update image is in use, it shows an informational AlertDialog (R.string.dsu_is_running) and returns instead of continuing with the reset flow.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained in MainClear.java but depends on R.string.dsu_is_running and R.string.okay existing in Settings resources (not added by this diff), and on android.os.image.DynamicSystemManager / Context.DYNAMIC_SYSTEM_SERVICE. Check that the string resources exist before cherry-picking to 12/12L/13.",
       "The insertion point is immediately before runKeyguardConfirmation(KEYGUARD_REQUEST), after the existing early return. On older branches with restructured code, place the check at the equivalent spot."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "69b3831009d1f1d167e4b91ea47334345f263199",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2024-49737",
     "osv_id": "ASB-A-369103643",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In WindowOrganizerController, the OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT handler now builds a SafeActivityOptions from the operation's options Bundle using the actual caller's pid/uid (caller.mPid, caller.mUid), and ActivityStartController.startActivityInTaskFragment now takes that SafeActivityOptions instead of a raw Bundle.",
      "confidence": "medium",
      "backport_notes": [
       "Self-contained two-file change. Confirm the target branch has SafeActivityOptions.fromBundle(Bundle, int, int), and update all callers of startActivityInTaskFragment to the new SafeActivityOptions parameter. On 15 the call is inside a waitAsyncStart lambda; on 14 it is a direct call."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5eb2aa58018c3e3b42c4574e76e9aa845ab31bff",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49738",
     "osv_id": "ASB-A-370840874",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In libbinder Parcel.cpp, writeInplace(), writeObject() and writeAligned() now call validateReadData() on the end offset of the write before storing into mData+mDataPos, and refuse the write if it fails.",
      "confidence": "medium",
      "backport_notes": [
       "This is a single-commit fix that adds three checks in Parcel.cpp, each placed right after the restart_write label in writeInplace, writeObject and writeAligned. It needs an existing Parcel::validateReadData(size_t) in the target branch. The 14 and 15 hunks are textually identical."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "f3c7aac0e3277f7ebabaab94f34b5c9156412cc9",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2024-49742",
     "osv_id": "ASB-A-363248394",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "NotificationAccessConfirmationActivity in Settings now verifies, before showing the grant dialog, that the requesting package declares a service resolving the NotificationListenerService intent action for the target user and that this service is protected by BIND_NOTIFICATION_LISTENER_SERVICE, finishing the activity otherwise, and it drops the onBackPressed override that prevented dismissal.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit in Settings. It uses NotificationListenerService.SERVICE_INTERFACE, PackageManager.queryIntentServicesAsUser and the existing mUserId field. Move the check from onAllow into onCreate, remove the onBackPressed override, and update the Robolectric test (start_showsDialog no longer holds). The material does not show whether 12/12L/13 have the same code structure."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "b0eec6fdfd5b4f9ba81750da468d26440904142e",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2024-49744",
     "osv_id": "ASB-A-360846772",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens AccountManagerService.checkKeyIntent so that an authenticator-supplied KEY_INTENT is pinned to the activity component the system resolved and validated, and extends the Intent-subclass check in checkKeyIntentParceledCorrectly to the re-parceled copy.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, two-hunk change confined to AccountManagerService.java; the Android 14 and 15 diffs are textually identical apart from line offsets, so it should apply to any branch that already has checkKeyIntent with the isExportedSystemActivity/hasSignatureCapability check and the checkKeyIntentParceledCorrectly simulation (the Android 12/12L/13 variants are not shown 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."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "985bdc676ac5ea4f35be4b56b74f723afe5b2af3",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49745",
     "osv_id": "ASB-A-370831157",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Parcel::growData in libbinder now rejects a grow request with BAD_VALUE when the current data position is beyond the data size, instead of going ahead with growth that per the commit message used to abort or could request a very large allocation.",
      "confidence": "medium",
      "backport_notes": [
       "A single self-contained hunk in Parcel::growData with no dependencies shown. It was applied to the 14 and 15 trees in identical form; only the line offset differs."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "01a1e34929996a8fb9791dec6313627fee4ab1b3",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2024-49749",
     "osv_id": "ASB-A-355461643",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces a broken (effectively dead) overflow check in DGifSlurp with a correct one that rejects non-positive or overflowing ImageDesc.Width * ImageDesc.Height before RasterBits is allocated.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained change to one if-statement in dgif_lib.c DGifSlurp. The diff is identical on 14 and 15 and should apply to any branch that contains the broken `Width < 0 && Height < 0 && ...` guard."
      ],
      "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_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r16",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/giflib",
       "sha": "3281e14f6b9b85473145ee0ae33b8d4b8fbabffe",
       "tag": "android-security-14.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/giflib"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r16"
   ],
   "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-2024-43096": {
     "cve": "CVE-2024-43096",
     "summary": "Adds an mtu == 0 rejection at the top of build_read_multi_rsp in system/stack/gatt/gatt_sr.cc and clamps the EATT channel tx MTU to at least EATT_MIN_MTU_MPS in EattChannel::EattChannelSetTxMTU, so the Read Multiple response buffer is never allocated with zero payload room before the opcode byte is written.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt) and EATT (system/stack/eatt)",
     "weakness": "Missing bounds check leading to out-of-bounds (heap) write",
     "root_cause": "build_read_multi_rsp(tGATT_SR_CMD* p_cmd, uint16_t mtu) allocates a BT_HDR buffer of size sizeof(BT_HDR) + L2CAP_MIN_OFFSET + mtu, sets p to (p_buf + 1) + L2CAP_MIN_OFFSET, and unconditionally writes the response opcode (GATT_RSP_READ_MULTI or GATT_RSP_READ_MULTI_VAR) at *p++ and sets p_buf->len = 1. With mtu == 0 the allocation has no payload bytes, so the opcode write lands one byte past the end of the osi_calloc'd buffer. Nothing validated mtu before the write. On the EATT side, EattChannelSetTxMTU only applied an upper clamp (std::min with EATT_MAX_TX_MTU), so a tx_mtu_ of 0 (or anything below the EATT minimum) could be stored. The code context shows it is called at eatt.h line 70, apparently from the EattChannel constructor. Per index search, it is also called with the peer-provided p_cfg->mtu in eatt_impl.h eatt_l2cap_reconfig_completed when !is_local_cfg.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:build_read_multi_rsp: before computing len and calling osi_calloc, the fix checks `if (mtu == 0)`, logs \"Invalid MTU\" at ERROR, sets p_cmd->status = GATT_ILLEGAL_PARAMETER and returns. This prevents the zero-payload allocation and the subsequent opcode write past the buffer end.",
      "system/stack/eatt/eatt.h:EattChannel::EattChannelSetTxMTU: after the existing std::min clamp to EATT_MAX_TX_MTU, the fix adds `this->tx_mtu_ = std::max<uint16_t>(this->tx_mtu_, EATT_MIN_MTU_MPS)`, establishing a lower bound so an EATT channel's tx_mtu_ cannot be stored below EATT_MIN_MTU_MPS regardless of the value passed in.",
      "Both hunks are in a single commit (bfe316cf9f02 on Android 14, 5318ec44af65 on Android 15). The gatt_sr.cc check is the direct guard. The eatt.h clamp hardens the EATT tx MTU value, but the material does not show that this value is what is passed as mtu to build_read_multi_rsp."
     ],
     "completeness": [
      "Covers: the exact mtu == 0 case in build_read_multi_rsp, which is the only value for which the one-byte opcode write at offset L2CAP_MIN_OFFSET exceeds the allocation; and the EATT tx_mtu_ setter, which is now floored at EATT_MIN_MTU_MPS.",
      "The check only rejects 0; values of 1 or more leave room for the opcode byte. The context shows an is_overflow variable declared in the function, but the per-handle overflow handling itself is not shown.",
      "The fix does not change how the caller obtains mtu for the non-EATT (fixed ATT channel) path; the material does not show that caller or whether its MTU source can be zero. The gatt_sr.cc check applies to all callers since it is inside build_read_multi_rsp.",
      "What the caller does when p_cmd->status is GATT_ILLEGAL_PARAMETER with no response built is not shown in the material."
     ],
     "behavior_changes": [
      "build_read_multi_rsp with mtu == 0 now logs \"Invalid MTU\" at ERROR, sets p_cmd->status = GATT_ILLEGAL_PARAMETER, and returns without allocating or building a Read Multiple response.",
      "EattChannelSetTxMTU now clamps tx_mtu_ into [EATT_MIN_MTU_MPS, EATT_MAX_TX_MTU]; a peer-requested EATT MTU below EATT_MIN_MTU_MPS is raised to that minimum locally (per index search, this setter is used on the remote reconfiguration path in eatt_impl.h eatt_l2cap_reconfig_completed)."
     ],
     "preconditions": [
      "The device acts as a GATT server and processes a Read Multiple / Read Multiple Variable request so that build_read_multi_rsp is invoked (the bulletin classifies this as RCE; the commit message says the OOB write occurs when the mtu parameter is zero).",
      "The mtu passed to build_read_multi_rsp must be 0. How the caller derives mtu is not shown. For EATT channels, per index search, tx_mtu_ can be set from the peer's p_cfg->mtu in eatt_impl.h eatt_l2cap_reconfig_completed when !is_local_cfg.",
      "Bluetooth enabled; the commit references a researcher POC but the material does not state whether pairing is required."
     ],
     "per_major": {
      "14": "same fix: commit bfe316cf9f02 in android-security-14.0.0_r15 (Change-Id I18e4325dbc9d6814220332288c85b114d0415c2f, cherry-picked from internal commits cad927034a37, e5ab6c617683, 8d5c170681e7); at android-security-14.0.0_r29 all 6/6 lines of the patched region are present.",
      "15": "same fix: commit 5318ec44af65 in android-security-15.0.0_r3, identical hunks (only line offsets differ: gatt_sr.cc @@ -162 vs -149, eatt.h @@ -100 vs -99); at android-security-15.0.0_r17 6/6 lines present.",
      "16": "not affected (not listed by the bulletin); at android-security-16.0.0_r8 the patched region is present 6/6.",
      "17": "not affected (not listed by the bulletin); at android-security-17.0.0_r1 the patched region is present 6/6."
     },
     "backport_notes": [
      "Single commit, two independent additive hunks (8 lines). The gatt_sr.cc check uses LOG(ERROR) (already used in that file) and GATT_ILLEGAL_PARAMETER; the eatt.h clamp depends on EATT_MIN_MTU_MPS being defined in the target tree. Android 12/12L/13 are listed as affected by the bulletin but no per-major fix data is provided in the material; verify EattChannelSetTxMTU and build_read_multi_rsp exist in that form before applying."
     ],
     "confidence": "high",
     "unknowns": [
      "The caller of build_read_multi_rsp, how it computes the mtu argument (EATT and fixed ATT channel paths), and what it does after status is set to GATT_ILLEGAL_PARAMETER are not shown.",
      "Whether the vulnerable condition requires pairing/bonding or is reachable pre-authentication is not stated in the material.",
      "Fix status and commit identifiers for Android 12, 12L and 13 (listed as affected) are not provided.",
      "The value of EATT_MIN_MTU_MPS is not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 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-2024-43770": {
     "cve": "CVE-2024-43770",
     "summary": "Adds early returns in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size_tx() returns 0 (channel already closed), so response buffers are no longer allocated with a zero payload size and written past their end.",
     "component": "platform/packages/modules/Bluetooth, system/stack/gatt (GATT server, ATT request processing)",
     "weakness": "Missing length validation leading to heap out-of-bounds write (buffer sized from an unchecked zero payload size)",
     "root_cause": "In gatt_sr.cc, the functions gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req get the ATT payload size from gatt_tcb_get_payload_size_tx(tcb, cid). They use it right away to size a response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET) without checking that it is positive. Per the commit message, in exceptional cases the channel is already closed when the handler runs, so the function returns 0, which leads to an OOB write in later processing. One example from the context: gatt_build_primary_service_rsp writes the opcode and length byte at p = (p_msg + 1) + L2CAP_MIN_OFFSET before its 'p_msg->len + p_msg->offset > payload_size' check (lines 555-568). With a zero-sized payload area, p points at the end of the allocation, so these writes land outside it.",
     "fix_analysis": [
      "gatt_sr.cc:gatts_process_primary_service_req: after 'uint16_t payload_size = gatt_tcb_get_payload_size_tx(tcb, cid);' the fix adds 'if (payload_size == 0) return;' before msg_len is computed and osi_calloc is called.",
      "gatt_sr.cc:gatts_process_find_info: the same zero check is added after payload_size is obtained and before buf_len is computed.",
      "gatt_sr.cc:gatts_process_read_by_type_req: the same zero check is added before msg_len, osi_calloc and the pointer setup.",
      "gatt_sr.cc:gatts_process_read_req: the same zero check is added at function entry, before buf_len is computed.",
      "All four hunks are independent, additive validity checks (21 insertions, no deletions). Neither gatt_tcb_get_payload_size_tx nor the response builders are changed."
     ],
     "completeness": [
      "Covers the four request handlers named in the diff. Each now returns before any allocation or write when payload_size is 0.",
      "gatt_build_primary_service_rsp (context line 540) still calls gatt_tcb_get_payload_size_tx on its own and has no zero check of its own. When it is called from gatts_process_primary_service_req, the new check covers it. The material does not show whether it has other callers.",
      "The response builders are not made robust against a small payload_size by themselves. The fix relies on the early return in the four handlers.",
      "The material does not show whether gatt_tcb_get_payload_size_tx has other users in gatt_sr.cc or elsewhere (for example write or indication paths)."
     ],
     "behavior_changes": [
      "When the channel's payload size is 0 (closed channel), the four handlers now return silently: no response buffer is allocated and neither a response nor an ATT error response is sent for that request.",
      "No change in behavior when payload_size is non-zero."
     ],
     "preconditions": [
      "A remote GATT client sends the local GATT server an ATT request handled by one of the four functions (primary service discovery via read-by-group-type or read-by-type-value, find information, read-by-type, or read request).",
      "The request is processed when the L2CAP/ATT channel for (tcb, cid) is already closed, so gatt_tcb_get_payload_size_tx returns 0. The commit message calls this an exceptional case.",
      "The bulletin rates this Critical RCE. The material does not detail the exact timing needed to reach the closed-channel state."
     ],
     "per_major": {
      "14": "Same fix: commit c7468e64bb5e in android-security-14.0.0_r15 adds four zero checks after gatt_tcb_get_payload_size_tx. 8/8 patched lines are present at android-security-14.0.0_r29.",
      "15": "Adapted fix: commit 339ea2fc1905 in android-security-15.0.0_r3 has the same four hunks, but on this branch the helper is called gatt_tcb_get_payload_size (no _tx suffix). 8/8 lines are present at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin). 5/8 lines of the patched region are present at android-security-16.0.0_r8; the material does not show the exact code form.",
      "17": "Not affected (not listed by the bulletin). 5/8 lines of the patched region are present at android-security-17.0.0_r1; the material does not show the exact code form."
     },
     "backport_notes": [
      "The four hunks are self-contained additive checks. The only branch difference seen is the helper name (gatt_tcb_get_payload_size_tx on 14, gatt_tcb_get_payload_size on 15), so use whatever name the target branch uses at each of the four sites."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact sequence by which the channel closes while a request is still being processed (the commit only says 'exceptional cases').",
      "Whether gatt_tcb_get_payload_size_tx has other call sites in gatt_sr.cc or other GATT files that use the result to size buffers. The material only shows the four fixed sites plus gatt_build_primary_service_rsp.",
      "Whether gatt_build_primary_service_rsp has callers other than gatts_process_primary_service_req.",
      "Whether the builders used by the other three handlers write before checking payload_size.",
      "What the Android 16/17 code looks like at the patched sites (only a 5/8 line match is reported)."
     ],
     "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-43771": {
     "cve": "CVE-2024-43771",
     "summary": "Adds early-return checks for a zero payload size (returned by gatt_tcb_get_payload_size_tx when the channel is already closed) in four ATT server request handlers in system/stack/gatt/gatt_sr.cc. This stops undersized response buffers from being allocated and written out of bounds.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Missing validation of a buffer length (zero payload size) before allocation and writes, leading to an out-of-bounds write",
     "root_cause": "In gatt_sr.cc, gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req call gatt_tcb_get_payload_size_tx(tcb, cid). They use the result directly to size the response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET) and to bound later writes. The commit message says that in exceptional cases the channel can already be closed when the function is called, so the returned payload size is 0. The handlers did not check for this, so later processing caused an OOB write.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:gatts_process_primary_service_req: after payload_size is computed via gatt_tcb_get_payload_size_tx, the function returns immediately if payload_size == 0. This happens before osi_calloc of p_msg and before the response is built.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_find_info: the same zero check is inserted after payload_size is computed and before buf_len is derived.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_by_type_req: the same zero check is inserted before msg_len is computed and p_msg is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_req: the same zero check is inserted before buf_len is computed.",
      "All four checks are identical trivial validity checks (Flag: EXEMPT). The fix is a single commit that adds 21 lines and touches no other files."
     ],
     "completeness": [
      "The fix covers the four server-side request handlers named in the diff. Each now returns silently when the payload size is 0.",
      "The fix does not send an error response or log anything. It only returns.",
      "gatt_build_primary_service_rsp (shown in context) also calls gatt_tcb_get_payload_size_tx without a zero check. It writes the opcode and length bytes before its payload_size comparison. It writes into the buffer allocated by gatts_process_primary_service_req, which is now guarded. The material does not show whether the two calls can return different values.",
      "The material does not show other uses of gatt_tcb_get_payload_size_tx elsewhere in the GATT stack, so they cannot be judged."
     ],
     "behavior_changes": [
      "When the channel's payload size resolves to 0 (channel closed), the requests handled by these four functions are dropped without a response instead of being processed.",
      "No new error code, log or timeout is introduced."
     ],
     "preconditions": [
      "A remote peer with an ATT connection to the local GATT server sends one of the affected ATT requests.",
      "The commit message says the channel must already be closed, so that gatt_tcb_get_payload_size_tx returns 0, when the request is processed. The material does not say how that state is reached.",
      "The bulletin rates the issue Critical RCE. The material does not state any pairing or authentication requirements."
     ],
     "per_major": {
      "14": "same fix: commit c7468e64bb5e in android-security-14.0.0_r15 adds the four zero checks after the gatt_tcb_get_payload_size_tx calls. 8/8 patched lines are present at android-security-14.0.0_r29.",
      "15": "adapted fix: commit 339ea2fc1905 in android-security-15.0.0_r3 adds the same four checks. On this branch the helper is named gatt_tcb_get_payload_size. 8/8 patched lines are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). 5/8 patched-region lines are present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). 5/8 patched-region lines are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single self-contained commit. The only difference between branches is the helper name: gatt_tcb_get_payload_size_tx on 14 and gatt_tcb_get_payload_size on 15. In each of the four handlers, put the check right after whichever call the target branch uses."
     ],
     "confidence": "high",
     "unknowns": [
      "How the channel becomes closed while a request is still being processed. The material only says 'exceptional cases'.",
      "Whether other callers of gatt_tcb_get_payload_size_tx outside the four patched functions exist and are safe. The material does not show them.",
      "Why Android 16/17 show only a partial match of the patched region."
     ],
     "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-49747": {
     "cve": "CVE-2024-49747",
     "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when the transmit payload size returned for the channel is 0 (channel already closed), so a response buffer sized from that zero length is never allocated and written past.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Missing validation of a length value leading to heap out-of-bounds write (undersized buffer allocation)",
     "root_cause": "In gatt_sr.cc, gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req obtain payload_size from gatt_tcb_get_payload_size_tx(tcb, cid) (gatt_tcb_get_payload_size on Android 15) and use it to size the response buffer: sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET. Per the commit message, in exceptional cases the channel can already be closed when the function is called, so it returns 0. The handlers then build the response into a buffer with no room for payload, and subsequent processing writes out of bounds.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:gatts_process_primary_service_req: after payload_size = gatt_tcb_get_payload_size_tx(tcb, cid), return immediately if payload_size == 0, before msg_len is computed and p_msg is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_find_info: same zero check inserted after obtaining payload_size and before buf_len is computed.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_by_type_req: same zero check inserted before msg_len / osi_calloc.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_req: same zero check inserted before buf_len is computed.",
      "All four checks are identical trivial validity checks (no flag, 'Flag: EXEMPT trivial validity checks'). No error response is sent; the request is simply dropped."
     ],
     "completeness": [
      "Covers the four handlers in the diff that allocate a response buffer from the transmit payload size: primary service request, find information, read by type, and read request.",
      "gatt_build_primary_service_rsp (code context at android-security-14.0.0_r29, line 540) also calls gatt_tcb_get_payload_size_tx and uses payload_size only in a 'p_msg->len + p_msg->offset > payload_size' bounds check, after already writing the opcode and length byte into p_msg. This commit does not change it. Whether it can be reached with a zero payload size depends on its callers, which the material does not show.",
      "Other handlers in gatt_sr.cc (for example write, execute write or MTU paths) are not shown in the material, so it is unknown whether any of them size buffers from the same function without a zero check."
     ],
     "behavior_changes": [
      "Primary service, find info, read by type and read requests received when the channel payload size resolves to 0 (channel closed) are now silently dropped: no response and no ATT error response is sent, and no buffer is allocated.",
      "No other behavioral change is visible in the diff. Requests on an open channel are processed as before."
     ],
     "preconditions": [
      "A remote GATT client must send one of the affected ATT requests (primary service discovery via read by group type / find by type value, find information, read by type, read) to the local GATT server.",
      "The request must be processed when gatt_tcb_get_payload_size_tx(tcb, cid) returns 0 for the connection, which per the commit message happens when the channel is already closed.",
      "The bulletin rates this Critical / RCE. The material does not state whether pairing or bonding is required."
     ],
     "per_major": {
      "14": "same fix: commit c7468e64bb5e in android-security-14.0.0_r15 (cherry-pick of 7de5617f7d52), four zero checks after gatt_tcb_get_payload_size_tx; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 339ea2fc1905 in android-security-15.0.0_r3, identical four checks; the existing accessor on this branch is gatt_tcb_get_payload_size(tcb, cid). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin); 5/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); 5/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The added lines are identical across branches. Only the existing accessor name differs (gatt_tcb_get_payload_size_tx on 14, gatt_tcb_get_payload_size on 15). Place each check directly after the payload_size assignment and before the buffer length computation in the four functions."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact sequence by which the channel becomes closed while a request is still being dispatched to these handlers (the commit only says 'exceptional cases').",
      "Whether gatt_tcb_get_payload_size_tx can return 0 in any situation other than a closed channel.",
      "Whether other gatt_sr.cc handlers not shown in the material use the payload size without a zero check.",
      "Why only 5/8 patched-region lines match on Android 16/17; that code 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-2024-49748": {
     "cve": "CVE-2024-49748",
     "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when the per-channel transmit payload size is 0 (channel already closed). As a result, a response buffer with effectively zero payload space is no longer allocated and written into.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Missing length/validity check leading to an out-of-bounds write (heap buffer sized from an unchecked zero payload size)",
     "root_cause": "In gatt_sr.cc, the handlers gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req get payload_size from gatt_tcb_get_payload_size_tx(tcb, cid). They immediately use it to size a response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET, allocated with osi_calloc). Per the commit message, if the channel is already closed when the function is called, the length is zero. The buffer is then only sizeof(BT_HDR) + L2CAP_MIN_OFFSET bytes, and the response-building code that follows writes past the end of the allocation. For example, gatt_build_primary_service_rsp writes the opcode and length bytes via *p++ at (p_msg + 1) + L2CAP_MIN_OFFSET before it compares p_msg->len + p_msg->offset against payload_size.",
     "fix_analysis": [
      "gatt_sr.cc:gatts_process_primary_service_req: after payload_size = gatt_tcb_get_payload_size_tx(tcb, cid), return if payload_size == 0, before msg_len is computed and osi_calloc is called.",
      "gatt_sr.cc:gatts_process_find_info: the same check is inserted after the payload_size fetch and before buf_len is computed.",
      "gatt_sr.cc:gatts_process_read_by_type_req: the same check runs before msg_len is computed, osi_calloc is called and the pointer p into the buffer is set up.",
      "gatt_sr.cc:gatts_process_read_req: the same check runs before buf_len is computed.",
      "Single commit, 21 added lines, no other logic changed. No error response is sent on the new early return."
     ],
     "completeness": [
      "Covers the four request handlers in gatt_sr.cc that size a response buffer from the payload size. The commit message says the unchecked use happens at 'various points' in gatt_sr.cc and says 'Fix all of these'.",
      "gatt_build_primary_service_rsp also calls gatt_tcb_get_payload_size_tx and writes into the caller-supplied buffer before its bounds comparison. It gets no check of its own. It is protected only if every caller is guarded, and the material does not show its callers.",
      "The fix checks only the zero case. It does not change how gatt_tcb_get_payload_size_tx derives the size for a closed channel (that body is not in the material)."
     ],
     "behavior_changes": [
      "When the transmit payload size for the cid is 0, primary service discovery, Find Information, Read By Type and Read requests are silently dropped: neither a response nor an ATT error response is sent."
     ],
     "preconditions": [
      "A remote GATT client sends one of the affected ATT requests (primary service discovery, Find Information, Read By Type, Read) to the local GATT server.",
      "The channel for that request must already be closed when the handler calls gatt_tcb_get_payload_size_tx, so that the size is 0. The commit message calls this an exceptional case.",
      "The commit message cites a researcher POC. The exact sequence it used is not in the material."
     ],
     "per_major": {
      "14": "Same fix: commit c7468e64bb5e in android-security-14.0.0_r15 (cherry-pick of 7de5617f7d52). All 8 patched-region lines are present at android-security-14.0.0_r29. Uses gatt_tcb_get_payload_size_tx.",
      "15": "Adapted fix: commit 339ea2fc1905 in android-security-15.0.0_r3. Same four checks, but the callee is named gatt_tcb_get_payload_size (no _tx suffix) and the line offsets differ. All 8 lines are present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin. Only 5/8 patched-region lines are present at android-security-16.0.0_r8. The reason is not shown in the material.",
      "17": "Not affected per the bulletin. 5/8 patched-region lines are present at android-security-17.0.0_r1. The reason is not shown in the material."
     },
     "backport_notes": [
      "The two branch fixes differ only in the helper name (gatt_tcb_get_payload_size_tx on Android 14, gatt_tcb_get_payload_size on Android 15) and in line offsets. Insert the four checks directly after the payload_size assignment and before any buffer length computation."
     ],
     "confidence": "high",
     "unknowns": [
      "How gatt_tcb_get_payload_size_tx produces 0 for a closed channel (its body is not in the material).",
      "The callers of gatt_build_primary_service_rsp (the index search returned no caller chunks).",
      "The exact timing or sequence the researcher POC used to have the channel closed during request processing.",
      "Whether code outside gatt_sr.cc uses the payload size in a similar unchecked way. The commit only touches gatt_sr.cc.",
      "Why only 5/8 patched-region lines are present on Android 16/17, and why the bulletin does not list those versions."
     ],
     "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-34722": {
     "cve": "CVE-2024-34722",
     "summary": "Adds a new SMP control-block flag, SMP_PAIR_FLAGS_CMD_CONFIRM_SENT, which is set when the local Pairing Confirm is sent. smp_proc_init and smp_proc_rand now reject a peer rand received in BLE legacy pairing before the local confirm was sent, and fail the pairing with SMP_INVALID_PARAMETERS.",
     "component": "platform/packages/modules/Bluetooth, system/stack/smp (BLE Security Manager Protocol, legacy pairing)",
     "weakness": "Missing protocol state/ordering check in legacy pairing (authentication bypass; improper authentication)",
     "root_cause": "In system/stack/smp/smp_act.cc, smp_proc_init (and smp_proc_rand, an identical function on branches from sc-dev to udc-dev) stored the peer's random value into p_cb->rrand after only smp_command_has_invalid_parameters(). They did not check whether the local device had already sent its own Pairing Confirm. Per the fix's comment, in legacy pairing the peer should send its rand only after the local confirm. Per the commit message, when BLE legacy pairing is initiated from the remote side, authentication can be bypassed.",
     "fix_analysis": [
      "Commit 317b4e293cef (android-security-14.0.0_r10), system/stack/smp/smp_int.h: defines SMP_PAIR_FLAGS_CMD_CONFIRM_SENT (1 << 9) next to the existing SMP_PAIR_FLAG* bits. p_cb->flags is uint16_t, so bit 9 fits.",
      "Same commit, system/stack/smp/smp_act.cc:smp_send_confirm: after smp_send_cmd(SMP_OPCODE_CONFIRM, p_cb), it sets p_cb->flags |= SMP_PAIR_FLAGS_CMD_CONFIRM_SENT to record that the local confirm has gone out.",
      "Same commit, smp_act.cc:smp_proc_init: adds a check after smp_command_has_invalid_parameters() and before STREAM_TO_ARRAY into p_cb->rrand. If loc_auth_req and peer_auth_req do not both carry SMP_SC_SUPPORT_BIT (legacy pairing) and the CONFIRM_SENT flag is clear, it builds tSMP_INT_DATA with status SMP_INVALID_PARAMETERS, raises SMP_AUTH_CMPL_EVT via smp_sm_event, and returns without saving the rand.",
      "Commit 6ad3d749f7f6 (android-security-14.0.0_r16), smp_act.cc:smp_proc_rand: adds the same legacy-pairing ordering check. Its commit message says that on branches from sc-dev to udc-dev, SMP has two identical functions, smp_proc_init and smp_proc_rand. Both have the problem, and the first commit patched only smp_proc_init."
     ],
     "completeness": [
      "After both commits, both handlers in smp_act.cc (smp_proc_init and smp_proc_rand) refuse a peer rand in legacy pairing unless the local confirm was sent. The code at android-security-14.0.0_r29 shows the check in both functions.",
      "On Android 14, and on other branches that have smp_proc_rand, the first commit alone is incomplete, as the second commit's message states.",
      "The check is skipped when both loc_auth_req and peer_auth_req carry SMP_SC_SUPPORT_BIT (Secure Connections pairing). The fix does not change the Secure Connections flow.",
      "The diff does not show where SMP_PAIR_FLAGS_CMD_CONFIRM_SENT is cleared. Clearing the flag between pairings is not visible in the material."
     ],
     "behavior_changes": [
      "In legacy (non-Secure Connections) pairing, a peer rand that arrives before the local Pairing Confirm was sent now ends the pairing with SMP_AUTH_CMPL_EVT and status SMP_INVALID_PARAMETERS. Before the fix, the rand was stored in p_cb->rrand.",
      "smp_send_confirm now sets a new flag bit (1 << 9) in p_cb->flags each time it runs. The fix does not change the no-op test mocks for these functions in system/test/mock/mock_stack_smp_act.h (per index search)."
     ],
     "preconditions": [
      "BLE legacy pairing initiated from the remote device, per the commit message.",
      "Legacy pairing: loc_auth_req and peer_auth_req do not both carry SMP_SC_SUPPORT_BIT.",
      "The peer's rand is processed before the local stack has sent its Pairing Confirm (SMP_PAIR_FLAGS_CMD_CONFIRM_SENT not yet set)."
     ],
     "per_major": {
      "14": "Same fix, applied in two commits: 317b4e293cef (flag, smp_send_confirm, smp_proc_init) in android-security-14.0.0_r10, then 6ad3d749f7f6 (smp_proc_rand) in android-security-14.0.0_r16. At android-security-14.0.0_r29, 8/8 lines of the patched region are present.",
      "15": "Not affected: the bulletin does not list Android 15. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "Not affected: the bulletin does not list Android 16. At android-security-16.0.0_r8, 6/8 lines of the patched region are present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 5/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Apply 317b4e293cef first, because it defines SMP_PAIR_FLAGS_CMD_CONFIRM_SENT in smp_int.h and sets it in smp_send_confirm. Then apply 6ad3d749f7f6, which depends on that define. On any branch where smp_act.cc has smp_proc_init and smp_proc_rand as separate functions, both need the check, as the second commit documents.",
      "On customized trees, make sure bit (1 << 9) does not collide with any vendor-added SMP_PAIR_FLAG* bits in smp_int.h. p_cb->flags is uint16_t."
     ],
     "confidence": "high",
     "unknowns": [
      "Where p_cb->flags, and with it the new CONFIRM_SENT bit, is cleared between pairings. This is not shown in the material.",
      "The fix commits and code shape on the Android 15/16/17 branches. Only line-match counts are given. The bulletin facts list '15-next' as an affected version, while the per-major facts say Android 15 is not listed.",
      "Which SMP state-machine events route to smp_proc_init and which to smp_proc_rand. The state table is not included.",
      "The attack vector and the privilege required. The material does not state them."
     ],
     "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-2024-34730": {
     "cve": "CVE-2024-34730",
     "summary": "Adds a per-device \"reconnect_allowed\" flag to the HID host btif layer (persisted as HidReConnectAllowed) and makes hh_open_handler in system/btif/src/btif_hh.cc reject incoming HID connections unless the device is an added device with reconnection allowed or an outgoing connection to it is pending.",
     "component": "platform/packages/modules/Bluetooth, HID Host profile (btif_hh, btif_profile_storage, HidHostService JNI, Floss topshim)",
     "weakness": "Missing authorization check: incoming HID connections accepted regardless of the application's connection policy",
     "root_cause": "In system/btif/src/btif_hh.cc, hh_open_handler processed every BTA_HH open event as an accepted connection: it emitted BTHH_CONN_STATE_CONNECTING and proceeded to set up the device without checking whether the application had allowed connections from that device. The HID connection policy (BluetoothProfile.CONNECTION_POLICY_ALLOWED) was evaluated only in the Java HidHostService and was not propagated to the native stack; the native disconnect() took only an address. The only guard visible in the material is the Java-side okToConnect check in MESSAGE_CONNECT_STATE_CHANGED, which acts once the HAL reports CONNECTED (virtualUnPlugNative). Per the commit message, a remote device could initiate a HID connection even when HID connection was disabled by the connection policy.",
     "fix_analysis": [
      "system/include/hardware/bt_hh.h: bthh_interface_t.disconnect gains a second parameter bool reconnect_allowed so the application can tell the native stack whether future incoming connections from this device are acceptable.",
      "android/app/src/com/android/bluetooth/hid/HidHostService.java (MESSAGE_DISCONNECT handler): computes reconnectAllowed = getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_ALLOWED and passes it to disconnectHidNative(byte[], boolean). android/app/jni/com_android_bluetooth_hid_host.cpp: disconnectHidNative takes jboolean reconnect_allowed, forwards it to sBluetoothHidInterface->disconnect, JNI signature changed to \"([BZ)Z\".",
      "system/btif/include/btif_hh.h: btif_hh_added_device_t gains bool reconnect_allowed; btif_hh_add_added_dev gains a reconnect_allowed parameter.",
      "system/btif/src/btif_hh.cc: new btif_hh_find_added_dev(addr) looks up the added-device table; new static hh_connection_allowed(bda) returns true only if the device is an added device with reconnect_allowed == true, or btif_hh_cb.pending_conn_address == bda (an outgoing connect was requested).",
      "system/btif/src/btif_hh.cc:hh_open_handler: at entry, if !hh_connection_allowed(conn.bda), logs \"Reject unexpected incoming HID Connection\", sets dev_status = BTHH_CONN_STATE_DISCONNECTED on the device found by btif_hh_find_connected_dev_by_handle (if any), calls hh_connect_complete(conn.handle, conn.bda, BTIF_HH_DEV_DISCONNECTED) and returns, skipping the CONNECTING callback and device setup.",
      "system/btif/src/btif_hh.cc:disconnect(bd_addr, reconnect_allowed): when reconnect_allowed is false, finds the added device and if it currently allows reconnection, clears reconnect_allowed and persists via btif_storage_set_hid_connection_policy(addr, false). This happens before the connected-device lookup, so the policy is recorded even if the device is not currently opened.",
      "system/btif/src/btif_hh.cc:connect(bd_addr): if the device is already added with reconnect_allowed == false, sets it to true and persists it, so an application-initiated connect re-enables incoming reconnections.",
      "system/btif/src/btif_hh.cc: BTA_HH_GET_DSCP_EVT handling in btif_hh_upstreams_evt and set_info() now call btif_hh_add_added_dev(..., true) and btif_storage_set_hid_connection_policy(addr, true), so newly added devices default to allowed.",
      "system/btif/src/btif_profile_storage.cc: adds key BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED (\"HidReConnectAllowed\"), btif_storage_set_hid_connection_policy (btif_config_set_int) and btif_storage_get_hid_connection_policy (btif_config_get_int, defaulting to 1 when the key is absent for backward compatibility). btif_storage_load_bonded_hid_info reads the policy and passes it to btif_hh_add_added_dev; btif_storage_remove_hid_info removes the key.",
      "system/gd/rust/topshim/src/profiles/hid_host.rs and system/gd/rust/linux/stack/src/bluetooth.rs (Floss): disconnect callers updated to pass true for the new parameter, i.e. Floss never marks a device as disallowed; this is an interface-compatibility change."
     ],
     "completeness": [
      "Enforcement is placed in hh_open_handler, which handles the BTA_HH open event; outgoing connections are allowed via the pending_conn_address check, so only connections without a pending outgoing request from devices not marked allowed are rejected.",
      "The policy only reaches the native layer through the Java MESSAGE_DISCONNECT path (disconnectHidNative with reconnectAllowed). The diff does not show any other propagation of a connection-policy change (for example setting the policy to FORBIDDEN while the device is not connected) to the native flag; whether such paths exist is not visible in the material.",
      "Backward compatibility default: btif_storage_get_hid_connection_policy returns allowed when the HidReConnectAllowed key is absent, so devices bonded before the fix are treated as allowed at the btif layer until a disconnect with a policy other than ALLOWED records the flag. The existing Java okToConnect/virtualUnPlugNative check in MESSAGE_CONNECT_STATE_CHANGED remains for those devices.",
      "The rejection path sets dev_status to DISCONNECTED and reports BTIF_HH_DEV_DISCONNECTED via hh_connect_complete; the diff does not show an explicit lower-layer close in that path, so what hh_connect_complete does with the underlying channel is not visible in the material.",
      "Floss (Rust) callers pass reconnect_allowed = true unconditionally, so on Floss added devices are never marked disallowed by this fix; the native gate still rejects devices that are not in the added-device table and have no pending outgoing connect. Floss separately rejects unbonded devices in its BtifHHCallbacks implementation."
     ],
     "behavior_changes": [
      "Incoming HID connections from devices that are not added with reconnect_allowed and have no pending outgoing connect are now rejected in btif with a connection_state_cb of DISCONNECTED (log: \"Reject unexpected incoming HID Connection\") instead of proceeding to CONNECTING/CONNECTED.",
      "New persisted per-device config key \"HidReConnectAllowed\" written on device add (true), on connect() when previously disallowed (true) and on disconnect() with policy not ALLOWED when previously allowed (false); removed with the device's HID info.",
      "HAL interface change: bthh_interface_t.disconnect now takes (RawAddress*, bool); JNI method disconnectHidNative signature changed to ([BZ)Z. Any out-of-tree caller of the old signature must be updated.",
      "Disconnecting a device whose Java connection policy is not CONNECTION_POLICY_ALLOWED now also disables native acceptance of its future incoming HID connections; a subsequent application-initiated connect re-enables it."
     ],
     "preconditions": [
      "Bluetooth enabled with the HID Host profile (HidHostService / btif_hh) active.",
      "A remote device able to initiate a HID connection to the Android host; per the commit message the tested scenario is a paired HID device whose HID connection was disabled in Bluetooth device settings."
     ],
     "per_major": {
      "14": "same fix: commit 9529bb85c051 (RESTRICT AUTOMERGE, Change-Id Iba2ac3502bf1e6e4ac1f60ed64b1b074facd880b) in android-security-14.0.0_r13; at android-security-14.0.0_r29 7/8 lines of the patched region are present and the context shows the reconnect_allowed plumbing (bt_hh.h, JNI, HidHostService, btif_hh.h, storage key) in place.",
      "15": "not affected per the bulletin; the commit message states the vulnerability no longer exists on main because of feature request b/324093729. 5/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin; 4/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; 4/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit, but it changes the bthh_interface_t.disconnect HAL signature, so all callers must be updated together: JNI (com_android_bluetooth_hid_host.cpp plus the sMethods table), HidHostService.java native declaration, and the Rust topshim/Floss callers if present in the target branch. The android-security-14.0.0_r29 context shows these carry the new signature.",
      "For Android 12/12L the Bluetooth stack lived in system/bt and the app in packages/apps/Bluetooth, before it became the packages/modules/Bluetooth mainline module in Android 13 (general platform knowledge); paths differ there."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether hh_connect_complete with BTIF_HH_DEV_DISCONNECTED closes the underlying HID/L2CAP channel, or only reports state; the function body is not in the material.",
      "Whether any Java path other than MESSAGE_DISCONNECT propagates a connection-policy change to the native reconnect_allowed flag.",
      "The exact content of feature b/324093729 that made Android 15+ not affected, and what the partial line matches on 15/16/17 reflect.",
      "How the fix was applied to Android 12, 12L and 13 (not in the material)."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43095": {
     "cve": "CVE-2024-43095",
     "summary": "PermissionController now treats a platform permission group as granted (and eligible for whole-group auto-grant) only when a permission in that group defined by the platform or a system app is granted, or when all permissions in the group were auto-granted (REVOKE_WHEN_REQUESTED). Grants of permissions defined by other apps that sit in a platform group no longer count.",
     "component": "platform/packages/modules/Permission, PermissionController (runtime permission grant flow and the LightAppPermGroup/LightPermission data model)",
     "weakness": "Improper authorization: the group-level grant decision trusted permissions defined by non-system packages that were members of a platform permission group",
     "root_cause": "In LightAppPermGroup.AppPermSubGroup, isGranted and isGrantedExcludingRWROrAllRWR were computed over every LightPermission in the subgroup, regardless of which package defined the permission. When a permission defined by a non-platform, non-system package was in a platform permission group (permGroupInfo.packageName == Utils.OS_PKG) and was granted, the subgroup reported itself as granted. GrantPermissionsViewModel used group.foreground/background.isGrantedExcludingRWROrAllRWR together with canAutoGrantWholeGroup to call grantForegroundRuntimePermissions/grantBackgroundRuntimePermissions for other requested permissions in the same group. After a user grant, it also set groupState.state = STATE_ALLOWED unconditionally, without checking whether the affected permissions were platform or system ones. LightPermInfo carried no information about whether the defining package was a system app, so no such check could be made.",
     "fix_analysis": [
      "LightPermInfo.kt: adds a nullable isSystem field (whether a system app defines the permission) and changes the constructor to LightPermInfo(permInfo: PermissionInfo, isSystem: Boolean?).",
      "LightPackageInfo.kt: the constructor now builds LightPermInfo(perm, pI.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM != 0) for the permissions the package defines.",
      "PermGroupLiveData.kt:onUpdate: collects PermissionInfo objects into permInfos and registers LightPackageInfoLiveData[packageName, UserHandle.SYSTEM] sources for every defining package. It returns early until all of them are initialized, then builds LightPermInfo(permInfo, isSystem) with isSystem derived from the defining package's appFlags and FLAG_SYSTEM, and only then posts the PermGroup value. Previously the value was posted before the package live datas were set up.",
      "LightPermInfoLiveData.kt:loadDataAndPostValue: passes null for isSystem.",
      "LightPermission.kt: renames isGrantedIncludingAppOp to isGranted. Adds isPlatformOrSystem = permInfo.packageName == Utils.OS_PKG || permInfo.isSystem == true, and allowFullGroupGrant = isGranted && !isRevokeWhenRequested.",
      "LightAppPermGroup.kt: adds isPlatformPermissionGroup = permGroupInfo.packageName == Utils.OS_PKG and passes it to both AppPermSubGroup instances. For platform groups, AppPermSubGroup.isGranted now also requires the granted permission to be isPlatformOrSystem. isGrantedExcludingRWROrAllRWR is renamed allowFullGroupGrant. It is true only if (a) some permission has allowFullGroupGrant and either the group is not a platform group or the permission isPlatformOrSystem, or (b) all permissions are granted and REVOKE_WHEN_REQUESTED. specialLocationGrant still overrides both.",
      "GrantPermissionsViewModel.kt: the check that background access requires granted foreground access, and the auto-grant-whole-group check, now use group.foreground/background.allowFullGroupGrant.",
      "GrantPermissionsViewModel.kt (user-grant result path): tracks the affectedPermissions actually passed to the grant call and computes shouldDenyFullGroupGrant = group.isPlatformPermissionGroup && affectedPermissions.none { isPlatformOrSystem }. When that is true, groupState.state is set to STATE_UNKNOWN instead of STATE_ALLOWED.",
      "Remaining files (AutoRevokePermissions.kt, RuntimePermissionsUpgradeController.kt, ReviewPermissionsFragment.java, AppPermissionViewModel.kt, ReviewPermissionsViewModel.kt, KotlinUtils.kt, SafetyNetLogger.java, tests): rename of isGrantedIncludingAppOp to isGranted and updates to LightPermInfo constructor calls. No logic change."
     ],
     "completeness": [
      "Covers the group-level grant signals (AppPermSubGroup.isGranted, allowFullGroupGrant) and both GrantPermissionsViewModel paths that auto-grant or mark a group as allowed, for platform permission groups only.",
      "Non-platform permission groups are explicitly exempt through !isPlatformPermissionGroup. Their any-granted semantics are unchanged.",
      "isSystem is only populated through PermGroupLiveData and LightPackageInfo. LightPermInfoLiveData passes null, so a permission obtained through that path counts as platform-or-system only if its package is Utils.OS_PKG. If the defining package's LightPackageInfoLiveData value is null (the TODO about packages not installed for the system user remains), isSystem is null and the permission is treated as non-system.",
      "The onUpdate path in GrantPermissionsViewModel that sets STATE_ALLOWED when all affectedPermissions are isGranted and not isRevokeWhenRequested is unchanged apart from the rename.",
      "The REVOKE_WHEN_REQUESTED clause (permissions.all { isGranted && isRevokeWhenRequested }) still grants whole-group status without checking isPlatformOrSystem, as the commit message describes."
     ],
     "behavior_changes": [
      "PermGroupLiveData posts no new value until LightPackageInfoLiveData is initialized for every package that defines a permission in the group, plus the group's own package. Previously the value was posted immediately.",
      "For platform permission groups, consumers of LightAppPermGroup.foreground/background.isGranted no longer report the group as granted when the only granted permissions in it are defined by non-platform, non-system packages.",
      "After a user grant in GrantPermissionsViewModel in a platform group where none of the affected permissions are platform or system ones, the group state becomes STATE_UNKNOWN instead of STATE_ALLOWED.",
      "Internal API changes inside PermissionController: LightPermission.isGrantedIncludingAppOp is renamed isGranted; AppPermSubGroup.isGrantedExcludingRWROrAllRWR is renamed allowFullGroupGrant; the LightPermInfo constructor gains a required isSystem parameter; the AppPermSubGroup constructor gains isPlatformPermissionGroup."
     ],
     "preconditions": [
      "A permission defined by a non-platform, non-system package belongs to a platform permission group (permGroupInfo.packageName == Utils.OS_PKG) and is granted to the app.",
      "The app then requests other permissions of that platform group through the GrantPermissionsViewModel request flow, where the pre-fix code consulted isGrantedExcludingRWROrAllRWR to decide on a whole-group auto-grant.",
      "specialLocationGrant is null. Location provider and extra location packages bypass the per-permission computation in both the old and the new code."
     ],
     "per_major": {
      "14": "Same fix: ea7b26fe98c1 in android-security-14.0.0_r15, with the grant-flow changes made directly in GrantPermissionsViewModel. At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "Adapted fix: f677d33568d9 in android-security-15.0.0_r3. It makes the same data-model changes, but the group-grant checks also land in the refactored grantPermissions/BackgroundGrantBehavior.kt, GrantBehavior.kt, HealthGrantBehavior.kt, LocationGrantBehavior.kt and StorageGrantBehavior.kt. The grant-result path uses STATE_GRANTED/STATE_UNKNOWN. At android-security-15.0.0_r17, 7/8 lines are present.",
      "16": "Not affected (not listed by the bulletin). At android-security-16.0.0_r8, 6/8 lines of the patched region are present.",
      "17": "Not affected (not listed by the bulletin). At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The security-relevant pieces are LightPermInfo.isSystem, the population of isSystem in PermGroupLiveData.onUpdate and LightPackageInfo, LightPermission.isPlatformOrSystem/allowFullGroupGrant, AppPermSubGroup.isGranted/allowFullGroupGrant, and the two GrantPermissionsViewModel changes. The LightPermInfo constructor change must be propagated to every caller, including GrantRevokeTests. If the isGrantedIncludingAppOp to isGranted rename is skipped, the new code that references isGranted must be adjusted to match.",
      "On Android 15, the grantPermissions/*GrantBehavior.kt classes also use isGrantedExcludingRWROrAllRWR and must be switched to allowFullGroupGrant, as f677d33568d9 does. On Android 14 the logic lives in GrantPermissionsViewModel."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how the non-system permission in a platform group initially becomes granted in the reported scenario.",
      "The exact downstream effect of setting groupState.state to STATE_UNKNOWN after a grant is not fully visible in the provided context.",
      "The material does not show whether the referenced DynamicPermissionsTest is present on the security branch (it is not in the diff).",
      "The exact names of the GrantPermissionsViewModel functions changed on Android 14 are not shown in the diff hunk headers."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43763": {
     "cve": "CVE-2024-43763",
     "summary": "Changes the early-exit condition in build_read_multi_rsp (system/stack/gatt/gatt_sr.cc) so a zero-length attribute value is encoded into the GATT Read Multiple (Variable) response instead of terminating the response builder and producing a truncated or empty packet.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Incorrect loop termination / logic error: a valid zero-length value was treated as buffer exhaustion, so the response was cut short (bulletin type: DoS)",
     "root_cause": "In build_read_multi_rsp, for each requested handle the code computes len = std::min(p_rsp->attr_value.len, mtu - total_len) and then did `if (len == 0) break;` with a \"Buffer space not enough\" log. That condition mixes up two cases: the buffer really being full (mtu - total_len == 0) and the attribute simply having a zero-length value (attr_value.len == 0). A zero-length value is valid and, in variable-length mode, should still add its 2-byte length field (value 0) to the packet. Because the loop broke out instead, the response stopped at the first zero-length value. If the first characteristic had length 0, the commit message says an empty packet was returned (p_buf->len starts at 1, the opcode byte).",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:build_read_multi_rsp: the break condition `if (len == 0)` is replaced with `if (total_len == mtu && p_rsp->attr_value.len > 0)`. In the post-fix code, the earlier `if (total_len > mtu) break;` guarantees total_len <= mtu at this point. So the new condition fires only when the buffer is exactly full and the attribute has data that cannot fit.",
      "With the new condition, a zero-length attr_value falls through. In variable_len mode, UINT16_TO_STREAM writes a 2-byte length of 0 and p_buf->len += 2. ARRAY_TO_STREAM with len 0 appends no value bytes, and the loop moves on to the next handle.",
      "The overflow handling below is unchanged for non-zero values that only partly fit: len < attr_value.len sets is_overflow, the partial value is written, then the loop breaks."
     ],
     "completeness": [
      "The fix is limited to the single break condition in build_read_multi_rsp. The handle-mismatch path (GATT_NOT_FOUND) and the partial-fit overflow path are untouched.",
      "The same loop serves both variable-length and fixed-length Read Multiple; variable_len only controls the 2-byte length prefix. For fixed-length responses, a zero-length value now adds nothing and the loop continues.",
      "Nothing changes in how p_rsp entries are queued into multi_rsp_q or in the caller. The material does not show the surrounding request handling, so other response-building paths cannot be judged from it."
     ],
     "behavior_changes": [
      "Read Multiple (Variable) responses now include a 2-byte length field of 0 for zero-length attribute values and continue to later handles, instead of stopping at the first zero-length value.",
      "The 'Buffer space not enough for this data item, skipping' verbose log at this check is now emitted only when the buffer is exactly full and the item has non-zero length."
     ],
     "preconditions": [
      "A local GATT server handling a Read Multiple / Read Multiple Variable request (p_cmd->multi_req with num_handles entries and responses queued in p_cmd->multi_rsp_q) in system/stack/gatt/gatt_sr.cc.",
      "At least one of the requested attributes has a zero-length value (p_rsp->attr_value.len == 0), notably as the first handle in the request.",
      "The request comes from a remote GATT client. Per index search, no other callers of build_read_multi_rsp were found."
     ],
     "per_major": {
      "14": "same fix: ec76dc86a8e9 in android-security-14.0.0_r15 (cherry-picked from ba907afffe1f, Change-Id Ida4f6b566cf9fa40fc5330d8084c29669ccaa608). One-line condition change, using VLOG(1). Patched region fully present at android-security-14.0.0_r29 (2/2 lines).",
      "15": "same fix: 124c2ac6cffb in android-security-15.0.0_r3. Identical condition change, using log::verbose instead of VLOG(1). Patched region fully present at android-security-15.0.0_r17 (2/2 lines).",
      "16": "not affected per the bulletin. 0/2 lines of the patched region present at android-security-16.0.0_r8; the material does not show the code there.",
      "17": "not affected per the bulletin. 0/2 lines of the patched region present at android-security-17.0.0_r1; the material does not show the code there."
     },
     "backport_notes": [
      "Single one-line change with no dependencies. The only per-branch difference is the logging macro (VLOG(1) on 14, log::verbose on 15). The new condition relies on total_len <= mtu, which the `if (total_len > mtu) break;` check before it guarantees. Confirm that check exists in the target tree."
     ],
     "confidence": "high",
     "unknowns": [
      "How a truncated or empty response leads to the DoS described by the bulletin (which side fails and how) is not shown in the material.",
      "The transport and request dispatch path that reaches build_read_multi_rsp are not shown.",
      "The state of build_read_multi_rsp on Android 16/17 (why 0/2 lines match) is not shown. The bulletin lists only 12 through 15-next as affected.",
      "The second bug id (356886209) is referenced but its content is not in the material."
     ],
     "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-2024-43765": {
     "cve": "CVE-2024-43765",
     "summary": "DocumentsUI now declares android.permission.HIDE_OVERLAY_WINDOWS and calls Window.setHideOverlayWindows(true) on every BaseActivity window and on the ConfirmFragment dialog window (on S+), so non-system overlay windows are hidden over the picker UI.",
     "component": "platform/packages/apps/DocumentsUI, BaseActivity and picker/ConfirmFragment (document picker UI)",
     "weakness": "UI redress / clickjacking: missing restriction of overlay windows over security-relevant UI (improper restriction of rendered UI layers)",
     "root_cause": "The DocumentsUI activities derived from BaseActivity and the ConfirmFragment overwrite/open-tree confirmation dialog did not ask the system to hide non-system overlay windows. Other apps' overlay windows could therefore be drawn on top of the picker and its confirmation dialog. The commit describes this as enabling a clickjacking attack. The manifest also did not declare the HIDE_OVERLAY_WINDOWS permission, which the fix adds alongside the setHideOverlayWindows calls.",
     "fix_analysis": [
      "AndroidManifest.xml: adds <uses-permission android:name=\"android.permission.HIDE_OVERLAY_WINDOWS\"/>.",
      "src/com/android/documentsui/BaseActivity.java:onCreate: right after recording mStartTime, calls getWindow().setHideOverlayWindows(true) if SdkLevel.isAtLeastS(). This applies to every activity that extends BaseActivity. Adds the import com.android.modules.utils.build.SdkLevel.",
      "src/com/android/documentsui/picker/ConfirmFragment.java (dialog creation, onCreateDialog): stores the result of builder.create() instead of returning it directly. If SdkLevel.isAtLeastS(), calls dialog.getWindow().setHideOverlayWindows(true) before returning the dialog, so the dialog's own window is also covered. Adds the SdkLevel import.",
      "Both calls are gated on SdkLevel.isAtLeastS(), and the manifest declares minSdkVersion 29. Window.setHideOverlayWindows was added in API 31 (general platform knowledge)."
     ],
     "completeness": [
      "Covers all activity windows created through BaseActivity.onCreate and the ConfirmFragment dialog window.",
      "Only the ConfirmFragment dialog is handled explicitly. This diff does not touch any other DocumentsUI dialog or popup that has its own Window. The material does not show whether such windows exist or whether they need the same change.",
      "Activities that do not extend BaseActivity, if any, are not covered. The material does not list them.",
      "Below S (API 31) the SdkLevel gate skips the call, so no overlay hiding applies there even though minSdkVersion is 29."
     ],
     "behavior_changes": [
      "On S and later, non-system overlay windows are hidden while a DocumentsUI activity or the ConfirmFragment dialog is shown.",
      "DocumentsUI requests one additional permission, HIDE_OVERLAY_WINDOWS.",
      "No change on devices below S (API 29 and 30) because of the SdkLevel.isAtLeastS() gate."
     ],
     "preconditions": [
      "A DocumentsUI activity or the ConfirmFragment dialog is on screen. Ordinary apps launch the picker activities with intents such as ACTION_OPEN_DOCUMENT and ACTION_OPEN_DOCUMENT_TREE (for example development/samples/ApiDemos DocumentsSample.onClick, per index search).",
      "Another app able to draw overlay windows on top of the picker. The material describes the attack only as 'clickjacking'.",
      "For the fix to be active, the device must run Android 12 (S) or later."
     ],
     "per_major": {
      "14": "same fix: commit 6a1dd25117d5 in android-security-14.0.0_r15 (cherry-pick of 4f610dc48300, Change-Id I511730856be58cad3e13fa50bfac1e1ee2f5fee0). All 8 patched lines are present at android-security-14.0.0_r29.",
      "15": "same fix: commit c8e21a60d784 in android-security-15.0.0_r3. Content is identical; only the BaseActivity hunk position differs. All 8 lines are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). The patched code (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). The patched code (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change uses com.android.modules.utils.build.SdkLevel. The manifest permission and the setHideOverlayWindows calls belong to the same commit and should land together."
     ],
     "confidence": "high",
     "unknowns": [
      "The material describes the clickjacking scenario, and which user decision was being hijacked, only through the commit title and bug reference b/233605527.",
      "Whether DocumentsUI has other dialog or popup windows that would also need setHideOverlayWindows. The diff only touches ConfirmFragment.",
      "Whether the fix was applied to the Android 12/12L/13 branches, which the bulletin lists as affected. The material gives no per-major facts for them."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49724": {
     "cve": "CVE-2024-49724",
     "summary": "Hardens AccountManagerService's validation of an authenticator-supplied KEY_INTENT. After the target activity passes the signature/exported-system check, the intent is explicitly pinned to the resolved component and written back into the result bundle. The exact Intent class check in checkKeyIntentParceledCorrectly now applies to both the original and the re-parceled intent.",
     "component": "platform/frameworks/base, services/core AccountManagerService (accounts)",
     "weakness": "Incomplete validation of untrusted authenticator input: the activity that was validated by resolution was not bound to the intent actually delivered, and the Intent subclass check covered only one of the two compared intents.",
     "root_cause": "In AccountManagerService checkKeyIntent, the KEY_INTENT was resolved via pm.resolveActivityAsUser(intent, 0, mAccounts.userId) and the resolved ActivityInfo was checked (isExportedSystemActivity or hasSignatureCapability with authUid, CertCapabilities.AUTH). On success the method returned true but left the intent in the bundle unchanged, so the delivered intent was not pinned to the component that had been validated. Separately, in checkKeyIntentParceledCorrectly only the original intent was required to be exactly Intent.class. The re-parceled simulateIntent was only compared via filterEquals, so an Intent subclass on the re-parceled side was not rejected.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountManagerService.java:checkKeyIntent: after the targetActivityInfo check passes, the fix calls intent.setComponent(targetActivityInfo.getComponentName()) and bundle.putParcelable(AccountManager.KEY_INTENT, intent) before returning true. The bundle therefore carries an explicit intent targeting the validated activity.",
      "services/core/java/com/android/server/accounts/AccountManagerService.java:checkKeyIntentParceledCorrectly: the early `intent != null && intent.getClass() != Intent.class` check is removed. After the null handling (`intent == null` returns `simulateIntent == null`), a new check returns false if either intent.getClass() or simulateIntent.getClass() is not exactly Intent.class, before the filterEquals comparison."
     ],
     "completeness": [
      "Covers both the pinning of the validated component into the delivered intent and the symmetric class check on the re-parceled intent, within the single file touched.",
      "The existing checks in checkKeyIntent are unchanged by this fix: null intent accepted, empty ClipData set, null resolveInfo rejected, \"content\" scheme rejected, and an exported system activity or AUTH signature capability required.",
      "The new check in checkKeyIntentParceledCorrectly dereferences simulateIntent without a null check. If intent is non-null and simulateIntent is null, this now throws NullPointerException where the old code fell through to intent.filterEquals(simulateIntent). Whether that state is reachable is not shown in the material.",
      "Only the KEY_INTENT path in AccountManagerService is touched; the material shows no other paths."
     ],
     "behavior_changes": [
      "Result bundles validated by checkKeyIntent now contain an explicit-component intent (component set to the resolved activity) instead of whatever component the authenticator supplied.",
      "checkKeyIntentParceledCorrectly now returns false when the re-parceled intent is an Intent subclass, not only when the original is.",
      "Potential NullPointerException in checkKeyIntentParceledCorrectly if the original intent is non-null but the re-parceled one is null (previously that case reached filterEquals)."
     ],
     "preconditions": [
      "An account authenticator returns a result Bundle containing AccountManager.KEY_INTENT to AccountManagerService.",
      "The intent must pass the existing checks in checkKeyIntent: it resolves for mAccounts.userId, does not use the content scheme, and resolves to an exported system activity or to an activity in a package with the AUTH signature capability relative to the authenticator uid."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 985bdc676ac5 in android-security-14.0.0_r16 (cherry-pick, Change-Id Ied7961c73299681aa5b523cf3f00fd905893116f). At android-security-14.0.0_r29 the setComponent/putParcelable lines are present (7/8 patched-region lines present).",
      "15": "same fix: platform/frameworks/base 5018e2273cb3 in android-security-15.0.0_r4, identical hunks at shifted line numbers; 7/8 patched-region lines 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": [
      "Single-commit, self-contained change in AccountManagerService.java. The 14 and 15 diffs are textually identical apart from line offsets, and both rely on the existing targetActivityInfo signature-capability check and the checkKeyIntentParceledCorrectly helper (present as diff context). The material does not show the code on the older affected branches (12, 12L, 13)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which callers of checkKeyIntent/checkKeyIntentParceledCorrectly are involved; the material does not show them.",
      "Why 7/8 rather than 8/8 patched-region lines match in the latest tags.",
      "Whether the simulateIntent-null state in checkKeyIntentParceledCorrectly is reachable in practice.",
      "How the pre-fix behavior could be abused; the bulletin only states EoP, High.",
      "The state of this code on Android 12, 12L and 13, which the bulletin lists as affected."
     ],
     "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-2024-49733": {
     "cve": "CVE-2024-49733",
     "summary": "ServiceListing.reload() in SettingsLib now always includes every component already approved in the backing Settings.Secure setting (queried explicitly by ComponentName), instead of only listing services that currently match the intent action, declared permission and validator, so previously approved services no longer disappear from the list given to callbacks.",
     "component": "platform/frameworks/base, packages/SettingsLib (com.android.settingslib.applications.ServiceListing)",
     "weakness": "Incomplete listing of granted access: approved service components were left out of the list delivered to callbacks when they no longer met the current listing criteria (intent action match, required permission, validator). The bulletin type is ID. The likely effect is that an existing grant was hidden from the management UI.",
     "root_cause": "ServiceListing.reload() built mServices only from pmWrapper.queryIntentServicesAsUser(new Intent(mIntentAction), ...). It then dropped any ServiceInfo whose info.permission did not equal mPermission or that failed mValidator.test(info). The set of approved components (mEnabledServices, parsed from Settings.Secure mSetting in loadEnabledServices()) was never used when building the list. A component that was approved earlier, possibly under laxer criteria, and that now does not resolve for mIntentAction, does not declare mPermission, or fails the validator was therefore left out of the list delivered to Callback.onServicesReloaded(), even though it stayed approved in the setting.",
     "fix_analysis": [
      "packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java:reload(): the first loop over the mIntentAction query now applies the permission check and mValidator only to components that are NOT in mEnabledServices. Components that pass are added to mServices as before. Components that are already enabled are skipped in this loop.",
      "ServiceListing.java:reload(): a new second loop goes through mEnabledServices. For each ComponentName cn it calls pmWrapper.queryIntentServicesAsUser(new Intent().setComponent(cn), flags, user) and adds every returned resolveInfo.serviceInfo to mServices with no permission or validator filtering. As a result, every approved component that still resolves is always listed.",
      "packages/SettingsLib/tests/robotests/.../ServiceListingTest.java: adds testHasPermissionWithoutMeetingCurrentRegs. The test approves pkg2/service2 in TEST_SETTING, makes it fail the validator and keeps it out of the action query. It then asserts that pkg2/service2 is still delivered, together with the validated pkg service, and that queryIntentServicesAsUser is called twice. Existing tests now also set service names (s1.name/s2.name), presumably because the new code calls getComponentName()."
     ],
     "completeness": [
      "Covers the listing path only: ServiceListing.reload() now shows all approved components that resolve through an explicit component query for the current user with the same flags.",
      "Approved components that no longer resolve at all (the query returns nothing) are still not listed. The fix does not prune or otherwise change the stored approvals in mSetting.",
      "The approval logic itself (setEnabled / saveEnabledServices) is unchanged. The fix only makes existing grants visible.",
      "The second loop deliberately applies neither the mPermission check nor mValidator to approved components. The commit message says this is intended ('Regardless of what the current criteria is')."
     ],
     "behavior_changes": [
      "Services whose component is in the Settings.Secure setting now appear in the list passed to Callback.onServicesReloaded() even if they do not match mIntentAction, do not declare mPermission, or fail mValidator.",
      "reload() makes one extra queryIntentServicesAsUser call per approved ComponentName. The test asserts 2 calls with one approved component.",
      "The 'Skipping ... does not require the permission' warning is no longer logged for components that are already approved."
     ],
     "preconditions": [
      "A ServiceListing instance built with a Settings.Secure setting (mSetting) that already contains an approved ComponentName.",
      "That approved component resolves by explicit component but does not resolve for mIntentAction, does not declare mPermission, or fails the configured mValidator. Before the fix, this caused it to be hidden.",
      "The specific Settings screens that use ServiceListing are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 8516dfb89f99 in android-security-14.0.0_r16 (cherry-pick of 234c5e843ca4); 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 3b2be226f1b9 in android-security-15.0.0_r4, same diff; 8/8 lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin); the patched code is present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "not affected (not listed by the bulletin); the patched code is present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "Single self-contained commit that touches only ServiceListing.reload() and its Robolectric test. The same diff was used on 14 and 15. For other branches, check that reload() still has the single-loop permission/validator structure before applying (ServiceInfo.getComponentName() and Intent.setComponent are long-standing APIs (general platform knowledge)).",
      "The new test uses Mockito argThat with Intent.filterEquals to tell the action query apart from the component query. Older branches need a ServiceListingTest with mPm mocked the same way for the test to carry over."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Settings screens or ServiceListing builders (setting, intent action, permission, validator) were affected in practice is not shown in the material.",
      "The material does not show which 'more lax' approval criteria allowed the hidden approvals to be granted.",
      "Whether a separate fix was applied for Android 12/12L/13 is not shown. The bulletin lists them as affected, but no commit is provided.",
      "Whether the reload() code on 12/12L/13 matches the pre-fix structure seen here is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49734": {
     "cve": "CVE-2024-49734",
     "summary": "ConnectivityService now installs ingress discard rules (via mBpfNetMaps.setIngressDiscardRule) so that packets to an address used only by a VPN interface are dropped when they arrive on a non-VPN interface. Two frameworks/libs/net changes accompany it and keep IPv4-mapped IPv6 addresses as Inet6Address.",
     "component": "platform/packages/modules/Connectivity (ConnectivityService, BpfNetMaps ingress discard rules) and platform/frameworks/libs/net (Struct, InetAddressUtils)",
     "weakness": "Missing ingress filtering: nothing dropped packets addressed to a VPN-only address when they came in through a non-VPN interface (bulletin type ID).",
     "root_cause": "Before the fix, ConnectivityService had no way to reject traffic to a VPN interface's addresses when it arrived on other interfaces. updateLinkProperties called only updateVpnFiltering, and the destroy path had no related cleanup. So a packet sent to the VPN-assigned address could be accepted through a non-VPN interface. Separately, Struct.java parsed Type.Ipv6Address fields with InetAddress.getByAddress, which turns a v4-mapped v6 byte array into an Inet4Address.",
     "fix_analysis": [
      "frameworks/libs/net b20d67cbfc24, common/framework/com/android/net/module/util/InetAddressUtils.java: adds InetAddressUtils.v4MappedV6Address(Inet4Address). It builds a 16-byte ::ffff:a.b.c.d array and returns it through Inet6Address.getByAddress(null, bytes, -1), so the result is not collapsed back to Inet4Address. On the (unreachable) UnknownHostException it logs wtf and returns null.",
      "frameworks/libs/net e72c61380c52, common/device/com/android/net/module/util/Struct.java (field read switch, case Ipv4Address/Ipv6Address): when isIpv6 is true, it uses Inet6Address.getByAddress(null, address, -1) instead of InetAddress.getByAddress, so v4-mapped v6 addresses parse as Inet6Address. IPv4 fields are unchanged.",
      "packages/modules/Connectivity 1a0dd8ccde71, service/src/com/android/server/ConnectivityService.java: adds the field mIngressToVpnAddressFiltering, set in the constructor to mDeps.isAtLeastT(). The commit message says isTetheringFeatureNotChickenedOut is not available on U, so the feature is enabled on T+ with no kill switch.",
      "ConnectivityService.generateIngressDiscardRules(nai, lp): first counts how many non-destroyed NetworkAgentInfos own each address from LinkProperties.getAllAddresses(). The set is mNetworkAgentInfos plus the passed nai, and the passed lp is used for nai. It then goes through every non-destroyed VPN agent that has a non-null interface name. For each address with an owner count of exactly 1 that is not link-local, it emits Pair<addr, ifname>.",
      "ConnectivityService.updateIngressToVpnAddressFiltering(newLp, oldLp, nai): returns early if the flag is off or !mDeps.isAtLeastT(). Otherwise it diffs the rules generated from oldLp and newLp with CompareOrUpdateResult, keyed on the address. It calls mBpfNetMaps.removeIngressDiscardRule(addr) for removed rules and mBpfNetMaps.setIngressDiscardRule(addr, ifname) for added and updated rules.",
      "Call sites: updateLinkProperties calls updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent) right after updateVpnFiltering. The network destroy path calls updateIngressToVpnAddressFiltering(null, nai.linkProperties, nai) just before mNetd.networkDestroy.",
      "Follow-up c16addaec604 (android-security-14.0.0_r16), ConnectivityService.generateIngressDiscardRules: also skips agents where getVpnType(agent) == VpnManager.TYPE_VPN_LEGACY, because some legacy VPNs need to receive packets to the VPN address through a non-VPN interface."
     ],
     "completeness": [
      "Covers all addresses from LinkProperties.getAllAddresses() of non-destroyed, non-legacy VPN agents. Rules are re-evaluated across all networks on each updateLinkProperties call and on network destroy.",
      "Adds no rules for addresses configured on more than one interface (owner count != 1), for link-local addresses, or, after c16addaec604, for legacy VPNs (VpnManager.TYPE_VPN_LEGACY).",
      "Rules are keyed by address only (the CompareOrUpdateResult key is rule.first). When the owning interface changes, setIngressDiscardRule overwrites the rule.",
      "Enabled only when mDeps.isAtLeastT(); this branch has no kill switch. The material does not include the BpfNetMaps.setIngressDiscardRule/removeIngressDiscardRule implementation or the enforcement path.",
      "The Struct.java change affects only Type.Ipv6Address fields. The new InetAddressUtils.v4MappedV6Address helper is added, but the material does not show its caller."
     ],
     "behavior_changes": [
      "Packets sent to an address unique to a non-legacy VPN interface are covered by an ingress discard rule bound to that interface, which is meant to drop them when they come in on other interfaces.",
      "Ingress discard rules are added, updated and removed as VPN LinkProperties change, and are processed for removal when the network is destroyed.",
      "Struct parsing of Type.Ipv6Address fields now always yields Inet6Address, including for v4-mapped v6 byte patterns that used to become Inet4Address.",
      "The follow-up commit excludes legacy VPNs (TYPE_VPN_LEGACY) from the new filtering."
     ],
     "preconditions": [
      "A VPN network (NetworkAgentInfo.isVPN()) is up, with a LinkProperties interface name and at least one non-link-local address that no other interface uses.",
      "Traffic addressed to that VPN address arrives on a non-VPN interface.",
      "The device runs T or later (mDeps.isAtLeastT()), which is always true on Android 14."
     ],
     "per_major": {
      "14": "Adapted fix. Cherry-picks of aosp/2795709, aosp/2807798 and aosp/2795711 went into frameworks/libs/net and packages/modules/Connectivity in android-security-14.0.0_r16 for non-mainline U devices. Tests were dropped, and the isTetheringFeatureNotChickenedOut kill switch was replaced by an unconditional isAtLeastT() enable. Follow-up c16addaec604 in the same tag skips legacy VPNs. At android-security-14.0.0_r29, 6/8 lines of the patched region are present (fix recorded as c16addaec604).",
      "15": "Upstream fix: the change \"Drop packets to VPN address ingressing via non-VPN interface\" was merged into main and is in android-15.0.0_r1 (merge c4c1c3721c0a). The state of the patched region at android-security-15.0.0_r17 is unknown from the material.",
      "16": "Not affected per the bulletin; the project is not present in this major's security tags.",
      "17": "Not affected per the bulletin; the project is not present in this major's security tags."
     },
     "backport_notes": [
      "Take all three commits (two in frameworks/libs/net, one in packages/modules/Connectivity) plus follow-up c16addaec604 so legacy VPNs keep working. The ConnectivityService change relies on BpfNetMaps.setIngressDiscardRule/removeIngressDiscardRule, getVpnType, CompareOrUpdateResult and mDeps.isAtLeastT() existing in the target tree.",
      "The libs/net commits carry Merged-In to block automerge to udc-mainline-prod, where frameworks/libs/net does not exist."
     ],
     "confidence": "medium",
     "unknowns": [
      "How BpfNetMaps.setIngressDiscardRule/removeIngressDiscardRule and the ingress enforcement are implemented, including whether they use the new v4MappedV6Address helper or the Struct Ipv6Address handling.",
      "Whether the ConnectivityService change depends on the two libs/net commits, and in what order the commits must be applied.",
      "What information is disclosed and under which network topology; the bulletin gives only the ID type and the High rating.",
      "Which 2 of the 8 patched-region lines differ at android-security-14.0.0_r29.",
      "Whether the patched region on Android 15 (android-security-15.0.0_r17) matches the Android 14 form, including the legacy VPN exclusion."
     ],
     "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-2024-49736": {
     "cve": "CVE-2024-49736",
     "summary": "Settings' factory reset screen (MainClear) now checks DynamicSystemManager.isInUse() before keyguard confirmation. If a Dynamic System Update image is in use, it shows an informational AlertDialog (R.string.dsu_is_running) and returns instead of continuing with the reset flow.",
     "component": "platform/packages/apps/Settings, factory reset UI (MainClear) / Dynamic System Updates",
     "weakness": "Missing state check (factory reset allowed while a DSU image is in use), rated DoS by the bulletin",
     "root_cause": "In src/com/android/settings/MainClear.java, the reset handler went from its existing early-return check straight to runKeyguardConfirmation(KEYGUARD_REQUEST) and the factory reset flow. It never checked whether the device was booted into a Dynamic System Update (DSU) image. The material does not show the concrete failure mode. The bulletin classifies the result as a High severity DoS, and the fix title (\"Disable factory reset in DSU mode\") indicates that resetting while DynamicSystemManager.isInUse() is true is the problematic condition.",
     "fix_analysis": [
      "src/com/android/settings/MainClear.java, in the reset handler code just before runKeyguardConfirmation(KEYGUARD_REQUEST): after the pre-existing early return, the fix gets DynamicSystemManager via getActivity().getSystemService(Context.DYNAMIC_SYSTEM_SERVICE) and calls isInUse().",
      "If isInUse() is true, it builds an AlertDialog with title R.string.dsu_is_running and a no-op positive button R.string.okay, shows it, and returns. runKeyguardConfirmation and the rest of the reset flow are never reached.",
      "New imports: android.app.AlertDialog, android.content.DialogInterface, android.os.image.DynamicSystemManager. Single-commit fix, 16 added lines, no other files changed."
     ],
     "completeness": [
      "The guard is placed only in MainClear, before keyguard confirmation. No later stage of the Settings reset flow is modified by this diff.",
      "The diff does not touch any factory reset entry points outside Settings. The material does not show whether those are guarded elsewhere.",
      "The dialog is purely informational; no persistent state or setting is changed.",
      "The diff references R.string.dsu_is_running but does not add it. The commit changes only MainClear.java. Where the string is defined is not shown."
     ],
     "behavior_changes": [
      "When a DSU image is in use, pressing the factory reset button in Settings shows an AlertDialog titled R.string.dsu_is_running with an OK button and does nothing else. Before the fix, it proceeded to keyguard confirmation and reset.",
      "No change in behavior when DynamicSystemManager.isInUse() is false.",
      "getSystemService(Context.DYNAMIC_SYSTEM_SERVICE) is now called on every reset button press, and the result is used without a null check."
     ],
     "preconditions": [
      "Device is booted into a Dynamic System Update image (DynamicSystemManager.isInUse() returns true).",
      "A user of the Settings UI reaches the MainClear (factory reset) screen, presses the reset button and gets past the handler's pre-existing early-return check that comes before the new code."
     ],
     "per_major": {
      "14": "same fix: commit 69b3831009d1 in android-security-14.0.0_r16 (cherry-pick of 5f8f7f93f3b5, Change-Id I485eb6ac7beec0893d91ca5fe8ad88ecd96a5cbe); 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "not affected (not listed by the bulletin); the patched region is present (8/8 lines) at android-security-15.0.0_r17.",
      "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": [
      "The change is self-contained in MainClear.java but depends on R.string.dsu_is_running and R.string.okay existing in Settings resources (not added by this diff), and on android.os.image.DynamicSystemManager / Context.DYNAMIC_SYSTEM_SERVICE. Check that the string resources exist before cherry-picking to 12/12L/13.",
      "The insertion point is immediately before runKeyguardConfirmation(KEYGUARD_REQUEST), after the existing early return. On older branches with restructured code, place the check at the equivalent spot."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete DoS outcome of factory resetting while a DSU is in use is not shown in the material.",
      "Whether other factory reset entry points (outside MainClear) have equivalent DSU guards is not shown.",
      "Whether R.string.dsu_is_running was added in a separate commit or already existed in Settings on each affected branch.",
      "The exact name and type of the listener containing the new code are not visible in the diff hunk."
     ],
     "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-2024-49737": {
     "cve": "CVE-2024-49737",
     "summary": "In WindowOrganizerController, the OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT handler now builds a SafeActivityOptions from the operation's options Bundle using the actual caller's pid/uid (caller.mPid, caller.mUid), and ActivityStartController.startActivityInTaskFragment now takes that SafeActivityOptions instead of a raw Bundle.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm: WindowOrganizerController, ActivityStartController)",
     "weakness": "Caller identity confusion: options attributed to the wrong identity after the Binder calling identity is cleared",
     "root_cause": "Per the commit message, WindowOrganizerController clears the calling uid before applying the WindowContainerTransaction, but SafeActivityOptions queries the Binder uid when it is constructed. Before the fix, the OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT case passed the raw options Bundle (operation.getBundle()) to ActivityStartController.startActivityInTaskFragment, so the options were not explicitly tied to the actual caller's uid/pid.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowOrganizerController.java, OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT case: after reading operation.getBundle(), the fix calls SafeActivityOptions.fromBundle(activityOptions, caller.mPid, caller.mUid), which binds the options to the caller's pid/uid (the same caller.mUid/caller.mPid already passed as callingUid/callingPid). It then passes safeOptions to startActivityInTaskFragment instead of the Bundle.",
      "services/core/java/com/android/server/wm/ActivityStartController.java, startActivityInTaskFragment(): parameter type changed from @Nullable Bundle to @Nullable SafeActivityOptions (unused android.os.Bundle import removed, javadoc updated).",
      "Because the parameter type changed, any caller of startActivityInTaskFragment must now pass a SafeActivityOptions."
     ],
     "completeness": [
      "Covers the TaskFragment start-activity operation in WindowOrganizerController. In the diff, that is the only call site of startActivityInTaskFragment that changed.",
      "The bodies of SafeActivityOptions and startActivityInTaskFragment are not in the material, so how the options are later validated against the supplied pid/uid is not visible.",
      "The other cases shown in the same switch (create, delete, reparent) do not pass an options Bundle and are not touched. Whether other WCT paths construct SafeActivityOptions under a cleared identity is not shown."
     ],
     "behavior_changes": [
      "Options supplied with a TaskFragment start-activity operation are now bound to the caller's uid/pid via SafeActivityOptions.fromBundle. The resulting effect on permission-gated options is not shown.",
      "Internal API change: ActivityStartController.startActivityInTaskFragment takes SafeActivityOptions instead of Bundle."
     ],
     "preconditions": [
      "Caller applies a WindowContainerTransaction containing an OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT operation with an options Bundle (operation.getBundle()).",
      "The operation is processed in WindowOrganizerController after the calling uid has been cleared, per the commit message."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin; no Android 13 commit is in the material, so how it was applied there is unknown.",
      "14": "Same fix: commit 5eb2aa58018c in android-security-14.0.0_r16 (cherry-pick of 20c568e77eae); 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Adapted fix: commit 4b2a235c895c in android-security-15.0.0_r4; same change, but the call sits inside waitAsyncStart(() -> ...) in WindowOrganizerController; 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": [
      "Self-contained two-file change. Confirm the target branch has SafeActivityOptions.fromBundle(Bundle, int, int), and update all callers of startActivityInTaskFragment to the new SafeActivityOptions parameter. On 15 the call is inside a waitAsyncStart lambda; on 14 it is a direct call."
     ],
     "confidence": "medium",
     "unknowns": [
      "The bodies of startActivityInTaskFragment and SafeActivityOptions: where the Binder identity was previously captured and which options are permission-gated.",
      "Which specific options were affected, and the exact behavior after the fix.",
      "Whether an Android 13 patch exists and its form."
     ],
     "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-49738": {
     "cve": "CVE-2024-49738",
     "summary": "In libbinder Parcel.cpp, writeInplace(), writeObject() and writeAligned() now call validateReadData() on the end offset of the write before storing into mData+mDataPos, and refuse the write if it fails.",
     "component": "platform/frameworks/native, libbinder (libs/binder/Parcel.cpp)",
     "weakness": "Missing validation before in-place writes into the Parcel buffer (the commit message says this could lead to memory corruption)",
     "root_cause": "The in-place write paths of Parcel (writeInplace, writeObject, writeAligned) checked only that mDataPos plus the write length fits within mDataCapacity (writeObject also checks object capacity). They then wrote directly at mData+mDataPos without calling validateReadData() on the target range. The commit message says this validation is required to prevent memory corruption. What exactly validateReadData() guards against is not shown in the material.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::writeInplace(size_t len): in the restart_write block, after computing data = mData+mDataPos, it calls validateReadData(mDataPos + padded). On failure it returns nullptr and drops the status, as the code comment says. The padding mask and finishWrite(padded) run only after the validation passes.",
      "libs/binder/Parcel.cpp:Parcel::writeObject(const flat_binder_object& val, bool nullMetaData): at restart_write, it calls validateReadData(mDataPos + sizeof(val)) before storing val into mData+mDataPos with reinterpret_cast. On failure it returns the status.",
      "libs/binder/Parcel.cpp:Parcel::writeAligned(T val): at restart_write, it calls validateReadData(mDataPos + sizeof(val)) before memcpy(mData + mDataPos, &val, sizeof(val)). On failure it returns the status.",
      "In all three functions the check sits right after the restart_write label. In writeInplace the growData() success path jumps back to that label (lines 1129-1130), so the check also runs after the buffer grows. The commit accepts the performance cost ('This is slow, but it's required to prevent memory corruption')."
     ],
     "completeness": [
      "The fix covers three write primitives in Parcel.cpp: writeInplace, writeObject and the writeAligned template. The material does not show whether every other write helper goes through these three.",
      "writeInplace drops the validateReadData status and returns nullptr. The caller shown at lines 1076-1081 then returns mError. Whether mError reflects the validation failure cannot be seen in the material.",
      "How much protection the fix gives depends on what validateReadData() checks. Its body is not in the material.",
      "Per index search, system/libhwbinder/Parcel.cpp has a writeAligned with the same structure and no validateReadData call. This fix does not touch it."
     ],
     "behavior_changes": [
      "Writes for which validateReadData() fails are now refused. writeObject and writeAligned return the validation status. writeInplace returns nullptr, and the caller shown then returns mError.",
      "Each of these in-place writes now runs an extra validation, which the commit says is slow."
     ],
     "preconditions": [
      "Reaching the code needs a libbinder Parcel write through writeInplace, writeObject or writeAligned. The parcel state that triggers the bug is not shown, because validateReadData() semantics are not in the material.",
      "The commit lists 'Test: fuzzer' as its test method. The index search found no relevant libbinder callers, only Java serialization and hwbinder matches."
     ],
     "per_major": {
      "14": "Same fix: f3c7aac0e327 in android-security-14.0.0_r16 (cherry-picked from c54dad65317f, Change-Id Ibc5566ade0389221690dc90324f93394cf7fc9a5). All 8 patched lines are present at android-security-14.0.0_r29.",
      "15": "Same fix: 49eb0ea0816e in android-security-15.0.0_r4, with identical hunks at shifted line numbers. All 8 lines are present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin. The patched lines are present at android-security-16.0.0_r8 (8/8).",
      "17": "Not affected per the bulletin. The patched lines are present at android-security-17.0.0_r1 (8/8)."
     },
     "backport_notes": [
      "This is a single-commit fix that adds three checks in Parcel.cpp, each placed right after the restart_write label in writeInplace, writeObject and writeAligned. It needs an existing Parcel::validateReadData(size_t) in the target branch. The 14 and 15 hunks are textually identical."
     ],
     "confidence": "medium",
     "unknowns": [
      "The body and exact semantics of validateReadData() are not in the material.",
      "The concrete trigger condition and the affected callers or processes are not shown. The bulletin gives only EoP / High.",
      "Fix commits for Android 12, 12L and 13, which are listed as affected, are not provided.",
      "Whether other write helpers in Parcel.cpp bypass these three primitives is not visible.",
      "Whether mError is set when writeInplace fails validation is not shown.",
      "The growData() paths of writeObject and writeAligned are not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49742": {
     "cve": "CVE-2024-49742",
     "summary": "NotificationAccessConfirmationActivity in Settings now verifies, before showing the grant dialog, that the requesting package declares a service resolving the NotificationListenerService intent action for the target user and that this service is protected by BIND_NOTIFICATION_LISTENER_SERVICE, finishing the activity otherwise, and it drops the onBackPressed override that prevented dismissal.",
     "component": "platform/packages/apps/Settings, notification listener access confirmation (CDM / NLS flow)",
     "weakness": "Insufficient validation of the target component before granting notification listener access: there was no check that the package declares a NotificationListenerService intent-filter, and the existing permission check ran only on Allow and was not per-user",
     "root_cause": "NotificationAccessConfirmationActivity receives a component name and user id via EXTRA_COMPONENT_NAME / EXTRA_USER_ID (NotificationAccessConfirmationActivityContract) and, on user approval, calls mNm.setNotificationListenerAccessGranted(mComponentName, true). Before the fix the only validation was in onAllow(): PackageManager.getServiceInfo(mComponentName, 0) and a comparison of serviceInfo.permission against BIND_NOTIFICATION_LISTENER_SERVICE. It never checked that the component declared an intent-filter for android.service.notification.NotificationListenerService, the lookup was not done for mUserId, and the check ran only after the dialog had been shown. The activity also overrode onBackPressed() to suppress finishing, so the back button did not dismiss the dialog.",
     "fix_analysis": [
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java:onCreate: after the existing early-exit checks and before building AlertController.AlertParams, the fix builds an Intent for NotificationListenerService.SERVICE_INTERFACE and calls getPackageManager().queryIntentServicesAsUser(NLSIntent, 0, mUserId). It walks the ResolveInfo list for an entry whose serviceInfo.packageName equals mComponentName.getPackageName(). If that entry's serviceInfo.permission is not BIND_NOTIFICATION_LISTENER_SERVICE, it logs, calls finish() and returns. Otherwise it sets hasNLSIntentFilter and breaks. If no entry matched, it logs 'lacks an intent-filter action', calls finish() and returns. Only then is the dialog created.",
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java:onAllow: the previous getServiceInfo(mComponentName, 0) permission check and its NameNotFoundException handling are removed. onAllow now only calls mNm.setNotificationListenerAccessGranted(mComponentName, true) and finish(), relying on the onCreate validation.",
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java: the onBackPressed() override that suppressed finishing on back press is deleted. cancel() still calls finish().",
      "tests/robotests/.../NotificationAccessConfirmationActivityTest.java: start_showsDialog is replaced by start_withMissingIntentFilter_finishes, which asserts that a package with no NLS service yields no dialog text and that activity.isFinishing() is true."
     ],
     "completeness": [
      "Covered: the activity refuses to show the grant dialog when the package has no service resolving the NotificationListenerService action for mUserId, or when the first such service in that package is not protected by BIND_NOTIFICATION_LISTENER_SERVICE. The query is now user-aware (queryIntentServicesAsUser with mUserId); the old check used getServiceInfo without a user.",
      "Limitation visible in the code: the match is by package name only (service.serviceInfo.packageName.equals(mComponentName.getPackageName())) followed by break. It does not compare the matched serviceInfo.name to mComponentName.getClassName(). If a package declares more than one service, the check validates the first NLS-resolving service of that package, while setNotificationListenerAccessGranted is still called with the full mComponentName. The old per-component getServiceInfo(mComponentName) permission check was removed rather than kept alongside the new check.",
      "The grant itself (mNm.setNotificationListenerAccessGranted) is unchanged. Any server-side validation in NotificationManagerService is not part of this commit and is not shown in the material."
     ],
     "behavior_changes": [
      "The activity finishes immediately (no dialog) with a Slog.e when the package has no NotificationListenerService intent-filter for the target user, or when its NLS service lacks BIND_NOTIFICATION_LISTENER_SERVICE. Previously the dialog was shown and the permission mismatch was only logged on Allow.",
      "The back button now dismisses the confirmation activity (onBackPressed override removed).",
      "The service lookup is now done for mUserId instead of the caller's implicit user.",
      "The Robolectric test start_showsDialog is replaced by start_withMissingIntentFilter_finishes."
     ],
     "preconditions": [
      "NotificationAccessConfirmationActivity is started with EXTRA_COMPONENT_NAME and EXTRA_USER_ID (NotificationAccessConfirmationActivityContract). The commit title's [CDM][NLS] tag ties this to the companion device notification listener flow.",
      "The target component's package does not declare a service with an intent-filter for android.service.notification.NotificationListenerService that is protected by BIND_NOTIFICATION_LISTENER_SERVICE (the condition the fix now rejects).",
      "The user taps Allow in the dialog for the grant to happen (onAllow)."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/Settings b0eec6fdfd5b in android-security-14.0.0_r16 (cherry-pick of 7ae59a42eb13, Change-Id Ib79c219c); patched region fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "same fix: platform/packages/apps/Settings 334323abd571 in android-security-15.0.0_r4. The added and removed lines are identical; only hunk offsets and test-file context differ. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin); patched code present (8/8 lines) at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin); patched code present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit in Settings. It uses NotificationListenerService.SERVICE_INTERFACE, PackageManager.queryIntentServicesAsUser and the existing mUserId field. Move the check from onAllow into onCreate, remove the onBackPressed override, and update the Robolectric test (start_showsDialog no longer holds). The material does not show whether 12/12L/13 have the same code structure."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact EoP consequence (what a non-NLS component could obtain once granted listener access) is not shown in the material.",
      "Which callers launch this activity in the affected versions (the commit title says CDM), and whether NotificationManagerService does its own validation in setNotificationListenerAccessGranted, are not shown.",
      "How queryIntentServicesAsUser with flags 0 treats disabled or otherwise filtered components is not shown.",
      "Whether 12/12L/13 received the same patch, and whether their code matches, is not shown in the per-major facts."
     ],
     "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-2024-49744": {
     "cve": "CVE-2024-49744",
     "summary": "Hardens AccountManagerService.checkKeyIntent so that an authenticator-supplied KEY_INTENT is pinned to the activity component the system resolved and validated, and extends the Intent-subclass check in checkKeyIntentParceledCorrectly to the re-parceled copy.",
     "component": "platform/frameworks/base, services/core AccountManagerService (account authenticator KEY_INTENT validation)",
     "weakness": "Improper input validation / incomplete validation of an authenticator-supplied Intent (resolution target not pinned after the security check; subclass check only applied to one of the two compared Intents)",
     "root_cause": "In services/core/java/com/android/server/accounts/AccountManagerService.java, checkKeyIntent resolves the Intent found under AccountManager.KEY_INTENT with pm.resolveActivityAsUser and verifies that the resolved activity is an exported system activity or shares signature capability (CertCapabilities.AUTH) with the authenticator. Before the fix it returned true without modifying the Intent, so the Intent left in the result Bundle was not bound to the activity that had been checked. In addition, checkKeyIntentParceledCorrectly only rejected the case where the original Intent object was a subclass of Intent (intent.getClass() != Intent.class) and did not apply the same check to simulateIntent, the copy obtained by writing and re-reading the Bundle through a Parcel.",
     "fix_analysis": [
      "AccountManagerService.java:checkKeyIntent: after the isExportedSystemActivity / hasSignatureCapability check passes, the fix calls intent.setComponent(targetActivityInfo.getComponentName()) and writes the modified Intent back with bundle.putParcelable(AccountManager.KEY_INTENT, intent), so the Intent in the result Bundle is explicit and targets the validated activity.",
      "AccountManagerService.java:checkKeyIntentParceledCorrectly: the early 'intent != null && intent.getClass() != Intent.class' rejection is removed; after the null handling (intent == null returns simulateIntent == null) a new check returns false if either intent.getClass() != Intent.class or simulateIntent.getClass() != Intent.class, before the existing intent.filterEquals(simulateIntent) comparison."
     ],
     "completeness": [
      "Covers the KEY_INTENT path in checkKeyIntent: the component is pinned only after the target passes the isExportedSystemActivity / hasSignatureCapability check, and the pinned Intent is written back into the result Bundle.",
      "The existing early return `if (intent == null) return true;` and the ClipData / content-scheme handling in checkKeyIntent are unchanged.",
      "In checkKeyIntentParceledCorrectly, the new class check dereferences simulateIntent without a null check when intent is non-null.",
      "Only the KEY_INTENT key is validated by these methods; the material shows no other Bundle keys being covered or any other call site changed."
     ],
     "behavior_changes": [
      "Clients receiving KEY_INTENT from AccountManager now get an explicit Intent whose component is the system-resolved, validated activity, even if the authenticator supplied an implicit Intent.",
      "A result Bundle whose re-parceled KEY_INTENT deserializes to an Intent subclass is now rejected (checkKeyIntentParceledCorrectly returns false), in addition to the original object being a subclass."
     ],
     "preconditions": [
      "An account authenticator returns a result Bundle containing AccountManager.KEY_INTENT to AccountManagerService.",
      "The Intent must resolve (pm.resolveActivityAsUser non-null), must not use the content scheme, and the resolved activity must be an exported system activity or share CertCapabilities.AUTH signature capability with the authenticator for checkKeyIntent to return true.",
      "The exact call sites of checkKeyIntent / checkKeyIntentParceledCorrectly are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: cherry-pick 985bdc676ac5 in android-security-14.0.0_r16 (Change-Id Ied7961c73299681aa5b523cf3f00fd905893116f); at android-security-14.0.0_r29 the setComponent/putParcelable lines are present in checkKeyIntent (7/8 patched lines present).",
      "15": "same fix: commit 5018e2273cb3 in android-security-15.0.0_r4 with an identical diff at shifted line numbers (5067/5088 vs 4978/4999); 7/8 patched lines present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin (not listed); the patched region (7/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); the patched region (7/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-commit, two-hunk change confined to AccountManagerService.java; the Android 14 and 15 diffs are textually identical apart from line offsets, so it should apply to any branch that already has checkKeyIntent with the isExportedSystemActivity/hasSignatureCapability check and the checkKeyIntentParceledCorrectly simulation (the Android 12/12L/13 variants are not shown in the material)."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete scenario by which an unpinned KEY_INTENT led to privilege escalation, including how the client later uses the Intent (the commit message says only 'Explicitly set component after target activity check' and 'Update Intent subclass check').",
      "Which callers invoke checkKeyIntent and checkKeyIntentParceledCorrectly and in what order (the surrounding Session code is not included).",
      "Whether the Android 12/12L/13 branches received the same or an adapted patch (no diff provided for them).",
      "Whether the case of a non-null intent with a null simulateIntent is reachable, and how the old filterEquals path behaved in that case versus the new unguarded simulateIntent.getClass() call."
     ],
     "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-49745": {
     "cve": "CVE-2024-49745",
     "summary": "Parcel::growData in libbinder now rejects a grow request with BAD_VALUE when the current data position is beyond the data size, instead of going ahead with growth that per the commit message used to abort or could request a very large allocation.",
     "component": "platform/frameworks/native, libbinder (libs/binder/Parcel.cpp, Parcel growth/write path)",
     "weakness": "Missing state/bounds validation (mDataPos vs mDataSize not checked before buffer growth)",
     "root_cause": "Parcel::growData(len) is only expected to be called at the end of a Parcel, as the new log message says. Before the fix it checked len and overflow (len > SIZE_MAX - mDataSize, mDataSize + len > SIZE_MAX / 3) and computed newSize = ((mDataSize+len)*3)/2 without looking at mDataPos. Parcel::setDataPosition(pos) only rejects pos > INT32_MAX and does not clamp pos to mDataSize, so a Parcel can end up with mDataPos > mDataSize. Per the commit message, that case \"used to abort\", and allocating that much memory \"may cause OOM or other issues\".",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::growData: after an existing check that returns BAD_VALUE, and before the overflow checks and the newSize computation, the fix adds `if (mDataPos > mDataSize)`. That branch logs via ALOGE (\"growData only expected at the end of a Parcel\", printing pos, len and capacity) and returns BAD_VALUE.",
      "The added comment says mDataPos < mDataSize is also unexpected but is deliberately tolerated because it only wastes some memory. Only the mDataPos > mDataSize case is rejected.",
      "Single-commit fix in one file. setDataPosition, continueWrite and the callers are not changed."
     ],
     "completeness": [
      "Covers only Parcel::growData in libs/binder/Parcel.cpp. Any write path that reaches growData with mDataPos > mDataSize now fails with BAD_VALUE instead of growing the buffer.",
      "Parcel::setDataPosition is unchanged and still accepts any pos <= INT32_MAX regardless of mDataSize, so the inconsistent state can still be created. Only the growth step rejects it.",
      "The mDataPos < mDataSize case is explicitly left unhandled (tolerated as wasted memory).",
      "Per index search, the separate copy of growData in system/libhwbinder/Parcel.cpp has no mDataPos check, and this commit does not touch it."
     ],
     "behavior_changes": [
      "Parcel::growData returns BAD_VALUE (a new error path) when mDataPos > mDataSize. Before, the call went ahead and, per the commit message, could abort.",
      "A new ALOGE log line is emitted in that case.",
      "Writes that need to grow the buffer while the data position is beyond the data size now return an error status instead of growing the buffer."
     ],
     "preconditions": [
      "A Parcel whose mDataPos has been moved past mDataSize (per the code context, setDataPosition only checks pos > INT32_MAX).",
      "A later write into that Parcel that needs more capacity, so that Parcel::growData is called.",
      "Per index search, Parcel::setDataSize and setDataCapacity are reachable from Java via frameworks/base/core/jni/android_os_Parcel.cpp. The exact trigger path for this CVE is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 01a1e3492999 in android-security-14.0.0_r16 (cherry-pick of 608524d46227). All 4 added lines are present at android-security-14.0.0_r29.",
      "15": "same fix: commit df99a3903097 in android-security-15.0.0_r4, an identical hunk at a different line offset (@@ -2942). 4/4 lines are present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). The patched region is present (4/4 lines) at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). Only 2/4 lines of the patched region match at android-security-17.0.0_r1, and the material does not show the current form of the code."
     },
     "backport_notes": [
      "A single self-contained hunk in Parcel::growData with no dependencies shown. It was applied to the 14 and 15 trees in identical form; only the line offset differs."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete trigger path, and which process or privilege boundary gives the EoP rating, are not shown in the material.",
      "Where exactly the pre-fix abort happened, or whether a given case led to an oversized allocation, is not shown. The commit message only mentions both outcomes.",
      "What the Android 17 code looks like, given that only 2/4 lines of the patched region match.",
      "Whether the libhwbinder Parcel can reach a state with mDataPos > mDataSize."
     ],
     "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-2024-49749": {
     "cve": "CVE-2024-49749",
     "summary": "Replaces a broken (effectively dead) overflow check in DGifSlurp with a correct one that rejects non-positive or overflowing ImageDesc.Width * ImageDesc.Height before RasterBits is allocated.",
     "component": "platform/external/giflib, GIF decoder (dgif_lib.c, DGifSlurp)",
     "weakness": "Integer overflow leading to undersized heap allocation (incorrect bounds check)",
     "root_cause": "In dgif_lib.c DGifSlurp, the guard before computing ImageSize was written as `Width < 0 && Height < 0 && Width > (INT_MAX / Height)`. Because it required both dimensions to be negative, it never rejected the real problem case: large positive Width and Height whose product overflows. ImageSize = Width * Height could then wrap, and the wrapped value is passed to reallocarray(NULL, ImageSize, sizeof(GifPixelType)), producing a RasterBits buffer smaller than the image dimensions imply. The old check also did not reject zero dimensions.",
     "fix_analysis": [
      "dgif_lib.c:DGifSlurp: the condition is changed to `Width <= 0 || Height <= 0 || Width > (INT_MAX / Height)`, returning GIF_ERROR. This rejects non-positive dimensions (which also rules out dividing by Height == 0) and rejects any pair whose product would exceed INT_MAX, so the subsequent `ImageSize = Width * Height` cannot overflow.",
      "The existing `ImageSize > (SIZE_MAX / sizeof(GifPixelType))` check and the reallocarray call are unchanged. The new check runs before them and is the one that actually prevents the multiplication from wrapping."
     ],
     "completeness": [
      "Covers the single multiplication in DGifSlurp that sizes sp->RasterBits. The guard is now correct for all int values of Width and Height.",
      "Single-commit fix: no other functions in dgif_lib.c are changed. The material does not show whether other callers that compute Width*Height from ImageDesc (outside DGifSlurp, e.g. consumers using DGifGetLine directly) have equivalent checks.",
      "The code context at android-security-14.0.0_r29 shows the fixed condition in place, matching the diff."
     ],
     "behavior_changes": [
      "GIF images whose image descriptor has Width == 0 or Height == 0 are now rejected by DGifSlurp with GIF_ERROR. Previously they were allowed to proceed with a zero-sized allocation.",
      "Images where Width * Height would exceed INT_MAX are now rejected with GIF_ERROR instead of being allocated with a wrapped size.",
      "Callers such as GIFMovie (frameworks/base/libs/hwui/jni/GIFMovie.cpp) treat a DGifSlurp failure as a failed decode and close the file, per index search."
     ],
     "preconditions": [
      "A GIF stream is decoded with DGifSlurp, and the file author controls the Width and Height in its image descriptor.",
      "DGifSlurp is reached from GIFMovie::GIFMovie in frameworks/base/libs/hwui/jni/GIFMovie.cpp, per index search. Other callers may exist."
     ],
     "per_major": {
      "14": "same fix: commit 3281e14f6b9b in android-security-14.0.0_r16 (cherry-pick, Change-Id Ieef04e789acf783eda2dff2cd9284ed204f1d117). The fixed condition is present at android-security-14.0.0_r29 (4/6 lines of the patched region match).",
      "15": "same fix: commit b3aea8d187b1 in android-security-15.0.0_r4, identical diff. At android-security-15.0.0_r17, 4/6 lines of the patched region are present.",
      "16": "not affected (not listed by the bulletin). At android-security-16.0.0_r8, 4/6 lines of the patched region are present.",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 4/6 lines of the patched region are present."
     },
     "backport_notes": [
      "Single self-contained change to one if-statement in dgif_lib.c DGifSlurp. The diff is identical on 14 and 15 and should apply to any branch that contains the broken `Width < 0 && Height < 0 && ...` guard."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the downstream write that the undersized RasterBits buffer leads to. The context stops at the start of the interlaced loop.",
      "Whether Android 12/12L/13 received the same commit. The bulletin lists them as affected, but no commit material is provided for them.",
      "Whether other Width*Height computations elsewhere in giflib or its consumers carry an equivalent guard."
     ],
     "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": 19,
    "certain": 18,
    "probable": 1,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2024-43096",
     "osv_id": "ASB-A-323850943",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an mtu == 0 rejection at the top of build_read_multi_rsp in system/stack/gatt/gatt_sr.cc and clamps the EATT channel transmit MTU to at least EATT_MIN_MTU_MPS in EattChannel::EattChannelSetTxMTU, so the read-multiple response buffer is not allocated without room for the opcode byte.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The gatt_sr.cc hunk applies as long as build_read_multi_rsp still takes (tGATT_SR_CMD* p_cmd, uint16_t mtu). The eatt.h hunk needs EATT_MIN_MTU_MPS to be defined in the target tree. The Android 14 and 15 diffs have the same content apart from line numbers.",
       "Bluetooth is a mainline module, so on devices that receive module updates the fix may arrive through the module rather than the platform tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "5318ec44af657f639218a1d475554d7ccd2595c6",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43770",
     "osv_id": "ASB-A-364026473",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return to four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size() returns 0 (channel already closed). A response buffer sized from a zero payload is therefore no longer allocated and written past its end.",
      "confidence": "high",
      "backport_notes": [
       "In each of the four handlers, insert the guard if (payload_size == 0) return; directly after the payload size call. On branches where the callee is gatt_tcb_get_payload_size_tx (as in Android 14), keep that name; the guard itself is the same."
      ],
      "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_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "339ea2fc19059436898f877d1aa2608af8dda376",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43771",
     "osv_id": "ASB-A-364027949",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return when gatt_tcb_get_payload_size() yields 0 (closed channel) in four GATT server request handlers in system/stack/gatt/gatt_sr.cc, so response buffers are no longer allocated with zero payload capacity and then written into.",
      "confidence": "high",
      "backport_notes": [
       "Single commit that only inserts code. The only branch difference is the helper name (gatt_tcb_get_payload_size on 15, gatt_tcb_get_payload_size_tx on 14). Use whichever name the target branch has and insert the check right after that call in each of the four handlers.",
       "Bluetooth is a mainline module (com.android.btservices) (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "339ea2fc19059436898f877d1aa2608af8dda376",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-49747",
     "osv_id": "ASB-A-364027038",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size() returns 0 (channel already closed). As a result, a response buffer is no longer allocated with zero payload capacity and then written past its end.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. On Android 14 the payload size helper is named gatt_tcb_get_payload_size_tx rather than gatt_tcb_get_payload_size, so match on the call site in each of the four functions rather than on the helper name. Older branches were 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."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "339ea2fc19059436898f877d1aa2608af8dda376",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-49748",
     "osv_id": "ASB-A-364025411",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size() returns 0 (channel already closed), so a response buffer is no longer allocated with zero payload capacity and then written into.",
      "confidence": "high",
      "backport_notes": [
       "Pure insertion of four identical guards; the only adaptation across branches is the helper name (gatt_tcb_get_payload_size on 15, gatt_tcb_get_payload_size_tx on 14). Verify the local name of the payload-size helper and place each guard directly after its assignment and before any buffer length computation."
      ],
      "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_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "339ea2fc19059436898f877d1aa2608af8dda376",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43095",
     "osv_id": "ASB-A-340480881",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PermissionController now only treats a platform permission group as granted (and therefore eligible for auto-granting its remaining permissions) when a granted permission in the group is defined by the platform or a system app, or when all permissions in the group were auto-granted (granted and REVOKE_WHEN_REQUESTED).",
      "confidence": "high",
      "backport_notes": [
       "The Android 14 variant shows how to adapt when grantPermissions/GrantBehavior.kt is not used. Apply the allowFullGroupGrant checks directly where GrantPermissionsViewModel inlines isGrantedExcludingRWROrAllRWR, and map STATE_GRANTED to the branch's state constant (STATE_ALLOWED on 14). The isGrantedIncludingAppOp -> isGranted rename is mechanical. The essential parts are: LightPermInfo.isSystem; LightPermission.isPlatformOrSystem and allowFullGroupGrant; LightAppPermGroup.isPlatformPermissionGroup and the AppPermSubGroup predicates; PermGroupLiveData.onUpdate; and the shouldDenyFullGroupGrant state logic.",
       "PermGroupLiveData.onUpdate now returns early until packageLiveDatas are initialized, so it relies on onUpdate running again when the LightPackageInfoLiveData sources load (SmartUpdateMediatorLiveData behavior, general platform knowledge). Every LightPermInfo(PermissionInfo) call site in the branch must be updated for the new constructor signature, including tests such as GrantRevokeTests.kt."
      ],
      "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_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Permission",
       "sha": "f677d33568d944dfe43a713dc148bd68a5472931",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Permission"
     ]
    },
    {
     "cve": "CVE-2024-43763",
     "osv_id": "ASB-A-356886209",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes the early-exit condition in the GATT server's Read Multiple (Variable) response builder so that a zero-length attribute value no longer aborts packet construction, and only a genuinely full buffer (total_len == mtu with a non-empty value pending) stops the loop.",
      "confidence": "high",
      "backport_notes": [
       "Single one-line change with no dependencies shown; when backporting, adapt only the logging macro (VLOG(1) on 14, log::verbose on 15) and keep the preceding `total_len > mtu` check and the `len = std::min(...)` computation intact, since the new condition relies on total_len already including the 2-byte length header in variable_len mode."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "124c2ac6cffb2b7c7e6c66b6def767cd74b62361",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2024-43765",
     "osv_id": "ASB-A-233605527",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "DocumentsUI now requests android.permission.HIDE_OVERLAY_WINDOWS. On S and later it also calls Window.setHideOverlayWindows(true) on the BaseActivity window and on the ConfirmFragment dialog window, so overlay windows from other apps are hidden over its UI.",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching three files. On older branches BaseActivity may lack the com.android.modules.utils.build.SdkLevel import (Android 14 needed it added). Make sure the import and the modules-utils-build dependency are available in the DocumentsUI build (general platform knowledge). Window.setHideOverlayWindows and HIDE_OVERLAY_WINDOWS exist only from API 31, so keep the SdkLevel.isAtLeastS() guard for a minSdk 29 build (general platform knowledge)."
      ],
      "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_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r3",
      "date": "2024-12-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/DocumentsUI",
       "sha": "c8e21a60d78414aca4115403fe63fc732a9cd5aa",
       "tag": "android-security-15.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 30,
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2024-49724",
     "osv_id": "ASB-A-369351375",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens AccountManagerService's validation of an authenticator-supplied KEY_INTENT by pinning the intent to the component that passed the resolve/signature check and by rejecting Intent subclasses on both the original and the re-parceled copy in checkKeyIntentParceledCorrectly.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit in AccountManagerService.java. Both hunks use only existing locals (targetActivityInfo, bundle, intent, simulateIntent), so the change applies to any branch that already has checkKeyIntent with the resolveActivityAsUser/hasSignatureCapability check and checkKeyIntentParceledCorrectly. Branches lacking the typed bundle.getParcelable(key, Intent.class) overload (added in API 33) would need the untyped getParcelable form (general platform knowledge)."
      ],
      "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": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5018e2273cb3156dee81cf372d221ba49d65d1e7",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 985bdc676ac5",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49732",
     "osv_id": "ASB-A-371975420",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a system-UID caller check to the three hidden permission-sync binder entry points (enablePermissionsSync, disablePermissionsSync, getPermissionSyncRequest) in CompanionDeviceManagerService, so that non-system callers get a SecurityException instead of reaching SystemDataTransferProcessor.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit adding 9 lines to one file. It requires the three permission-sync binder methods in CompanionDeviceManagerService, and SYSTEM_UID and getCallingUid must resolve in that class (the material does not show whether they were already in scope before the fix). If a fork lacks these methods (the 0/6 result on 14 may mean this), there is nothing to patch."
      ],
      "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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "076a97aa32492cc44e863f7ab75494dc0b3bf5ef",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49733",
     "osv_id": "ASB-A-365738306",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ServiceListing.reload() in SettingsLib now always includes every component already approved in the backing Settings.Secure setting (queried by explicit ComponentName, without permission/validator checks), so previously approved services that no longer meet the current listing criteria are still shown to the user.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the Android 14 and 15 diffs are byte-identical (same pre/post blob ids c8bcabff1094..261c722e517c), so the hunk should apply cleanly to any branch whose ServiceListing.reload() still has the original permission/validator loop. The bulletin also lists 12, 12L and 13 as affected; no commit for those branches is in the material.",
       "The commit also updates existing ServiceListingTest cases to set ServiceInfo.name on their test ServiceInfo objects; carry these test changes together with the new test."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3b2be226f1b944faad1fb7bf2ecda55fa4ac8c50",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49734",
     "osv_id": "ASB-A-193031925",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ConnectivityService installs ingress discard rules so that packets addressed to a VPN interface's address but arriving on a non-VPN interface are dropped, with a follow-up that excludes legacy VPNs from the rule.",
      "confidence": "medium",
      "backport_notes": [
       "Two-step fix: first the base change that adds ingress discard rule computation to ConnectivityService.java (c4c1c3721c0a / change 2795711), then the legacy-VPN exclusion (c16addaec604), which relies on getVpnType(agent) and VpnManager.TYPE_VPN_LEGACY. Connectivity is a mainline module, so the fix may also arrive through a module update rather than the platform tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 10,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "subject"
     ],
     "confidence": "probable",
     "first_fix": {
      "tag": "android-15.0.0_r1",
      "date": "2024-09-03",
      "provenance": "base_release"
     },
     "last_fix": {
      "tag": "android-15.0.0_r1",
      "date": "2024-09-03",
      "provenance": "base_release"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "c4c1c3721c0a7c0b8bcd8ffcad7024166398520e",
       "tag": "android-15.0.0_r1",
       "provenance": "base_release",
       "method": "subject",
       "confidence": "probable",
       "evidence": "probable: same subject as reference 1a0dd8ccde71, common file service/src/com/android/server/ConnectivityService.java, score 0.98",
       "similarity": 0.98
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 120,
     "projects": [
      "platform/packages/modules/Connectivity"
     ]
    },
    {
     "cve": "CVE-2024-49735",
     "osv_id": "ASB-A-345881518",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Caps the size of vibration data stored on a NotificationChannel by truncating vibration patterns to 500 entries, cropping non-repeating composed VibrationEffects (new VibrationEffect.cropToLengthOrNull) or dropping effects whose serialized form exceeds 32,768 characters, and no longer serializing a VibrationEffect to XML when an equivalent pattern already exists.",
      "confidence": "high",
      "backport_notes": [
       "Only the frameworks/base commit is required; the two build/release flag commits cancel each other and the security backport already inlines android.app.notif_channel_crop_vibration_effects. The change relies on Flags.notificationChannelVibrationEffectApi and VibrationEffect.Composed existing in the target branch.",
       "cropToLengthOrNull is declared abstract on VibrationEffect; any branch with additional concrete VibrationEffect subclasses beyond Composed must add an implementation (returning null is the documented 'not applicable' result) or the build will fail."
      ],
      "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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r5",
      "date": "2025-02-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a44beb0ca3f788c468748e637a2a947b4b44503e",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/build/release",
       "sha": "2283b66e8a4d2595e77289276d611a025ca0ff7d",
       "tag": "android-security-15.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/build/release",
       "sha": "3eae801ea3d0048e2bfc95c4d0a1ed5550b14109",
       "tag": "android-security-15.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/build/release",
       "sha": "f1e488e1285cf575e0e839d1b3a2690ffe3317fa",
       "tag": "android-security-15.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "multi_tag_fix": true,
     "projects": [
      "platform/build/release",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49737",
     "osv_id": "ASB-A-369103643",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowOrganizerController now builds a SafeActivityOptions from the TaskFragment organizer's actual pid/uid (caller.mPid, caller.mUid) and passes it to ActivityStartController.startActivityInTaskFragment, instead of passing the raw Bundle, which would otherwise yield a SafeActivityOptions built from the cleared Binder identity.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit. It relies on SafeActivityOptions.fromBundle(Bundle, int pid, int uid) and on the code downstream of startActivityInTaskFragment accepting a SafeActivityOptions; the diff adds neither, so both must already exist in the target branch. On 14 the call is direct rather than through waitAsyncStart.",
       "Android 13 is listed as affected by the bulletin, but the material gives no 13 commit or tag status."
      ],
      "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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "4b2a235c895c0ea593d21d0b057a502879ecd449",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49738",
     "osv_id": "ASB-A-370840874",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "libbinder Parcel now calls validateReadData() on the target range before writing in Parcel::writeInplace, Parcel::writeObject and Parcel::writeAligned, and refuses the write when validation fails.",
      "confidence": "medium",
      "backport_notes": [
       "The fix is a single self-contained commit. It needs Parcel::validateReadData to exist in the target Parcel.cpp, and each check must sit after the restart_write label so the growData retry path is also validated. The bulletin lists 12, 12L and 13 as affected, but the material has no diff for those branches. Confirm validateReadData exists there before applying."
      ],
      "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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "49eb0ea0816e1fc45101f8b2847d731fc192576f",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2024-49742",
     "osv_id": "ASB-A-363248394",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "NotificationAccessConfirmationActivity now checks, before it shows the grant dialog, that the requesting package has a service that resolves for the NotificationListenerService intent action and that this service is protected by BIND_NOTIFICATION_LISTENER_SERVICE. If either check fails, the activity finishes. The fix also removes the late permission check in onAllow and the onBackPressed suppression.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single self-contained Settings commit, and the Android 14 and 15 diffs are identical apart from line offsets. Backports need mUserId in the activity, and the existing ServiceInfo-based check in onAllow must be removed. queryIntentServicesAsUser(Intent, int, int) is a hidden PackageManager API that Settings can use (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "334323abd5714ef7b52db06bfc60ace5d857323f",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2024-49744",
     "osv_id": "ASB-A-360846772",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountManagerService.checkKeyIntent now sets the authenticator-supplied KEY_INTENT's component to the activity that passed its resolution and signature check, and writes the intent back into the result Bundle. checkKeyIntentParceledCorrectly now rejects the intent if either the original or the parcel-round-tripped copy is not exactly Intent.class.",
      "confidence": "high",
      "backport_notes": [
       "Single-file change in AccountManagerService.java, with identical hunks on 14 and 15. The material has no commit for 12/12L/13, although the bulletin lists them as affected. The typed Bundle.getParcelable(String, Class) overload used in the context is an API 33 (Android 13) addition, so 12/12L code would use the untyped getParcelable form (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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5018e2273cb3156dee81cf372d221ba49d65d1e7",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-49745",
     "osv_id": "ASB-A-370831157",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Parcel::growData in libbinder now logs an error and returns BAD_VALUE when the data position is beyond the data size, a case the commit says used to abort and where a large allocation could cause OOM.",
      "confidence": "high",
      "backport_notes": [
       "A single self-contained hunk in Parcel::growData with no new dependencies. Place the check after the preceding BAD_VALUE-returning check and before the SIZE_MAX overflow checks, as in the 14 and 15 commits."
      ],
      "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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/native",
       "sha": "df99a3903097c70f58ffca2a6e8d815ffeeee2c3",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/native"
     ]
    },
    {
     "cve": "CVE-2024-49749",
     "osv_id": "ASB-A-355461643",
     "source": "osv",
     "announced": "2025-01-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces an effectively dead overflow guard in DGifSlurp with a working one. Before ImageSize is computed and RasterBits is allocated, the new guard rejects non-positive Width/Height and any Width*Height product above INT_MAX.",
      "confidence": "medium",
      "backport_notes": [
       "Single-hunk, self-contained change in dgif_lib.c DGifSlurp. The same diff was applied to Android 14 and 15, so it is expected to apply to branches that carry the `Width < 0 && Height < 0 &&` form of the check."
      ],
      "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_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r4",
      "date": "2025-01-06",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/giflib",
       "sha": "b3aea8d187b147357325ea0f1818593170971068",
       "tag": "android-security-15.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/giflib"
     ]
    }
   ],
   "tags": [
    "android-security-15.0.0_r4"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "352424251"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "ad57b42bc69aa2194fe4aa99b5d6b430f810c34f",
       "subject": "Skip adding ingress discard rule to OEM VPN",
       "tag": "android-security-15.0.0_r4",
       "merged": "2024-11-06",
       "markers": []
      },
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "feaac56e41808fa137c21e4abea2e10721f0cbf9",
       "subject": "Skip adding ingress discard rule to legacy VPN",
       "tag": "android-security-15.0.0_r4",
       "merged": "2024-11-06",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r4"
     ],
     "tag": "android-security-15.0.0_r4"
    }
   ],
   "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-2024-43096": {
     "cve": "CVE-2024-43096",
     "summary": "Adds an mtu == 0 rejection at the top of build_read_multi_rsp in system/stack/gatt/gatt_sr.cc and clamps the EATT channel transmit MTU to at least EATT_MIN_MTU_MPS in EattChannel::EattChannelSetTxMTU, so the read-multiple response buffer is not allocated without room for the opcode byte.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt) and EATT (system/stack/eatt)",
     "weakness": "missing bounds check (heap out-of-bounds write)",
     "root_cause": "build_read_multi_rsp allocates a response buffer of sizeof(BT_HDR) + L2CAP_MIN_OFFSET + mtu bytes with osi_calloc and sets p_buf->offset = L2CAP_MIN_OFFSET. It then always writes the response opcode byte (GATT_RSP_READ_MULTI or GATT_RSP_READ_MULTI_VAR) at (uint8_t*)(p_buf + 1) + offset. When mtu is 0, that position is exactly the end of the allocation, so the opcode write goes past the end of the heap buffer. Nothing checked mtu before the allocation. On the EATT side, EattChannelSetTxMTU only capped tx_mtu_ at EATT_MAX_TX_MTU and had no lower bound, so a zero transmit MTU could be stored for a channel.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:build_read_multi_rsp: before computing len and calling osi_calloc, the fix checks if (mtu == 0). If so, it logs 'Invalid MTU', sets p_cmd->status = GATT_ILLEGAL_PARAMETER and returns without allocating or writing anything. The added comment says at least one extra byte is needed for the opcode.",
      "system/stack/eatt/eatt.h:EattChannel::EattChannelSetTxMTU: after the existing std::min against EATT_MAX_TX_MTU, the fix adds this->tx_mtu_ = std::max<uint16_t>(this->tx_mtu_, EATT_MIN_MTU_MPS), which sets a lower bound on the stored EATT transmit MTU. Line 71 of eatt.h in the r17 context also calls EattChannelSetTxMTU(tx_mtu) inside class EattChannel, apparently from the constructor. If so, the clamp applies both when the channel is created and on reconfiguration.",
      "Both changes are in a single commit, and there is no ordering dependency between them."
     ],
     "completeness": [
      "The gatt_sr.cc check only rejects mtu == 0. For any mtu of 1 or more, the opcode byte fits, and the rest of the function relies on the existing is_overflow logic for the handle data. The material shows that logic only as far as its declaration.",
      "The EATT clamp covers every write to tx_mtu_ that goes through EattChannelSetTxMTU. That includes the line 71 call in the class and eatt_l2cap_reconfig_completed in system/stack/eatt/eatt_impl.h (per index search). Any direct writes to tx_mtu_ that bypass the setter are not visible in the material.",
      "The fix sets p_cmd->status to GATT_ILLEGAL_PARAMETER, but the material does not show how the caller of build_read_multi_rsp reacts to that status."
     ],
     "behavior_changes": [
      "Building a read-multiple response with mtu 0 now fails: p_cmd->status is set to GATT_ILLEGAL_PARAMETER and an ERROR is logged, and no response is built.",
      "An EATT channel's tx_mtu_ can no longer be below EATT_MIN_MTU_MPS. A smaller configured value is silently raised to that minimum."
     ],
     "preconditions": [
      "A GATT server read-multiple (or read-multiple-variable) request handled by build_read_multi_rsp while its mtu argument is 0 (per the commit message).",
      "Per index search, the EATT transmit MTU is set from the peer side of L2CAP reconfiguration: when !is_local_cfg, eatt_l2cap_reconfig_completed in system/stack/eatt/eatt_impl.h calls channel->EattChannelSetTxMTU(p_cfg->mtu). This is the path the eatt.h clamp addresses.",
      "The bulletin rates this Critical RCE."
     ],
     "per_major": {
      "14": "same fix: commit bfe316cf9f02 in android-security-14.0.0_r15, with the same two-file diff (only line offsets differ); 6/6 patched lines present at android-security-14.0.0_r29",
      "15": "same fix: commit 5318ec44af65 in android-security-15.0.0_r3; 6/6 patched lines present at android-security-15.0.0_r17",
      "16": "not affected (not listed by the bulletin); the patched region is present (6/6 lines) at android-security-16.0.0_r8",
      "17": "not affected (not listed by the bulletin); the patched region is present (6/6 lines) at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "Single self-contained commit. The gatt_sr.cc hunk applies as long as build_read_multi_rsp still takes (tGATT_SR_CMD* p_cmd, uint16_t mtu). The eatt.h hunk needs EATT_MIN_MTU_MPS to be defined in the target tree. The Android 14 and 15 diffs have the same content apart from line numbers.",
      "Bluetooth is a mainline module, so on devices that receive module updates the fix may arrive through the module rather than the platform tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "How the caller of build_read_multi_rsp handles the new GATT_ILLEGAL_PARAMETER status (not shown).",
      "Whether the non-EATT (ATT fixed channel) path can pass mtu == 0 to build_read_multi_rsp; the material only shows the EATT setter being hardened.",
      "Which transport and connection state are needed to reach the code.",
      "The value of EATT_MIN_MTU_MPS and the rest of the is_overflow handling in build_read_multi_rsp; neither is in the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43770": {
     "cve": "CVE-2024-43770",
     "summary": "Adds an early return to four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size() returns 0 (channel already closed). A response buffer sized from a zero payload is therefore no longer allocated and written past its end.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Missing validation of buffer size (zero-length payload) leading to out-of-bounds write",
     "root_cause": "In gatt_sr.cc, four handlers (gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req) call gatt_tcb_get_payload_size(tcb, cid). They use the result directly to size the response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET). According to the commit message, in exceptional cases the channel can already be closed when the function is called, so it returns 0. The handler then allocates a buffer with no payload room, and the code that builds the response writes outside it (OOB write).",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:gatts_process_primary_service_req: right after uint16_t payload_size = gatt_tcb_get_payload_size(tcb, cid), the handler now returns if payload_size == 0. This happens before msg_len is computed and before osi_calloc of p_msg.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_find_info: the same check is inserted after the payload_size call, before buf_len is computed and the buffer is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_by_type_req: the same check is inserted before msg_len is computed and p_msg is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_req: the same check is inserted before buf_len is computed.",
      "It is a single commit of 21 added lines that touches no other files. The commit message says it fixes all such uses in gatt_sr.cc."
     ],
     "completeness": [
      "The fix covers the four handlers in gatt_sr.cc that size a response buffer from gatt_tcb_get_payload_size: primary service request, find info, read by type and read request.",
      "gatt_build_primary_service_rsp (shown in the context) also calls gatt_tcb_get_payload_size and compares p_msg->len + p_msg->offset against the result. The fix does not change it. Its call sites are not shown in the material, so whether it is only reached through the now-guarded gatts_process_primary_service_req is not established.",
      "The material does not show whether other handlers in gatt_sr.cc, or other parts of the GATT stack, use gatt_tcb_get_payload_size without a zero check. The commit message says all instances in gatt_sr.cc are fixed.",
      "The fix does not change gatt_tcb_get_payload_size itself. It only adds a guard in the callers."
     ],
     "behavior_changes": [
      "When the payload size for a cid resolves to 0, the four request handlers now return silently. They allocate no response and send no GATT error response to the peer.",
      "There are no new error codes, logs or flags. The commit marks the change 'Flag: EXEMPT trivial validity checks'."
     ],
     "preconditions": [
      "A remote GATT client connected to the device's GATT server sends a primary service discovery, Find Information, Read By Type or Read request.",
      "The request is processed while the corresponding channel is already closed, so gatt_tcb_get_payload_size(tcb, cid) returns 0. The commit message calls this an exceptional case.",
      "The material does not mention any need for user interaction or local privilege. The bulletin rates the issue Critical, type RCE."
     ],
     "per_major": {
      "14": "Adapted fix: commit c7468e64bb5e in android-security-14.0.0_r15 adds the same four zero checks, but the callee is named gatt_tcb_get_payload_size_tx instead of gatt_tcb_get_payload_size. All 8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 339ea2fc1905 in android-security-15.0.0_r3 (cherry-pick of 7de5617f7d52). All 8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Not affected according to the bulletin (Android 16 is not listed). All 8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Not affected according to the bulletin (Android 17 is not listed). All 8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "In each of the four handlers, insert the guard if (payload_size == 0) return; directly after the payload size call. On branches where the callee is gatt_tcb_get_payload_size_tx (as in Android 14), keep that name; the guard itself is the same."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the exact sequence that closes the channel while a request is still being dispatched. The commit message only calls it an exceptional case.",
      "The bulletin lists Android 12, 12L and 13 as affected, but their fix commits are not in the material.",
      "The material does not show whether GATT code outside the four patched handlers, including gatt_build_primary_service_rsp and its callers, uses gatt_tcb_get_payload_size without a zero check."
     ],
     "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-2024-43771": {
     "cve": "CVE-2024-43771",
     "summary": "Adds an early return when gatt_tcb_get_payload_size() yields 0 (closed channel) in four GATT server request handlers in system/stack/gatt/gatt_sr.cc, so response buffers are no longer allocated with zero payload capacity and then written into.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Missing length validation leading to heap out-of-bounds write (buffer sized from an unchecked zero payload size)",
     "root_cause": "In gatt_sr.cc, gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req call gatt_tcb_get_payload_size(tcb, cid) and use the result directly to size the response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET). Per the commit message, if the channel is already closed when the function is called, the result is 0. The response-building code then writes into the buffer assuming some payload capacity. For example, gatt_build_primary_service_rsp (shown in context) writes the opcode byte and, for read-by-group-type, the length byte before it compares p_msg->len + p_msg->offset against payload_size. With a zero payload size these writes go past the allocated buffer, which is the OOB write the commit describes.",
     "fix_analysis": [
      "gatt_sr.cc:gatts_process_primary_service_req: after uint16_t payload_size = gatt_tcb_get_payload_size(tcb, cid), adds 'if (payload_size == 0) return;' before msg_len is computed and p_msg is allocated with osi_calloc.",
      "gatt_sr.cc:gatts_process_find_info: same zero check inserted after obtaining payload_size and before buf_len is computed.",
      "gatt_sr.cc:gatts_process_read_by_type_req: same zero check inserted before msg_len is computed and the buffer allocated.",
      "gatt_sr.cc:gatts_process_read_req: same zero check inserted before buf_len is computed.",
      "Net effect: when the payload size is 0 (channel closed), the request is dropped instead of allocating a zero-capacity response buffer and writing into it. No error response is sent on this path."
     ],
     "completeness": [
      "Covers the four handlers named in the diff: primary service requests (the context comment names ReadByTypeValue and ReadByGroupType), find information, read-by-type, and read requests.",
      "gatt_build_primary_service_rsp also calls gatt_tcb_get_payload_size itself (context line 560) and does not check for zero. Its callers are not shown in the material.",
      "The fix only guards against payload_size == 0. It does not change the order in the response builders, where the opcode/length bytes are written before the size comparison. The material does not show whether other users of gatt_tcb_get_payload_size size buffers the same way."
     ],
     "behavior_changes": [
      "GATT server requests of the affected types that arrive while the payload size resolves to 0 (channel already closed) are now dropped without a response instead of being processed."
     ],
     "preconditions": [
      "A GATT client sends a primary service, find information, read-by-type, or read request that is dispatched to the matching gatts_process_* handler in gatt_sr.cc.",
      "The channel (cid) is already closed when the request is processed, so gatt_tcb_get_payload_size(tcb, cid) returns 0. The commit message calls this an exceptional case.",
      "The bulletin rates this Critical RCE. The commit's test lines mention a researcher POC."
     ],
     "per_major": {
      "14": "Adapted fix: commit c7468e64bb5e in android-security-14.0.0_r15 adds the same four zero checks, but the helper is named gatt_tcb_get_payload_size_tx on that branch. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 339ea2fc1905 in android-security-15.0.0_r3 (cherry-pick of 7de5617f7d52). 8/8 patched 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 commit that only inserts code. The only branch difference is the helper name (gatt_tcb_get_payload_size on 15, gatt_tcb_get_payload_size_tx on 14). Use whichever name the target branch has and insert the check right after that call in each of the four handlers.",
      "Bluetooth is a mainline module (com.android.btservices) (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact sequence that leaves the channel closed while a request is still being processed (the commit only says 'exceptional cases').",
      "Whether gatt_build_primary_service_rsp, or other callers of gatt_tcb_get_payload_size / gatt_tcb_get_payload_size_tx outside the four patched functions, can be reached with a zero payload size. The material does not show their callers.",
      "Whether gatts_process_read_req also handles read-blob requests.",
      "How the fix is delivered through mainline module updates.",
      "Fix state for Android 12, 12L and 13, which the bulletin lists as affected but for which the material includes no commit."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49747": {
     "cve": "CVE-2024-49747",
     "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size() returns 0 (channel already closed). As a result, a response buffer is no longer allocated with zero payload capacity and then written past its end.",
     "component": "platform/packages/modules/Bluetooth, system/stack/gatt (GATT server, ATT request processing)",
     "weakness": "Missing length validation leading to heap out-of-bounds write (undersized buffer allocation)",
     "root_cause": "In gatt_sr.cc, four handlers call gatt_tcb_get_payload_size(tcb, cid): gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req. They use the result directly to size the response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET, allocated with osi_calloc) and as the bound for later writes. Per the commit message, in exceptional cases the channel is already closed when the function is called, so it returns 0. The resulting buffer has no payload room, but the response-building code that follows still writes into it, which produces out-of-bounds writes.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:gatts_process_primary_service_req: after uint16_t payload_size = gatt_tcb_get_payload_size(tcb, cid), return immediately if payload_size == 0. This happens before msg_len is computed and p_msg is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_find_info: same check, inserted after payload_size is obtained and before buf_len is computed.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_by_type_req: same check, inserted before msg_len is computed and p_msg is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_req: same check, inserted before buf_len is computed.",
      "All four hunks are identical 'trivial validity checks' (per the commit's Flag line). No other logic, allocation sizes or write paths are changed. On each shown branch the fix is a single commit."
     ],
     "completeness": [
      "Covers the four handlers named in the diff. The commit message says it fixes 'all' of the 'various points in gatt_sr.cc' where the payload size is used unchecked.",
      "The handlers return silently and do not send an error response to the peer. This is consistent with the channel being closed.",
      "The code context at android-security-15.0.0_r17 shows that gatt_build_primary_service_rsp also calls gatt_tcb_get_payload_size(tcb, cid) (line 560) without a zero check. It uses the result as a comparison bound (p_msg->len + p_msg->offset > payload_size) and writes into a p_msg passed in by its caller. That this buffer comes from the now-guarded gatts_process_primary_service_req is likely, but the call relationship is not shown in the material.",
      "Other users of gatt_tcb_get_payload_size outside gatt_sr.cc are not shown in the material and are not addressed by this commit."
     ],
     "behavior_changes": [
      "Four kinds of ATT request are now dropped without any response (no error response is sent) when gatt_tcb_get_payload_size() reports 0 for the cid: primary service / read-by-group-type, find information, read-by-type and read. Previously a response buffer was allocated and processed."
     ],
     "preconditions": [
      "The device acts as a GATT server and processes incoming ATT requests of the types handled by the four patched functions (primary service discovery, find information, read by type, read).",
      "The GATT channel (cid) for the request is already closed when the handler runs, so gatt_tcb_get_payload_size(tcb, cid) returns 0. The commit message calls this an 'exceptional' case.",
      "The bulletin rates this as a Critical RCE. The exact remote trigger and whether pairing is required are not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit c7468e64bb5e in android-security-14.0.0_r15 applies the same four zero-length early returns. On this branch the helper is named gatt_tcb_get_payload_size_tx and the hunk offsets differ. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: commit 339ea2fc1905 in android-security-15.0.0_r3 (cherry-picked from 7de5617f7d52). 8/8 patched lines present at android-security-15.0.0_r17.",
      "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. On Android 14 the payload size helper is named gatt_tcb_get_payload_size_tx rather than gatt_tcb_get_payload_size, so match on the call site in each of the four functions rather than on the helper name. Older branches were not shown."
     ],
     "confidence": "high",
     "unknowns": [
      "How the channel comes to be closed while the request is still being processed (the specific race or sequence) is not shown. The commit only says it is possible in exceptional cases.",
      "Whether the researcher POC required pairing or any user interaction.",
      "Whether the Android 12/12L/13 fixes listed by the bulletin differ from the shown 14/15 commits (no diff provided).",
      "Whether other callers of gatt_tcb_get_payload_size outside gatt_sr.cc have the same unchecked pattern (not shown).",
      "The call relationship between gatts_process_primary_service_req and gatt_build_primary_service_rsp (not shown in the material)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49748": {
     "cve": "CVE-2024-49748",
     "summary": "Adds an early return in four GATT server request handlers in system/stack/gatt/gatt_sr.cc when gatt_tcb_get_payload_size() returns 0 (channel already closed), so a response buffer is no longer allocated with zero payload capacity and then written into.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Missing validation of a length value leading to heap out-of-bounds write (buffer sized from an unchecked zero payload size)",
     "root_cause": "In gatt_sr.cc, gatts_process_primary_service_req, gatts_process_find_info, gatts_process_read_by_type_req and gatts_process_read_req obtain the ATT payload size via gatt_tcb_get_payload_size(tcb, cid) and immediately use it to size the response buffer (sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET, allocated with osi_calloc or used as buf_len). Per the commit message, in exceptional cases the channel can already be closed when the function is called, so it returns 0. The handlers did not check for this, and per the commit message the zero length causes an OOB write in subsequent processing. For example, gatt_build_primary_service_rsp writes the opcode (and for read by group type a length byte) before comparing against payload_size.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:gatts_process_primary_service_req: after payload_size = gatt_tcb_get_payload_size(tcb, cid), return immediately if payload_size == 0, before msg_len is computed and p_msg is allocated.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_find_info: same check inserted after obtaining payload_size and before buf_len is computed.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_by_type_req: same check inserted before msg_len is computed and p_msg is osi_calloc'd.",
      "system/stack/gatt/gatt_sr.cc:gatts_process_read_req: same check inserted before buf_len is computed.",
      "Single commit; each hunk is a pure insertion (21 lines added, none removed). No error response is sent in the zero-size case; the request is dropped."
     ],
     "completeness": [
      "Covers four request handlers in gatt_sr.cc that size a response buffer from gatt_tcb_get_payload_size: primary service, find info, read by type, and read.",
      "gatt_build_primary_service_rsp also calls gatt_tcb_get_payload_size and writes header bytes before its payload_size bound check; it is not itself hardened by this commit.",
      "The fix does not change gatt_tcb_get_payload_size to avoid returning 0, nor does it prevent the underlying situation (handler running on an already closed channel); it only guards these call sites. Other users of gatt_tcb_get_payload_size are not shown in the material and are not addressed by this commit."
     ],
     "behavior_changes": [
      "GATT server requests (primary service discovery, find information, read by type, read) arriving when the channel payload size resolves to 0 are now silently discarded: no response and no ATT error response is sent."
     ],
     "preconditions": [
      "A remote GATT client sends an ATT request handled by one of the four patched handlers (primary service / read by group type, find information, read by type, read).",
      "The channel for the given cid is already closed (payload size 0) when the handler runs; the commit message describes this as an exceptional case.",
      "Whether pairing or any prior authentication is required is not stated in the material."
     ],
     "per_major": {
      "14": "Adapted fix: commit c7468e64bb5e in android-security-14.0.0_r15 adds the same four zero-size checks, with the call site named gatt_tcb_get_payload_size_tx instead of gatt_tcb_get_payload_size. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 339ea2fc1905 in android-security-15.0.0_r3 (cherry-pick of 7de5617f7d52). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin (not listed); the patched region (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed); the patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Pure insertion of four identical guards; the only adaptation across branches is the helper name (gatt_tcb_get_payload_size on 15, gatt_tcb_get_payload_size_tx on 14). Verify the local name of the payload-size helper and place each guard directly after its assignment and before any buffer length computation."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact sequence by which the channel becomes closed while a request is still processed is not shown; the commit only describes it as exceptional.",
      "The callers of gatt_build_primary_service_rsp are not shown, so whether it is protected only via the patched gatts_process_primary_service_req is not established.",
      "Whether any other callers of gatt_tcb_get_payload_size outside the four patched handlers have the same pattern is not shown in the material.",
      "The researcher POC referenced in the commit is not included."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43095": {
     "cve": "CVE-2024-43095",
     "summary": "PermissionController now only treats a platform permission group as granted (and therefore eligible for auto-granting its remaining permissions) when a granted permission in the group is defined by the platform or a system app, or when all permissions in the group were auto-granted (granted and REVOKE_WHEN_REQUESTED).",
     "component": "platform/packages/modules/Permission, PermissionController runtime permission grant logic (LightAppPermGroup / PermGroupLiveData / GrantPermissionsViewModel)",
     "weakness": "Improper authorization: when deciding whether a whole platform permission group counts as granted, the grant state of permissions defined by non-platform, non-system packages in that group was trusted (CWE-863 class, from the diff).",
     "root_cause": "In LightAppPermGroup.AppPermSubGroup, isGranted was `permissions.any { it.value.isGrantedIncludingAppOp }` and isGrantedExcludingRWROrAllRWR was `any { granted && !isRevokeWhenRequested } || all { granted && isRevokeWhenRequested }`. Neither looked at which package defines each permission. So a granted permission defined by a non-platform, non-system package inside a platform permission group (permGroupInfo.packageName == Utils.OS_PKG) could make the group count as granted. GrantBehavior.isGroupFullyGranted, BackgroundGrantBehavior and LocationGrantBehavior (all via group.foreground.isGrantedExcludingRWROrAllRWR) would then report the group as fully granted. GrantPermissionsViewModel (around line 592) then grants any requested permission in the group that is not yet granted (grantForeground/BackgroundRuntimePermissions). The data model (LightPermInfo) also had no record of whether the defining package is a system app.",
     "fix_analysis": [
      "LightPermInfo.kt: adds an `isSystem: Boolean?` field and a constructor parameter, `LightPermInfo(permInfo: PermissionInfo, isSystem: Boolean?)`.",
      "LightPackageInfo.kt constructor(pI: PackageInfo): builds the permissions the package defines with `LightPermInfo(perm, pI.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM != 0)`.",
      "PermGroupLiveData.kt onUpdate(): collects the PermissionInfos first and registers LightPackageInfoLiveData sources for every defining package plus the group's package. It returns early until all packageLiveDatas are initialized, then builds each LightPermInfo with isSystem taken from the defining package's appFlags (null if that package LiveData has no value). The PermGroup value is only published after this step.",
      "LightPermInfoLiveData.kt loadDataAndPostValue(): passes `null` for isSystem, so permissions loaded through this path count as platform or system only if their package is Utils.OS_PKG.",
      "LightPermission.kt: adds `isPlatformOrSystem = permInfo.packageName == Utils.OS_PKG || permInfo.isSystem == true` and `allowFullGroupGrant = isGranted && !isRevokeWhenRequested`. Renames isGrantedIncludingAppOp to isGranted, a mechanical rename applied across all users.",
      "LightAppPermGroup.kt: adds `isPlatformPermissionGroup = permGroupInfo.packageName == Utils.OS_PKG` and passes it to both AppPermSubGroup instances (foreground and background). For platform groups, AppPermSubGroup.isGranted now requires the granted permission to be isPlatformOrSystem. isGrantedExcludingRWROrAllRWR is renamed allowFullGroupGrant and becomes `any { it.allowFullGroupGrant && (!isPlatformPermissionGroup || it.isPlatformOrSystem) } || all { it.isGranted && it.isRevokeWhenRequested }`.",
      "Switched to group.foreground/background.allowFullGroupGrant, so they inherit the platform/system restriction: grantPermissions/GrantBehavior.kt isGroupFullyGranted; BackgroundGrantBehavior.kt getPrompt, isGroupFullyGranted and isForegroundFullyGranted; LocationGrantBehavior.kt isGroupFullyGranted.",
      "GrantPermissionsViewModel.kt (grant branch of the user-response handler): tracks the permissions actually affected and computes `shouldDenyFullGroupGrant = group.isPlatformPermissionGroup && affectedPermissions.none { permissions[it]?.isPlatformOrSystem == true }`. If this is true, the group state is set to STATE_UNKNOWN instead of STATE_GRANTED.",
      "GrantRevokeTests.kt and ReviewPermissionsViewModelTest.kt are updated for the new constructor parameter and the rename. The commit message cites DynamicPermissionsTest, which is not in the diff."
     ],
     "completeness": [
      "Covers both the foreground and background AppPermSubGroup computations, the generic, Background and Location isGroupFullyGranted paths, and the group state set after a grant in GrantPermissionsViewModel.",
      "The restriction applies only when isPlatformPermissionGroup is true (group defined by Utils.OS_PKG). Groups defined by other packages keep the old 'any granted permission' semantics.",
      "The `all { isGranted && isRevokeWhenRequested }` branch of allowFullGroupGrant has no platform/system condition. It relies on every permission in the subgroup having been auto-granted (RWR).",
      "isSystem comes only from ApplicationInfo.FLAG_SYSTEM of the defining package. LightPermInfoLiveData sets isSystem to null, which counts as not system.",
      "StorageGrantBehavior.isGroupFullyGranted (READ_MEDIA_VISUAL with photo picker) and HealthGrantBehavior.isGroupFullyGranted do not use allowFullGroupGrant. Only the rename was applied there (see potential_variants)."
     ],
     "behavior_changes": [
      "In platform permission groups, a granted permission defined by a non-system, non-platform package no longer makes the subgroup count as granted (AppPermSubGroup.isGranted / allowFullGroupGrant).",
      "PermGroupLiveData.onUpdate no longer publishes or updates its value until all LightPackageInfoLiveData sources for the defining packages are initialized.",
      "In the grant dialog, granting only non-platform, non-system permissions in a platform group leaves the group state at STATE_UNKNOWN rather than STATE_GRANTED.",
      "The LightPermInfo constructor signature changed (extra isSystem parameter), and LightPermission.isGrantedIncludingAppOp is renamed to isGranted. This is an API change for callers and tests inside the module."
     ],
     "preconditions": [
      "A permission defined by a non-platform package sits in a platform permission group (permGroupInfo.packageName == Utils.OS_PKG).",
      "That permission is granted for the requesting app, and the app requests platform permissions in the same group through the GrantPermissions flow (GrantPermissionsViewModel / GrantBehavior.isGroupFullyGranted).",
      "The defining package is not a system app (FLAG_SYSTEM not set)."
     ],
     "per_major": {
      "14": "adapted fix: commit ea7b26fe98c1 in android-security-14.0.0_r15. Same data-model changes (LightPermInfo.isSystem, isPlatformOrSystem, allowFullGroupGrant, isPlatformPermissionGroup, deferred PermGroupLiveData value). The grant logic changes are applied directly in GrantPermissionsViewModel (inline allowFullGroupGrant checks next to canAutoGrantWholeGroup, STATE_ALLOWED instead of STATE_GRANTED). The 14 diff does not touch any grantPermissions/*GrantBehavior.kt files. 7/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit f677d33568d9 in android-security-15.0.0_r3 (cherry-pick of 46abb4e1fd36). 7/8 lines of the patched region 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": [
      "The Android 14 variant shows how to adapt when grantPermissions/GrantBehavior.kt is not used. Apply the allowFullGroupGrant checks directly where GrantPermissionsViewModel inlines isGrantedExcludingRWROrAllRWR, and map STATE_GRANTED to the branch's state constant (STATE_ALLOWED on 14). The isGrantedIncludingAppOp -> isGranted rename is mechanical. The essential parts are: LightPermInfo.isSystem; LightPermission.isPlatformOrSystem and allowFullGroupGrant; LightAppPermGroup.isPlatformPermissionGroup and the AppPermSubGroup predicates; PermGroupLiveData.onUpdate; and the shouldDenyFullGroupGrant state logic.",
      "PermGroupLiveData.onUpdate now returns early until packageLiveDatas are initialized, so it relies on onUpdate running again when the LightPackageInfoLiveData sources load (SmartUpdateMediatorLiveData behavior, general platform knowledge). Every LightPermInfo(PermissionInfo) call site in the branch must be updated for the new constructor signature, including tests such as GrantRevokeTests.kt."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether arbitrary apps can define permissions in platform groups, and how such a permission comes to be granted, is not shown in the material.",
      "The DynamicPermissionsTest referenced in the commit message is not included in the diff.",
      "Which consumers use LightPermInfoLiveData (isSystem = null), and whether that path feeds any grant decision, is not shown.",
      "Which UI consumers rely on AppPermSubGroup.isGranted is not shown.",
      "Why the per-major facts show only 7/8 patched lines present on every branch is not explained."
     ],
     "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-2024-43763": {
     "cve": "CVE-2024-43763",
     "summary": "Changes the early-exit condition in the GATT server's Read Multiple (Variable) response builder so that a zero-length attribute value no longer aborts packet construction, and only a genuinely full buffer (total_len == mtu with a non-empty value pending) stops the loop.",
     "component": "platform/packages/modules/Bluetooth, GATT server (system/stack/gatt/gatt_sr.cc)",
     "weakness": "Logic error in response construction: a legitimate zero-length attribute value was conflated with the out-of-buffer-space condition, causing truncated or empty ATT responses (bulletin classifies the impact as DoS).",
     "root_cause": "In build_read_multi_rsp (system/stack/gatt/gatt_sr.cc), after computing len = min(attr_value.len, mtu - total_len), the code did `if (len == 0) break;` intending to stop when no buffer space remained. But len is also 0 when the attribute value itself is legitimately empty (attr_value.len == 0). In that case the loop broke out before writing the 2-byte length field (variable_len mode) and before processing any later handles. Per the commit message, if the first characteristic in the response had a 0-length value, the server returned an empty response packet.",
     "fix_analysis": [
      "system/stack/gatt/gatt_sr.cc:build_read_multi_rsp: the break condition `if (len == 0)` is replaced by `if (total_len == mtu && p_rsp->attr_value.len > 0)`. The loop now stops only when the buffer is exactly full (total_len, which already includes the 2-byte length header in variable_len mode, equals mtu) and the pending value actually has data that cannot fit.",
      "When attr_value.len == 0, execution now falls through: in variable_len mode UINT16_TO_STREAM writes a length of 0 and p_buf->len += 2; ARRAY_TO_STREAM copies 0 bytes; the loop proceeds to the next handle. The preceding `if (total_len > mtu) break;` check and the is_overflow truncation logic are unchanged.",
      "The out-of-space case previously caught by len == 0 with attr_value.len > 0 (i.e. mtu - total_len == 0) is still caught, since that is exactly total_len == mtu with a non-empty value."
     ],
     "completeness": [
      "The fix is confined to the single break condition in build_read_multi_rsp; it covers both the variable_len (Read Multiple Variable) and fixed-length (Read Multiple) paths since both go through the same loop.",
      "Zero-length values at any position in the handle list (not just the first) are now handled, because the condition no longer depends on len.",
      "No other code in the function is changed: the GATT_NOT_FOUND handling for handle mismatch, the is_overflow early break, and the leading `total_len > mtu` check are untouched. Nothing in the material indicates a similar pattern elsewhere."
     ],
     "behavior_changes": [
      "ATT Read Multiple Variable responses now include a 2-byte length field of 0 for empty characteristic values instead of terminating the response at that point.",
      "Subsequent handles after a zero-length value are now included in the response (subject to MTU).",
      "The 'Buffer space not enough for this data item, skipping' break now only triggers when the buffer is exactly full and the pending value is non-empty."
     ],
     "preconditions": [
      "The device acts as a GATT server and builds a multi-handle read response in build_read_multi_rsp from p_cmd->multi_rsp_q (Read Multiple, or Read Multiple Variable when multi_req.variable_len is set). That this is triggered by a remote client's request is inferred from the function's role and is not shown in the material.",
      "At least one requested attribute has a zero-length value (attr_value.len == 0); per the commit message the empty-packet outcome occurs when the first one is zero-length.",
      "Reachability details beyond this function (callers, connection requirements) are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit ec76dc86a8e9 in android-security-14.0.0_r15 applies the identical one-line condition change (only the logging macro differs, VLOG(1) vs log::verbose); 2/2 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix: commit 124c2ac6cffb in android-security-15.0.0_r3; 2/2 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin; at android-security-16.0.0_r8, 1/2 lines of the patched region are present. The material does not show the form of the condition there or why it differs.",
      "17": "not affected per the bulletin; at android-security-17.0.0_r1, 1/2 lines of the patched region are present. The material does not show the form of the condition there."
     },
     "backport_notes": [
      "Single one-line change with no dependencies shown; when backporting, adapt only the logging macro (VLOG(1) on 14, log::verbose on 15) and keep the preceding `total_len > mtu` check and the `len = std::min(...)` computation intact, since the new condition relies on total_len already including the 2-byte length header in variable_len mode."
     ],
     "confidence": "high",
     "unknowns": [
      "The precise DoS mechanism (what consumer of the empty/truncated response fails) is not shown; the bulletin only rates it High/DoS.",
      "The exact state of the patched line on Android 16 and 17 (1/2 lines matching) and the reason for the difference are not shown in the material.",
      "The request dispatch path into build_read_multi_rsp is not shown (index search returned no callers).",
      "The second bug id (356886209) referenced in the commit is not explained in the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-43765": {
     "cve": "CVE-2024-43765",
     "summary": "DocumentsUI now requests android.permission.HIDE_OVERLAY_WINDOWS. On S and later it also calls Window.setHideOverlayWindows(true) on the BaseActivity window and on the ConfirmFragment dialog window, so overlay windows from other apps are hidden over its UI.",
     "component": "platform/packages/apps/DocumentsUI, BaseActivity and picker/ConfirmFragment (file picker UI)",
     "weakness": "Missing protection against UI overlay (clickjacking / tapjacking): DocumentsUI windows did not opt out of being covered by overlay windows from other apps.",
     "root_cause": "BaseActivity.onCreate created the activity window without setting the hide-overlay-windows flag. ConfirmFragment returned builder.create() unchanged, without setting the flag on the dialog window. Neither window was protected from being covered by overlay windows from other apps, so the DocumentsUI UI and the confirmation dialog (TYPE_OVERWRITE / TYPE_OEPN_TREE) could be clickjacked, which matches the commit title 'Prevent clickjacking attack in DocsUi'. The manifest also did not declare HIDE_OVERLAY_WINDOWS, which the commit adds alongside the flag.",
     "fix_analysis": [
      "AndroidManifest.xml: adds <uses-permission android:name=\"android.permission.HIDE_OVERLAY_WINDOWS\"/>.",
      "src/com/android/documentsui/BaseActivity.java:onCreate: if SdkLevel.isAtLeastS(), calls getWindow().setHideOverlayWindows(true). This happens before the theme is applied and before super.onCreate. It applies to activities that run BaseActivity.onCreate.",
      "src/com/android/documentsui/picker/ConfirmFragment.java: replaces `return builder.create()` with code that creates the Dialog and, if SdkLevel.isAtLeastS(), calls dialog.getWindow().setHideOverlayWindows(true) before returning it. Also imports com.android.modules.utils.build.SdkLevel."
     ],
     "completeness": [
      "Covers the main window of activities that use BaseActivity.onCreate and the ConfirmFragment dialog window (overwrite and open-tree confirmation types).",
      "The flag is only applied when SdkLevel.isAtLeastS(). The manifest declares minSdkVersion 29, so on devices below S the fix adds no protection.",
      "Only the ConfirmFragment dialog is handled explicitly. A Dialog has its own window, separate from the activity window (general platform knowledge), so the BaseActivity change does not cover other dialog windows in DocumentsUI. The material does not show whether such dialogs exist or matter for security.",
      "Whether the fix works also depends on the platform granting HIDE_OVERLAY_WINDOWS to DocumentsUI. The material does not show the grant side."
     ],
     "behavior_changes": [
      "On Android S and later, non-system overlay windows from other apps are hidden while a window with the flag set (the BaseActivity window or the ConfirmFragment dialog) is shown (general platform knowledge).",
      "DocumentsUI now declares one more permission, HIDE_OVERLAY_WINDOWS, in its manifest.",
      "The diff does not change picker results, intents, or error handling."
     ],
     "preconditions": [
      "A DocumentsUI activity using BaseActivity.onCreate, or the ConfirmFragment dialog, is in the foreground. Third-party callers launch DocumentsUI through ACTION_OPEN_DOCUMENT / ACTION_OPEN_DOCUMENT_TREE (for example DocumentsSample.onClick in development/samples/ApiDemos, per index search).",
      "Another app can draw overlay windows over the DocumentsUI UI. After the fix, this is only possible on devices below S, where the flag is not set."
     ],
     "per_major": {
      "14": "adapted fix: commit 6a1dd25117d5 in android-security-14.0.0_r15. It has the same manifest and ConfirmFragment hunks, and also adds the com.android.modules.utils.build.SdkLevel import in BaseActivity. 8/8 patched lines are present at android-security-14.0.0_r29.",
      "15": "same fix: commit c8e21a60d784 in android-security-15.0.0_r3. This diff does not add the SdkLevel import to BaseActivity. 8/8 patched lines are present at android-security-15.0.0_r17.",
      "16": "not affected: the bulletin does not list Android 16. The patched region is present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "not affected: the bulletin does not list Android 17. The patched region is present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "Single commit touching three files. On older branches BaseActivity may lack the com.android.modules.utils.build.SdkLevel import (Android 14 needed it added). Make sure the import and the modules-utils-build dependency are available in the DocumentsUI build (general platform knowledge). Window.setHideOverlayWindows and HIDE_OVERLAY_WINDOWS exist only from API 31, so keep the SdkLevel.isAtLeastS() guard for a minSdk 29 build (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "How the fix was delivered for Android 12, 12L and 13, which the bulletin lists as affected. The material has no commit for those branches.",
      "Which DocumentsUI activities extend BaseActivity (for example, whether PickActivity does). The class hierarchy is not shown.",
      "Whether other DialogFragments in DocumentsUI besides ConfirmFragment show windows that should also get the flag.",
      "The exact trigger scenario from the bug (b/233605527) is referenced but not shown.",
      "The name of the ConfirmFragment method that was changed is not shown in the diff."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 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-2024-49724": {
     "cve": "CVE-2024-49724",
     "summary": "Hardens AccountManagerService's validation of an authenticator-supplied KEY_INTENT by pinning the intent to the component that passed the resolve/signature check and by rejecting Intent subclasses on both the original and the re-parceled copy in checkKeyIntentParceledCorrectly.",
     "component": "platform/frameworks/base, services/core AccountManagerService (accounts)",
     "weakness": "Insufficient validation of an untrusted Intent (validated target not bound to the intent handed to the client; incomplete Intent subclass check on the parcel round-trip copy)",
     "root_cause": "In AccountManagerService.checkKeyIntent, an account authenticator returns a Bundle with a KEY_INTENT entry. The check resolved that intent with pm.resolveActivityAsUser and verified that the resolved activity is an exported system activity or shares AUTH signature capability with the authenticator. However, the checked component was never written into the intent, so the intent passed onward was not tied to the activity that passed the check. Separately, checkKeyIntentParceledCorrectly only rejected a non-Intent.class object for the intent read from the original bundle. The simulateIntent re-read from the parcel round trip was compared with filterEquals without any class check, so a difference in deserialized type between the two copies was not detected.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountManagerService.java: checkKeyIntent, after the isExportedSystemActivity / hasSignatureCapability check succeeds, now calls intent.setComponent(targetActivityInfo.getComponentName()) and writes the intent back with bundle.putParcelable(AccountManager.KEY_INTENT, intent), so the intent in the bundle is explicit and targets the activity that was validated.",
      "services/core/java/com/android/server/accounts/AccountManagerService.java: checkKeyIntentParceledCorrectly, the early 'intent != null && intent.getClass() != Intent.class' check is removed. After the null handling, it is replaced with 'intent.getClass() != Intent.class || simulateIntent.getClass() != Intent.class' returning false, so both the original and the parcel round-trip copy must be exactly android.content.Intent before filterEquals is consulted."
     ],
     "completeness": [
      "Covers the KEY_INTENT path in checkKeyIntent: the resolve and signature check is unchanged, but its result is now enforced by writing the resolved component into the bundle.",
      "Covers the re-parceled copy in checkKeyIntentParceledCorrectly, which previously only type-checked the original intent.",
      "Existing checks (content scheme rejection, empty ClipData, resolveInfo == null) are left as they were; the fix adds to them rather than reworking them.",
      "Once intent is non-null, the new subclass check dereferences simulateIntent without a null check, where the prior code would have passed a null argument to intent.filterEquals(simulateIntent). The material does not show whether a null simulateIntent with a non-null intent can occur."
     ],
     "behavior_changes": [
      "Intents in the KEY_INTENT entry are now explicit: the component is set to the activity PackageManager resolved for the account's user (mAccounts.userId), so an implicit intent supplied by the authenticator becomes explicit.",
      "The bundle's KEY_INTENT entry is replaced server-side with the modified Intent object.",
      "Responses whose KEY_INTENT deserializes to an Intent subclass on the parcel round trip are now rejected (checkKeyIntentParceledCorrectly returns false), in addition to the existing rejection of a subclass in the original bundle."
     ],
     "preconditions": [
      "An account authenticator (an installed app exposing an authenticator for an account type) returns a result Bundle containing AccountManager.KEY_INTENT to AccountManagerService.",
      "The intent must pass the existing checks in checkKeyIntent: it resolves via resolveActivityAsUser for mAccounts.userId, is not a content: scheme, and resolves to an exported system activity or to a package with AUTH signature capability relative to the authenticator.",
      "A client app then receives the bundle and launches the intent (the client-side launch is outside the patched code and not shown in the material)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 985bdc676ac5 in android-security-14.0.0_r16 (resolved by change_id), identical diff to the 15 commit at different line offsets; 7/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 5018e2273cb3 in android-security-15.0.0_r4 (resolved by change_id); 7/8 patched lines present at android-security-15.0.0_r17, and the code context shows setComponent/putParcelable in checkKeyIntent.",
      "16": "not affected per the bulletin (not listed); the patched region (7/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); the patched region (7/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit in AccountManagerService.java. Both hunks use only existing locals (targetActivityInfo, bundle, intent, simulateIntent), so the change applies to any branch that already has checkKeyIntent with the resolveActivityAsUser/hasSignatureCapability check and checkKeyIntentParceledCorrectly. Branches lacking the typed bundle.getParcelable(key, Intent.class) overload (added in API 33) would need the untyped getParcelable form (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact scenario in bug 360846772 (how an implicit or subclassed KEY_INTENT led to the EoP) is not described in the material.",
      "The call sites of checkKeyIntent and checkKeyIntentParceledCorrectly and what the caller does when they return false are not shown.",
      "Which 1 of the 8 patched lines is reported missing in the latest tags is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49732": {
     "cve": "CVE-2024-49732",
     "summary": "Adds a system-UID caller check to the three hidden permission-sync binder entry points (enablePermissionsSync, disablePermissionsSync, getPermissionSyncRequest) in CompanionDeviceManagerService, so that non-system callers get a SecurityException instead of reaching SystemDataTransferProcessor.",
     "component": "platform/frameworks/base, Companion Device Manager (services/companion, CompanionDeviceManagerService binder impl)",
     "weakness": "missing permission check (no caller authorization on hidden binder APIs)",
     "root_cause": "In services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java, the binder-exposed methods enablePermissionsSync(int associationId), disablePermissionsSync(int associationId) and getPermissionSyncRequest(int associationId) passed calls straight to mSystemDataTransferProcessor with no caller check. Several nearby methods do carry @EnforcePermission and call *_enforcePermission(). Per index search, SystemDataTransferProcessor.enablePermissionsSync only calls mAssociationStore.getAssociationWithCallerChecks(associationId) and then writes a PermissionSyncRequest with setUserConsented(true) to mSystemDataTransferRequestStore. So a caller that passed the association check could record user consent for permission sync without going through a system-mediated flow. The bulletin rates the issue as High-severity EoP.",
     "fix_analysis": [
      "CompanionDeviceManagerService.java: enablePermissionsSync. Before handing off to mSystemDataTransferProcessor.enablePermissionsSync, the method now checks getCallingUid() != SYSTEM_UID and throws SecurityException(\"Caller must be system UID\").",
      "CompanionDeviceManagerService.java: disablePermissionsSync. The same SYSTEM_UID check is added before mSystemDataTransferProcessor.disablePermissionsSync.",
      "CompanionDeviceManagerService.java: getPermissionSyncRequest. The same SYSTEM_UID check is added before returning mSystemDataTransferProcessor.getPermissionSyncRequest.",
      "The check is a strict UID equality, not a permission check, so only callers running as the system UID can use these three APIs. All other callers are rejected at the binder entry point before the processor (and its association lookup) is called."
     ],
     "completeness": [
      "Covers exactly the three hidden permission-sync binder methods. Each gets the same gate, placed before any processing, and SystemDataTransferProcessor logic is unchanged.",
      "Does not modify SystemDataTransferProcessor itself. Per index search, its enablePermissionsSync still relies only on getAssociationWithCallerChecks. Through the binder, it is now reachable only by system-UID callers.",
      "The adjacent binder methods enableSystemDataSync and disableSystemDataSync (r17 context, lines 637-645) still have no @EnforcePermission annotation or UID check. The material does not show whether their delegate (mAssociationRequestsProcessor) does its own authorization.",
      "The client wrapper CompanionDeviceManager.enablePermissionsSync is not changed. Enforcement is server-side only."
     ],
     "behavior_changes": [
      "enablePermissionsSync, disablePermissionsSync and getPermissionSyncRequest now throw SecurityException(\"Caller must be system UID\") for any caller whose UID is not SYSTEM_UID. Clients using the binder interface (including the CompanionDeviceManager wrapper) receive this SecurityException.",
      "No change for callers running as the system UID."
     ],
     "preconditions": [
      "The caller can invoke the hidden ICompanionDeviceManager methods enablePermissionsSync / disablePermissionsSync / getPermissionSyncRequest over binder. Per index search, they are reachable through the CompanionDeviceManager.enablePermissionsSync wrapper.",
      "Before the fix, the only gate shown is the association-level check in SystemDataTransferProcessor.enablePermissionsSync (mAssociationStore.getAssociationWithCallerChecks), so the caller needed an associationId that passes that check. The material does not show what that check enforces."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29, 0/6 patched lines are present. The material does not show whether these binder methods are absent in 14 or present without the check.",
      "15": "Same fix, resolved via bug_id in android-security-15.0.0_r4 (cherry-pick of internal commit 08f57da9e398, Change-Id I9771ce38728d40b026d94cb6785d55f144c66790). 6/6 lines present at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin (not listed). 6/6 patched lines present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). 6/6 patched lines present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit adding 9 lines to one file. It requires the three permission-sync binder methods in CompanionDeviceManagerService, and SYSTEM_UID and getCallingUid must resolve in that class (the material does not show whether they were already in scope before the fix). If a fork lacks these methods (the 0/6 result on 14 may mean this), there is nothing to patch."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the methods exist in Android 14 (0/6 lines could mean the code is absent or unpatched). The bulletin lists only 15 and 15-next.",
      "What mAssociationStore.getAssociationWithCallerChecks actually enforces, and so how broad the set of possible callers was before the fix.",
      "What disablePermissionsSync and getPermissionSyncRequest in SystemDataTransferProcessor do and whether they perform caller checks (only enablePermissionsSync is shown).",
      "Whether SYSTEM_UID and getCallingUid were already in scope in the class before the fix.",
      "No test was added (the commit says Manual test), so the material shows no in-tree regression coverage."
     ],
     "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-2024-49733": {
     "cve": "CVE-2024-49733",
     "summary": "ServiceListing.reload() in SettingsLib now always includes every component already approved in the backing Settings.Secure setting (queried by explicit ComponentName, without permission/validator checks), so previously approved services that no longer meet the current listing criteria are still shown to the user.",
     "component": "platform/frameworks/base, packages/SettingsLib (com.android.settingslib.applications.ServiceListing)",
     "weakness": "Incomplete/incorrect listing of granted access (approved component filtered out of the UI list because validation was applied uniformly to already-approved entries); bulletin type ID.",
     "root_cause": "ServiceListing.reload() built mServices solely from pmWrapper.queryIntentServicesAsUser(new Intent(mIntentAction), ...) and dropped any ServiceInfo whose info.permission did not equal mPermission or that failed mValidator.test(info). It did not consult mEnabledServices (the ComponentNames loaded from the Settings.Secure setting by loadEnabledServices()). A component that had been approved earlier, under more lax criteria, but that now fails the permission check, fails the validator, or does not resolve for mIntentAction, was therefore omitted from the list passed to Callback.onServicesReloaded(), while its approval remained in the setting.",
     "fix_analysis": [
      "packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java:reload(): the first loop over queryIntentServicesAsUser(new Intent(mIntentAction), flags, user) is now wrapped in `if (!mEnabledServices.contains(info.getComponentName()))`; the mPermission equality check, the mValidator check and the mServices.add(info) call only run for components that are not already approved.",
      "ServiceListing.java:reload(): a second loop iterates every ComponentName cn in mEnabledServices, calls pmWrapper.queryIntentServicesAsUser(new Intent().setComponent(cn), flags, user) and adds every resulting resolveInfo.serviceInfo to mServices unconditionally (no permission or validator check). Callbacks then receive the combined list.",
      "packages/SettingsLib/tests/robotests/.../ServiceListingTest.java: adds testHasPermissionWithoutMeetingCurrentRegs, which approves pkg2/service2 in TEST_SETTING, makes it fail the validator, and asserts that onServicesReloaded receives both s2 (approved) and s1 (validated) and that queryIntentServicesAsUser is called twice; existing tests gain ServiceInfo.name values."
     ],
     "completeness": [
      "Covers the single code path ServiceListing.reload(); both the action-based query and the new per-component query are in that method, and approved components are excluded from the first loop so they are not added twice.",
      "An approved component only appears if queryIntentServicesAsUser(new Intent().setComponent(cn), flags, user) returns it; the flags are unchanged (GET_SERVICES | GET_META_DATA, plus direct-boot flags when mAddDeviceLockedFlags), so setting entries that do not resolve under those flags are still not listed. Whether this matters depends on PackageManager matching semantics not shown in the material.",
      "The approval string itself is not pruned or changed; the fix only changes what is displayed/reported via onServicesReloaded. The consumers of ServiceListing (Settings screens) are not part of the diff.",
      "mEnabledServices is parsed from the setting for mContentResolver, while queries use ActivityManager.getCurrentUser(); this user handling is unchanged by the fix."
     ],
     "behavior_changes": [
      "Services whose ComponentName is in the backing Settings.Secure setting are now always reported to callbacks, even if they do not declare mPermission, fail mValidator, or do not handle mIntentAction.",
      "reload() now issues one extra PackageManager.queryIntentServicesAsUser call per approved component (the new test expects two calls with one approved component).",
      "The 'Skipping ... service' warning log is no longer emitted for already-approved components."
     ],
     "preconditions": [
      "A ComponentName is present in the Settings.Secure setting managed by the ServiceListing instance (mSetting), i.e. it was approved earlier.",
      "That component no longer satisfies the current criteria: its ServiceInfo.permission differs from mPermission, the configured mValidator rejects it, or it does not resolve for mIntentAction.",
      "A Settings UI built on ServiceListing calls reload() for the current user; the specific consumers are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 8516dfb89f99 in android-security-14.0.0_r16, identical diff to ServiceListing.java and ServiceListingTest.java; 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 3b2be226f1b9 in android-security-15.0.0_r4 (cherry-pick of 234c5e843ca4); 8/8 patched 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; the Android 14 and 15 diffs are byte-identical (same pre/post blob ids c8bcabff1094..261c722e517c), so the hunk should apply cleanly to any branch whose ServiceListing.reload() still has the original permission/validator loop. The bulletin also lists 12, 12L and 13 as affected; no commit for those branches is in the material.",
      "The commit also updates existing ServiceListingTest cases to set ServiceInfo.name on their test ServiceInfo objects; carry these test changes together with the new test."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Settings screens (consumers of ServiceListing) were affected and which permission/validator combination triggered the original report are not shown.",
      "Fix commits for Android 12, 12L and 13 are not included in the material.",
      "Whether queryIntentServicesAsUser by explicit component with the existing flags returns disabled components is not shown.",
      "Why the existing tests needed ServiceInfo.name set is not stated in the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49734": {
     "cve": "CVE-2024-49734",
     "summary": "ConnectivityService installs ingress discard rules so that packets addressed to a VPN interface's address but arriving on a non-VPN interface are dropped, with a follow-up that excludes legacy VPNs from the rule.",
     "component": "platform/packages/modules/Connectivity, ConnectivityService (VPN / ingress discard rules)",
     "weakness": "Missing ingress filtering: traffic sent to a VPN-assigned address was accepted when it arrived on a non-VPN interface (information disclosure per bulletin)",
     "root_cause": "Before the base change, ConnectivityService did not install a rule rejecting packets sent to an address assigned to a VPN interface when those packets arrive on a different (non-VPN) interface. The commit subject and the javadoc in ConnectivityService.java ('Ingress discard rule is added to the address iff 1. The address is not a link local address 2. The address is used by a single VPN interface and not used by any other interfaces') show that the missing piece was a per-address ingress discard rule tied to the VPN interface.",
     "fix_analysis": [
      "Commit c4c1c3721c0a (Android 15, android-15.0.0_r1, original change 2795711), 'Drop packets to VPN address ingressing via non-VPN interface': adds 92 lines to service/src/com/android/server/ConnectivityService.java and 4 lines to service/src/com/android/server/connectivity/ConnectivityFlags.java. The diff body is not in the material. The Android 14 code context shows the resulting logic: a loop over NetworkAgentInfo builds an ingressDiscardRules set of (InetAddress, interface) pairs for VPN agents, covering addresses that are not link-local and are used by a single VPN interface and by no other interface.",
      "Commit c16addaec604 (Android 14, android-security-14.0.0_r16), 'Skip adding ingress discard rule to legacy VPN': in ConnectivityService.java, that loop now also skips agents where getVpnType(agent) == VpnManager.TYPE_VPN_LEGACY (in addition to !agent.isVPN() and agent.isDestroyed()). It also updates the javadoc to say the rule applies only to non-Legacy VPNs, because some legacy VPNs need to receive packets to the VPN address through a non-VPN interface.",
      "Tests: the base change updates ConnectivityServiceTest.java, adds CSIngressDiscardRuleTests.kt (289 lines), and updates CSAgentWrapper.kt and CSTest.kt."
     ],
     "completeness": [
      "The rule covers only addresses that are not link-local and are used by a single VPN interface and no other interface (per the javadoc in the Android 14 context). Addresses shared across interfaces and link-local addresses get no discard rule.",
      "After the Android 14 follow-up, legacy VPNs (VpnManager.TYPE_VPN_LEGACY) are excluded, so their addresses are not protected by an ingress discard rule.",
      "The base change adds 4 lines to ConnectivityFlags.java. Their content, and whether they gate the protection, are not shown.",
      "Destroyed agents (agent.isDestroyed()) are skipped when building the rule set."
     ],
     "behavior_changes": [
      "Packets sent to a (non-legacy) VPN interface address that arrive on any other interface are dropped instead of delivered.",
      "The rule computation is documented as callable during network disconnects, after the nai has been removed from mNetworkAgentInfos.",
      "After the follow-up commit, legacy VPNs keep the old behavior (no discard rule)."
     ],
     "preconditions": [
      "A VPN network (non-legacy after the follow-up) is connected and has at least one non-link-local address that is not shared with any other interface.",
      "Traffic addressed to that VPN address arrives on a non-VPN interface of the device."
     ],
     "per_major": {
      "14": "Adapted fix: the bulletin resolution is c16addaec604 in android-security-14.0.0_r16 ('Skip adding ingress discard rule to legacy VPN'). It is a follow-up to the base ingress discard rule logic, which is already present in the Android 14 code context of that diff. At android-security-14.0.0_r29, 6 of 8 lines of the patched region are present (cause of the drift unknown).",
      "15": "Same fix: c4c1c3721c0a (base change, 390 insertions including tests) is in android-15.0.0_r1. The material does not show whether the legacy-VPN follow-up is present in Android 15, or the state at android-security-15.0.0_r17.",
      "16": "Not affected: not listed by the bulletin.",
      "17": "Not affected: not listed by the bulletin."
     },
     "backport_notes": [
      "Two-step fix: first the base change that adds ingress discard rule computation to ConnectivityService.java (c4c1c3721c0a / change 2795711), then the legacy-VPN exclusion (c16addaec604), which relies on getVpnType(agent) and VpnManager.TYPE_VPN_LEGACY. Connectivity is a mainline module, so the fix may also arrive through a module update rather than the platform tag (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The diff body of the Android 15 base commit c4c1c3721c0a is not included. The exact method names, the mechanism used to install the ingress discard rules, and the purpose of the 4 lines added to ConnectivityFlags.java (for example, whether they add a feature flag and what its default is) are not shown.",
      "Whether the legacy-VPN follow-up is present in Android 15 security tags, and the state of the patched region at android-security-15.0.0_r17.",
      "Why 2 of 8 lines of the patched region differ at android-security-14.0.0_r29.",
      "The specific trigger and what information is disclosed are not described in the material beyond the bulletin type ID."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 10,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49735": {
     "cve": "CVE-2024-49735",
     "summary": "Caps the size of vibration data stored on a NotificationChannel by truncating vibration patterns to 500 entries, cropping non-repeating composed VibrationEffects (new VibrationEffect.cropToLengthOrNull) or dropping effects whose serialized form exceeds 32,768 characters, and no longer serializing a VibrationEffect to XML when an equivalent pattern already exists.",
     "component": "platform/frameworks/base, NotificationChannel / VibrationEffect (core/java/android/app, core/java/android/os)",
     "weakness": "Missing size limit on caller-supplied data that is persisted by the system (improper input validation / unbounded resource consumption)",
     "root_cause": "NotificationChannel accepted a VibrationEffect (behind Flags.notificationChannelVibrationEffectApi) from the Parcel constructor and from setVibrationEffect() without any size limit, and setVibrationPattern() stored the raw long[] without truncating it in the setter. MAX_VIBRATION_LENGTH (1000) was not applied to the effect, and writeXml() always serialized the effect with vibrationToString() even when an equivalent vibration pattern was also written. Per the commit message, serialized vibration effects take much more space than the pattern array, so an app-supplied channel could carry a very large vibration effect into notification channel state that is written to XML.",
     "fix_analysis": [
      "core/java/android/os/VibrationEffect.java: adds abstract @hide cropToLengthOrNull(int length). VibrationEffect.Composed implements it: returns null if mRepeatIndex >= 0 (repeating effects are never cropped), returns this if mSegments.size() <= length, otherwise builds a new Composed from mSegments.subList(0, length) with the same repeat index, calls validate(), and returns null if validation throws IllegalArgumentException.",
      "core/java/android/app/NotificationChannel.java: MAX_VIBRATION_LENGTH lowered from 1000 to 500; new MAX_SERIALIZED_VIBRATION_LENGTH = 32_768.",
      "NotificationChannel.java:getTrimmedVibrationEffect(effect) (new private helper): calls effect.cropToLengthOrNull(MAX_VIBRATION_LENGTH), uses the cropped result if non-null, then returns null if vibrationToString(result).length() exceeds MAX_SERIALIZED_VIBRATION_LENGTH, else returns the (possibly cropped) effect.",
      "NotificationChannel.java:NotificationChannel(Parcel) constructor: after reading mVibrationEffect from the parcel (flag-gated path), passes it through getTrimmedVibrationEffect so oversized effects arriving over Binder are cropped or dropped.",
      "NotificationChannel.java:setVibrationPattern(long[]): if the array is longer than MAX_VIBRATION_LENGTH, stores Arrays.copyOf(vibrationPattern, MAX_VIBRATION_LENGTH); the subsequent flag-gated conversion to mVibrationEffect therefore operates on the truncated pattern.",
      "NotificationChannel.java:setVibrationEffect(effect): when the effect converts to a pattern via computeCreateWaveformOffOnTimingsOrNull() and that pattern exceeds MAX_VIBRATION_LENGTH, delegates to setVibrationPattern(pattern) so both pattern and effect are regenerated from the truncated array; when it does not convert to a pattern, stores getTrimmedVibrationEffect(mVibrationEffect) and sets mVibrationPattern to null; a null effect clears both.",
      "NotificationChannel.java:writeXml: the ATT_VIBRATION_EFFECT attribute is only written when getVibrationPattern() == null, so an effect that has an equivalent pattern is no longer serialized twice. Comment in the XML restore path updated accordingly (restore still calls setVibrationEffect, which now trims).",
      "Tests: NotificationChannelTest.testLongVibrationFields_canWriteToXml checks a 32,775-entry pattern is cut to MAX_VIBRATION_LENGTH through parcel and XML round trips; VibrationEffectTest adds cropToLength_* cases for waveform, amplitude waveform, composed, and repeating effects.",
      "platform/build/release commits 2283b66e8a4d and 3eae801ea3d0: add then remove the android.app.notif_channel_crop_vibration_effects flag value in RELEASE_CONFIG_TRUNKFOOD_STAGING. Net effect is nil; the frameworks/base security backport inlines the flag (per commit message), and the flag file is not present at android-security-15.0.0_r17."
     ],
     "completeness": [
      "All three entry points that populate the vibration fields are covered: Parcel constructor (effect), setVibrationPattern (pattern, and derived effect), setVibrationEffect (effect and derived pattern). The XML restore path reaches the setters and is covered indirectly.",
      "cropToLengthOrNull is implemented only for VibrationEffect.Composed and deliberately returns null for repeating effects; such effects are not cropped. They are still bounded by the MAX_SERIALIZED_VIBRATION_LENGTH check in getTrimmedVibrationEffect, which drops the effect entirely (returns null) if its serialized form exceeds 32,768 characters.",
      "The pattern-length truncation in the Parcel constructor for mVibrationPattern itself is not in this diff; the pre-existing test testLongAlertFields already expected MAX_VIBRATION_LENGTH after unparceling, so that path is handled by code not shown here.",
      "When getTrimmedVibrationEffect drops an effect in setVibrationEffect, mVibrationEnabled was already set to true earlier in the method and is not reset; the diff does not adjust that.",
      "The diff contains no check of the new bugfix flag (inlined for the security backport), so the new truncation is unconditional for the pattern path; the effect paths remain gated by the pre-existing Flags.notificationChannelVibrationEffectApi."
     ],
     "behavior_changes": [
      "Vibration patterns on a NotificationChannel are now truncated to 500 entries (previously the limit constant was 1000, and setVibrationPattern did not truncate).",
      "Non-repeating composed VibrationEffects with more than 500 segments are silently cropped; effects whose serialized form exceeds 32,768 characters (including uncroppable repeating effects) are silently dropped (stored as null) on parcel read and, for non-pattern-convertible effects, in setVibrationEffect.",
      "writeXml no longer emits the vibration_effect attribute when a vibration pattern is present; on restore the effect is regenerated from the pattern via setVibrationPattern.",
      "setVibrationEffect with a pattern-equivalent effect longer than 500 entries now stores a regenerated, truncated effect rather than the original object."
     ],
     "preconditions": [
      "A caller supplies a NotificationChannel with a very long vibration pattern or a large VibrationEffect, e.g. via NotificationManager.createNotificationChannel as used in NotificationTestList.run (per index search), which parcels the channel to the system.",
      "The VibrationEffect paths require Flags.notificationChannelVibrationEffectApi to be enabled (pre-existing flag gating in the Parcel constructor and setters); the pattern truncation in setVibrationPattern applies regardless."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). 5/8 lines of the patched region present at android-security-14.0.0_r29; the material does not show what differs there.",
      "15": "Resolved via frameworks/base a44beb0ca3f7 in android-security-15.0.0_r4 (cherry-pick of 1181fd1b6769, flag inlined for the security backport). At android-security-15.0.0_r17 the constants (500 / 32_768) and cropToLengthOrNull are present; 7/8 lines of the patched region present.",
      "16": "Not affected per the bulletin (not listed). 7/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed). 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Only the frameworks/base commit is required; the two build/release flag commits cancel each other and the security backport already inlines android.app.notif_channel_crop_vibration_effects. The change relies on Flags.notificationChannelVibrationEffectApi and VibrationEffect.Composed existing in the target branch.",
      "cropToLengthOrNull is declared abstract on VibrationEffect; any branch with additional concrete VibrationEffect subclasses beyond Composed must add an implementation (returning null is the documented 'not applicable' result) or the build will fail."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete impact of an oversized persisted vibration effect (e.g. on notification policy persistence in the system server) is not shown in the material; only the bulletin rating (High, EoP) is known.",
      "How mVibrationPattern is bounded when read from the Parcel (that code is outside the diff).",
      "Why Android 14 is not affected and which line of the patched region differs on 14, 16 and 17; the material gives only line counts."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49737": {
     "cve": "CVE-2024-49737",
     "summary": "WindowOrganizerController now builds a SafeActivityOptions from the TaskFragment organizer's actual pid/uid (caller.mPid, caller.mUid) and passes it to ActivityStartController.startActivityInTaskFragment, instead of passing the raw Bundle, which would otherwise yield a SafeActivityOptions built from the cleared Binder identity.",
     "component": "platform/frameworks/base, WindowManager (TaskFragment organizer, WindowOrganizerController, ActivityStartController)",
     "weakness": "Check performed against the wrong caller identity (ActivityOptions associated with the cleared Binder identity instead of the actual organizer uid/pid)",
     "root_cause": "Per the commit message, the calling identity is cleared before the WindowContainerTransaction is applied, but SafeActivityOptions queries the Binder uid when it is constructed. For OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT, the options were forwarded as a plain Bundle to ActivityStartController.startActivityInTaskFragment, so any SafeActivityOptions built from it would carry the cleared identity rather than the organizer's actual caller identity.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowOrganizerController.java (OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT case): after reading operation.getBundle(), the fix calls SafeActivityOptions.fromBundle(activityOptions, caller.mPid, caller.mUid) and passes the resulting safeOptions to startActivityInTaskFragment (inside waitAsyncStart on 15). These are the same caller.mUid/caller.mPid already passed as callingUid/callingPid.",
      "services/core/java/com/android/server/wm/ActivityStartController.java:startActivityInTaskFragment: the activityOptions parameter type changes from @Nullable Bundle to @Nullable SafeActivityOptions, the unused android.os.Bundle import is removed and the javadoc is updated. The diff does not change the method body, so how the downstream code consumes the SafeActivityOptions is not shown.",
      "Per the commit message, the test is WmTests:WindowOrganizerTests#testStartActivityInTaskFragment_checkCallerPermission (test code not included in the material)."
     ],
     "completeness": [
      "Covers the single call site of startActivityInTaskFragment shown in the material (OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT in WindowOrganizerController). Because the Bundle-typed parameter is removed, any other caller would have to supply a SafeActivityOptions.",
      "The fix changes only the identity attached to the ActivityOptions. It does not change the identity clearing around WCT application, and the material does not show other operations in that region.",
      "The body of startActivityInTaskFragment and the code that consumes the options are not in the diff, so the material does not show how the SafeActivityOptions is checked downstream."
     ],
     "behavior_changes": [
      "ActivityOptions supplied by a TaskFragment organizer in a start-activity-in-TaskFragment operation are now associated with the organizer's own pid/uid rather than the cleared Binder identity. The resulting downstream effect (for example which options are now refused) is not shown in the material.",
      "startActivityInTaskFragment signature change (Bundle -> SafeActivityOptions); internal API."
     ],
     "preconditions": [
      "Caller is a TaskFragment organizer that applies a WindowContainerTransaction containing an OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT operation with an ActivityOptions Bundle (operation.getBundle()).",
      "The operation is processed after the Binder calling identity has been cleared (per commit message)."
     ],
     "per_major": {
      "14": "same fix, commit 5eb2aa58018c in android-security-14.0.0_r16; identical change except the call is synchronous (no waitAsyncStart wrapper) and at a different offset (hunk at line 1189); 8/8 patched lines present at android-security-14.0.0_r29",
      "15": "same fix, commit 4b2a235c895c in android-security-15.0.0_r4 (cherry-pick of 20c568e77eae); 8/8 patched lines present at android-security-15.0.0_r17",
      "16": "not affected (not listed by the bulletin); patched region present at android-security-16.0.0_r8 (8/8 lines)",
      "17": "not affected (not listed by the bulletin); patched region present at android-security-17.0.0_r1 (8/8 lines)"
     },
     "backport_notes": [
      "Single self-contained commit. It relies on SafeActivityOptions.fromBundle(Bundle, int pid, int uid) and on the code downstream of startActivityInTaskFragment accepting a SafeActivityOptions; the diff adds neither, so both must already exist in the target branch. On 14 the call is direct rather than through waitAsyncStart.",
      "Android 13 is listed as affected by the bulletin, but the material gives no 13 commit or tag status."
     ],
     "confidence": "medium",
     "unknowns": [
      "The body of startActivityInTaskFragment and how the SafeActivityOptions is consumed or checked downstream are not shown.",
      "Where the SafeActivityOptions was constructed before the fix, and with which identity, is not shown beyond the commit message.",
      "Which specific ActivityOptions fields or checks were affected is not shown.",
      "Status of the Android 13 fix is not in the per-major facts.",
      "The exact location of the identity clearing in WindowOrganizerController is referenced only by the commit message."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-49738": {
     "cve": "CVE-2024-49738",
     "summary": "libbinder Parcel now calls validateReadData() on the target range before writing in Parcel::writeInplace, Parcel::writeObject and Parcel::writeAligned, and refuses the write when validation fails.",
     "component": "platform/frameworks/native, libbinder (libs/binder/Parcel.cpp)",
     "weakness": "missing validation of the write range against parcel state before an in-place write (memory corruption per the commit message)",
     "root_cause": "Three low-level write primitives in libs/binder/Parcel.cpp (writeInplace, writeObject, writeAligned) checked only that mDataPos plus the write size fit within mDataCapacity. writeObject also checked that the object table had room. They then wrote directly into mData+mDataPos without calling validateReadData on the range being written. The commit message says this validation before writing is \"required to prevent memory corruption\". The material does not show what validateReadData checks.",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::writeInplace: after data = mData+mDataPos is computed under the restart_write label, the function calls validateReadData(mDataPos + padded). On failure it returns nullptr and drops the status, as the code comment notes. This happens before the padding mask write and finishWrite(padded). Because the check sits after the restart_write label, the growData()-then-goto path is also covered.",
      "libs/binder/Parcel.cpp:Parcel::writeObject: right after the restart_write label (inside the enoughData && enoughObjects branch), the function calls validateReadData(mDataPos + sizeof(val)) and returns its status on failure. This happens before the flat_binder_object is stored at mData+mDataPos and before the file-descriptor bookkeeping that follows.",
      "libs/binder/Parcel.cpp:Parcel::writeAligned<T>: right after the restart_write label, the function calls validateReadData(mDataPos + sizeof(val)) and returns its status on failure. This happens before the memcpy into mData+mDataPos and finishWrite().",
      "The fix is a single commit that adds 12 lines and touches no other files. The commit message lists the test as 'fuzzer'."
     ],
     "completeness": [
      "The fix covers the three write primitives in Parcel.cpp that store directly into mData+mDataPos: writeInplace, writeObject and writeAligned. Each check sits after its restart_write label, so it runs on both the first attempt and the retry after growing the buffer.",
      "writeInplace drops the validateReadData status and returns only nullptr. The caller shown in the context then returns mError, so the specific validation failure code may not be passed back on that path.",
      "The material shows no change to validateReadData itself. It does not show whether Parcel.cpp has other direct writes into mData.",
      "system/libhwbinder/Parcel.cpp has its own Parcel::writeAligned with the same unvalidated write pattern, and this commit does not touch it (see potential_variants)."
     ],
     "behavior_changes": [
      "Writes whose target range fails validateReadData are now rejected. writeInplace returns nullptr, and writeObject and writeAligned return the validateReadData error status instead of writing.",
      "Each write now costs an extra validation step. The commit message says explicitly 'This is slow'.",
      "No API change: the diff only touches the implementation in Parcel.cpp."
     ],
     "preconditions": [
      "Code must call one of the libbinder Parcel write primitives (writeInplace, writeObject, writeAligned) at a data position where the written range would fail validateReadData. The material does not show the exact triggering state or sequence.",
      "The bulletin rates the issue High EoP. The material does not show which processes or callers can reach it."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/native f3c7aac0e327 in android-security-14.0.0_r16, the same three hunks at different line offsets. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/native 49eb0ea0816e in android-security-15.0.0_r4 (cherry-pick of c54dad65317f). 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin (not listed). The patched code (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed). The patched code (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix is a single self-contained commit. It needs Parcel::validateReadData to exist in the target Parcel.cpp, and each check must sit after the restart_write label so the growData retry path is also validated. The bulletin lists 12, 12L and 13 as affected, but the material has no diff for those branches. Confirm validateReadData exists there before applying."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not include the body of validateReadData, so the precise condition under which a write is now rejected is not known.",
      "The material does not show the concrete trigger that led to memory corruption (which caller or Parcel state). The commit only says validation is required to prevent memory corruption and that a fuzzer was the test.",
      "The material does not show which public Parcel APIs and callers reach the three patched primitives.",
      "The material does not include the fix commits for Android 12, 12L and 13."
     ],
     "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-2024-49742": {
     "cve": "CVE-2024-49742",
     "summary": "NotificationAccessConfirmationActivity now checks, before it shows the grant dialog, that the requesting package has a service that resolves for the NotificationListenerService intent action and that this service is protected by BIND_NOTIFICATION_LISTENER_SERVICE. If either check fails, the activity finishes. The fix also removes the late permission check in onAllow and the onBackPressed suppression.",
     "component": "platform/packages/apps/Settings, notification listener access confirmation (CDM/NLS flow)",
     "weakness": "missing or insufficient validation of the target component before granting notification listener access (missing authorization check)",
     "root_cause": "NotificationAccessConfirmationActivity takes EXTRA_COMPONENT_NAME and EXTRA_USER_ID. When the user allows, it calls mNm.setNotificationListenerAccessGranted(mComponentName, true). Before the fix, the only validation was in onAllow(): it called getServiceInfo(mComponentName, 0) and compared serviceInfo.permission with BIND_NOTIFICATION_LISTENER_SERVICE. Nothing checked that the component declared an intent-filter for android.service.notification.NotificationListenerService (NotificationListenerService.SERVICE_INTERFACE), and the lookup was not done as mUserId. So the dialog could be shown, and access granted, for a component that does not declare the NLS intent-filter. The activity also overrode onBackPressed() to do nothing, so the back button could not dismiss the dialog.",
     "fix_analysis": [
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java:onCreate: after the existing early-return guards, the code builds an Intent for NotificationListenerService.SERVICE_INTERFACE and calls getPackageManager().queryIntentServicesAsUser(NLSIntent, 0, mUserId). It walks the ResolveInfo list for the first entry whose serviceInfo.packageName equals mComponentName.getPackageName(). If that entry's serviceInfo.permission is not BIND_NOTIFICATION_LISTENER_SERVICE, it logs and calls finish(). Otherwise it sets hasNLSIntentFilter and breaks. If no entry matched, it logs 'lacks an intent-filter action' and calls finish(). The AlertController dialog is only built once these checks pass. New imports: Intent, ResolveInfo, NotificationListenerService, List. ServiceInfo is removed.",
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java:onAllow: the old try/getServiceInfo(mComponentName, 0)/permission check is deleted. onAllow now calls mNm.setNotificationListenerAccessGranted(mComponentName, true) and finish() directly and relies on the onCreate validation.",
      "src/com/android/settings/notification/NotificationAccessConfirmationActivity.java: the empty onBackPressed() override is removed, so the default back behavior applies again.",
      "tests/robotests/.../NotificationAccessConfirmationActivityTest.java: start_showsDialog is replaced by start_withMissingIntentFilter_finishes. The new test asserts that, for an installed package with no NLS service, there is no dialog text and activity.isFinishing() is true. The com.android.settings.R import is removed."
     ],
     "completeness": [
      "What the fix covers: the activity will not show the dialog (and so will not grant access) unless the package has at least one service that resolves for the NLS action for mUserId and that service carries BIND_NOTIFICATION_LISTENER_SERVICE. Validation now happens up front instead of at allow time, and it uses the user-aware query.",
      "Gap visible in the code: the match is by serviceInfo.packageName only. The class name of mComponentName is never compared with service.serviceInfo.name. The loop stops at the first matching service of the package, so the specific component passed in EXTRA_COMPONENT_NAME is not itself verified to be the NLS service or to hold the permission. The old per-component getServiceInfo check was removed rather than kept alongside the new check.",
      "The grant call mNm.setNotificationListenerAccessGranted(mComponentName, true) is unchanged. Any server-side validation in NotificationManagerService is outside this diff and not shown in the material."
     ],
     "behavior_changes": [
      "The activity finishes immediately in onCreate, with no dialog, when the package has no service with an NLS intent-filter for the target user, or when that service lacks BIND_NOTIFICATION_LISTENER_SERVICE. Two new Slog.e messages are logged in these cases.",
      "The back button now dismisses the confirmation activity because the onBackPressed suppression is removed.",
      "onAllow no longer does its own permission check. Once the dialog has been shown, allowing grants access without a further check.",
      "The Robolectric test start_showsDialog is replaced by start_withMissingIntentFilter_finishes."
     ],
     "preconditions": [
      "NotificationAccessConfirmationActivity is started with EXTRA_COMPONENT_NAME and EXTRA_USER_ID (NotificationAccessConfirmationActivityContract). The commit title points to the CDM (companion device) notification listener flow.",
      "The earlier guards in onCreate, before the new block, must pass. Their content is not shown in the material.",
      "The user must press allow for access to be granted. The vulnerable path could grant access to a component that did not declare the NLS intent-filter."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/Settings b0eec6fdfd5b in android-security-14.0.0_r16. The diff is identical apart from line offsets differing by one. The patched region is fully present at android-security-14.0.0_r29.",
      "15": "same fix: platform/packages/apps/Settings 334323abd571 in android-security-15.0.0_r4 (cherry-pick of 7ae59a42eb13). The patched region is fully present at android-security-15.0.0_r17.",
      "16": "not affected: the bulletin does not list Android 16. The patched region (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "not affected: the bulletin does not list Android 17. The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix is a single self-contained Settings commit, and the Android 14 and 15 diffs are identical apart from line offsets. Backports need mUserId in the activity, and the existing ServiceInfo-based check in onAllow must be removed. queryIntentServicesAsUser(Intent, int, int) is a hidden PackageManager API that Settings can use (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The fix commits for Android 12, 12L and 13 (listed as affected) are not in the material, and neither is whether the patch applies cleanly there.",
      "The material does not show what launches the activity or the concrete trigger scenario, beyond the '[CDM][NLS]' commit title.",
      "The material does not show whether NotificationManagerService does its own intent-filter or permission validation in setNotificationListenerAccessGranted."
     ],
     "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-2024-49744": {
     "cve": "CVE-2024-49744",
     "summary": "AccountManagerService.checkKeyIntent now sets the authenticator-supplied KEY_INTENT's component to the activity that passed its resolution and signature check, and writes the intent back into the result Bundle. checkKeyIntentParceledCorrectly now rejects the intent if either the original or the parcel-round-tripped copy is not exactly Intent.class.",
     "component": "platform/frameworks/base, services/core AccountManagerService (account authenticator result handling, checkKeyIntent / checkKeyIntentParceledCorrectly)",
     "weakness": "Incomplete validation of an untrusted Intent. The activity validated at check time was not bound to the returned intent, and the Intent-subclass check did not cover the deserialized copy.",
     "root_cause": "In services/core/java/com/android/server/accounts/AccountManagerService.java, checkKeyIntent takes the Intent an authenticator placed under AccountManager.KEY_INTENT and resolves it with pm.resolveActivityAsUser(intent, 0, mAccounts.userId). It then checks that the resolved activity is an exported system activity or has AUTH signature capability with the authenticator. After that it returned true without changing the intent, so the intent in the returned Bundle carried no explicit component tying it to the validated activity. Separately, checkKeyIntentParceledCorrectly re-serializes the Bundle to detect Parcel read/write mismatches. It only checked that the original intent's class was exactly Intent.class and never checked the class of the round-tripped simulateIntent before comparing the two with filterEquals.",
     "fix_analysis": [
      "AccountManagerService.java:checkKeyIntent: once the resolveInfo null check, the \"content\" scheme rejection and the isExportedSystemActivity / hasSignatureCapability(targetUid, authUid, CertCapabilities.AUTH) check have passed, the fix calls intent.setComponent(targetActivityInfo.getComponentName()) so the intent becomes explicit for the validated activity. It then calls bundle.putParcelable(AccountManager.KEY_INTENT, intent) so the Bundle carries the pinned intent.",
      "AccountManagerService.java:checkKeyIntentParceledCorrectly: the fix removes the early check `intent != null && intent.getClass() != Intent.class`. After the `intent == null` handling (which returns simulateIntent == null), a new check returns false if `intent.getClass() != Intent.class || simulateIntent.getClass() != Intent.class`. Both the original and the round-tripped Intent must therefore be the base Intent class before filterEquals runs."
     ],
     "completeness": [
      "Covers the KEY_INTENT path in checkKeyIntent: once the target activity passes the check, the component is set explicitly and stored back into the Bundle.",
      "The resolution itself is unchanged (flags 0, mAccounts.userId). Other intent fields (action, data, extras, flags) stay as the authenticator supplied them. ClipData is still forced to empty when null, and the content scheme is still rejected.",
      "A null KEY_INTENT still makes checkKeyIntent return true (context lines 5148-5150). The fix does not change this.",
      "checkKeyIntentParceledCorrectly now checks both intents' classes, but only when intent is non-null. The new check calls simulateIntent.getClass() without a null check on simulateIntent. The material does not show whether a non-null intent with a null simulateIntent can occur."
     ],
     "behavior_changes": [
      "The KEY_INTENT in the authenticator result Bundle is now explicit, with its component set to the activity AccountManagerService resolved and validated.",
      "A result is now also rejected (checkKeyIntentParceledCorrectly returns false) when the round-tripped KEY_INTENT deserializes to an Intent subclass. The existing rejection of a subclass in the original intent remains.",
      "The subclass check now runs after the null handling instead of before it."
     ],
     "preconditions": [
      "An account authenticator returns a result Bundle containing AccountManager.KEY_INTENT to AccountManagerService, which reaches checkKeyIntent.",
      "The supplied intent must resolve (pm.resolveActivityAsUser) to an activity that passes the exported-system-activity check or the AUTH signature-capability check.",
      "The bulletin rates this as High severity EoP, with affected versions 12, 12L, 13, 14, 15 and 15-next."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/base 985bdc676ac5 in android-security-14.0.0_r16. The hunks are identical at different line offsets (4978/4999). At android-security-14.0.0_r29, 7 of the 8 lines of the patched region are present.",
      "15": "Same fix: platform/frameworks/base 5018e2273cb3 in android-security-15.0.0_r4 (cherry-pick of cde345a7ee06). At android-security-15.0.0_r17, 7 of the 8 lines of the patched region are present; the context shows setComponent/putParcelable at lines 5178-5179.",
      "16": "Not affected per the bulletin (not listed). At android-security-16.0.0_r8, 7 of the 8 lines of the patched region are present.",
      "17": "Not affected per the bulletin (not listed). At android-security-17.0.0_r1, 7 of the 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-file change in AccountManagerService.java, with identical hunks on 14 and 15. The material has no commit for 12/12L/13, although the bulletin lists them as affected. The typed Bundle.getParcelable(String, Class) overload used in the context is an API 33 (Android 13) addition, so 12/12L code would use the untyped getParcelable form (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how the client resolves and launches the returned intent, or how that could diverge from the validated resolution.",
      "Fix commits for Android 12, 12L and 13 are not included in the material.",
      "The material does not show why the per-major check reports only 7 of the 8 patched lines present.",
      "Whether a non-null intent with a null simulateIntent can reach the new class check 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-2024-49745": {
     "cve": "CVE-2024-49745",
     "summary": "Parcel::growData in libbinder now logs an error and returns BAD_VALUE when the data position is beyond the data size, a case the commit says used to abort and where a large allocation could cause OOM.",
     "component": "platform/frameworks/native, libbinder (libs/binder/Parcel.cpp)",
     "weakness": "Missing consistency/bounds check on Parcel state (mDataPos vs mDataSize) before buffer growth",
     "root_cause": "Parcel::growData(len) was only expected to run at the end of a Parcel (per the new ALOGE message), but it did not check mDataPos against mDataSize. After an earlier check that returns BAD_VALUE and the SIZE_MAX overflow checks, it computed newSize = ((mDataSize+len)*3)/2 and continued the write. setDataPosition, in the shown context, accepts any pos <= INT32_MAX without comparing it to mDataSize, so mDataPos can exceed mDataSize. Per the commit message, this case \"used to abort\" and \"allocating this much memory may cause OOM or other issues\".",
     "fix_analysis": [
      "libs/binder/Parcel.cpp:Parcel::growData: after an existing check that returns BAD_VALUE and before the SIZE_MAX overflow checks, the fix adds `if (mDataPos > mDataSize)`. In that state it logs ALOGE (\"growData only expected at the end of a Parcel...\") and returns BAD_VALUE, so the newSize computation is never reached.",
      "The comment in the diff deliberately leaves mDataPos < mDataSize unhandled, reasoning that it only wastes a bit of memory."
     ],
     "completeness": [
      "Covers only Parcel::growData in libs/binder/Parcel.cpp, with a single early return for the mDataPos > mDataSize state.",
      "Does not change setDataPosition, which (per the shown context) still allows mDataPos to be set anywhere up to INT32_MAX regardless of mDataSize. The inconsistent state is still allowed and is only rejected at grow time.",
      "The mDataPos < mDataSize case is intentionally not rejected (comment in the diff).",
      "A separate growData in system/libhwbinder/Parcel.cpp has no mDataPos > mDataSize check (per index search) and is not touched by this commit."
     ],
     "behavior_changes": [
      "Parcel::growData returns BAD_VALUE and emits an ALOGE when mDataPos > mDataSize. Writes that reach growData in that state now fail with BAD_VALUE instead of following the path the commit says used to abort.",
      "No change for the normal path where mDataPos <= mDataSize."
     ],
     "preconditions": [
      "A Parcel whose data position has been moved beyond its data size (setDataPosition in the shown context does not bound pos by mDataSize), followed by an operation that reaches Parcel::growData.",
      "The write entry points that call growData are not shown in the material. The Java/JNI wrappers in android_os_Parcel.cpp (setDataSize, setDataCapacity, unmarshall) operate on the same native Parcel (per index search)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/native 01a1e3492999 in android-security-14.0.0_r16, an identical 8-line hunk at a different line offset (2775). 4/4 lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/native df99a3903097 in android-security-15.0.0_r4. 4/4 lines present at android-security-15.0.0_r17.",
      "16": "not affected (not listed by the bulletin). The patched region is present 4/4 at android-security-16.0.0_r8.",
      "17": "not affected (not listed by the bulletin). Only 2/4 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "A single self-contained hunk in Parcel::growData with no new dependencies. Place the check after the preceding BAD_VALUE-returning check and before the SIZE_MAX overflow checks, as in the 14 and 15 commits."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Parcel write paths call growData, and the exact abort/allocation behavior (continueWrite body), are not in the material.",
      "The full pre-fix libbinder growData body, including the condition of the check that comes before the new one.",
      "Why only 2/4 lines of the patched region match at android-security-17.0.0_r1, and whether an equivalent check exists there.",
      "Whether the libhwbinder copy can be reached with mDataPos > mDataSize."
     ],
     "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-2024-49749": {
     "cve": "CVE-2024-49749",
     "summary": "Replaces an effectively dead overflow guard in DGifSlurp with a working one. Before ImageSize is computed and RasterBits is allocated, the new guard rejects non-positive Width/Height and any Width*Height product above INT_MAX.",
     "component": "platform/external/giflib, GIF decoder (dgif_lib.c, DGifSlurp)",
     "weakness": "Integer overflow in a size calculation, caused by a bounds check written with the wrong logical operators",
     "root_cause": "In dgif_lib.c DGifSlurp, the guard before `ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height` was written as `Width < 0 && Height < 0 && Width > (INT_MAX / Height)`. All three terms were joined with `&&` and the first two required negative dimensions, so the check never fired for positive dimensions. The diff compares the dimensions with 0 and INT_MAX, which suggests they are signed integer values. Their product is computed before being stored into the size_t ImageSize, so large dimensions can overflow the multiplication (the commit message says ImageSize can overflow). The result is then used as the element count in `reallocarray(NULL, ImageSize, sizeof(GifPixelType))` for sp->RasterBits. The old check also did not reject zero or negative dimensions.",
     "fix_analysis": [
      "dgif_lib.c:DGifSlurp: the condition is rewritten as `Width <= 0 || Height <= 0 || Width > (INT_MAX / Height)`. An image descriptor with a non-positive dimension, or whose Width*Height would exceed INT_MAX, now causes `return GIF_ERROR` before ImageSize is computed.",
      "The `Height <= 0` test comes before the division and `||` short-circuits, so `INT_MAX / Height` is only evaluated when Height is positive. This avoids division by zero.",
      "The existing `ImageSize > (SIZE_MAX / sizeof(GifPixelType))` check and the reallocarray call are unchanged. The fix only makes the guard on the multiplication actually take effect."
     ],
     "completeness": [
      "Covers the single ImageSize computation in DGifSlurp: the Width*Height product can no longer exceed INT_MAX, and zero or negative dimensions are rejected.",
      "Only DGifSlurp is changed. The material shows no other Width*Height allocation sites in giflib, and does not show whether similar computations exist elsewhere, for example in callers that allocate their own buffers from ImageDesc.",
      "The commit message mentions SIZE_MAX, but the new guard checks against INT_MAX. That is the correct bound for the int multiplication. The SIZE_MAX check on the sizeof multiplication was already present."
     ],
     "behavior_changes": [
      "DGifSlurp now returns GIF_ERROR for any image frame whose descriptor has Width <= 0 or Height <= 0. Previously such frames went on to allocation.",
      "DGifSlurp now returns GIF_ERROR for frames whose Width*Height exceeds INT_MAX. Previously the check never triggered for positive dimensions.",
      "Per index search, callers such as GIFMovie::GIFMovie in frameworks/base/libs/hwui/jni/GIFMovie.cpp will treat such files as failed decodes (fGIF is set to nullptr)."
     ],
     "preconditions": [
      "A GIF file is decoded through DGifSlurp. Per index search, one such path is GIFMovie::GIFMovie in frameworks/base/libs/hwui/jni/GIFMovie.cpp, which calls DGifOpen and then DGifSlurp.",
      "The file contains an image descriptor record whose Width and Height make the product overflow, or are non-positive."
     ],
     "per_major": {
      "14": "same fix: platform/external/giflib 3281e14f6b9b in android-security-14.0.0_r16, with the same diff as the Android 15 commit. At android-security-14.0.0_r29, 4/6 lines of the patched region are present.",
      "15": "same fix: platform/external/giflib b3aea8d187b1 in android-security-15.0.0_r4 (cherry-pick, Change-Id Ieef04e789acf783eda2dff2cd9284ed204f1d117). The code at android-security-15.0.0_r17 shows the corrected condition.",
      "16": "not affected per the bulletin (Android 16 is not listed). At android-security-16.0.0_r8, 4/6 lines of the patched region are present.",
      "17": "not affected per the bulletin (Android 17 is not listed). At android-security-17.0.0_r1, 4/6 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-hunk, self-contained change in dgif_lib.c DGifSlurp. The same diff was applied to Android 14 and 15, so it is expected to apply to branches that carry the `Width < 0 && Height < 0 &&` form of the check."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show the fix commits for Android 12, 12L and 13, which the bulletin lists as affected.",
      "The material does not show whether the broken check was Android-specific or also present in upstream giflib.",
      "The declared types of ImageDesc.Width/Height and the full read loops that fill RasterBits are not shown. What happens in memory after an undersized allocation is not shown either.",
      "Why only 4/6 lines of the patched region are reported present at the latest tags is not explained."
     ],
     "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."
     }
    }
   }
  },
  "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-01-01",
  "latest_version": "1.1",
  "latest_revision": {
   "version": "1.1",
   "date": "February 6, 2025",
   "notes": "Revised to include CVE links"
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "January 6, 2025",
    "notes": "Bulletin Published"
   },
   {
    "version": "1.1",
    "date": "February 6, 2025",
    "notes": "Revised to include CVE links"
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [],
  "out_of_scope_rows": {
   "Imagination Technologies": 1,
   "MediaTek components": 8,
   "Qualcomm components": 3
  },
  "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."
 }
}