{
 "month": "2026-09",
 "generated": "2026-10-14T01:30+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 59,
    "certain": 57,
    "probable": 0,
    "not_published": 2,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2026-27280",
     "osv_id": "ASB-A-491543354",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Updates external/dng_sdk from DNG SDK 1.7.1 build 2471 to build 2502. In OptimizeOrder (dng_pixel_buffer.cpp), unchecked 32-bit pointer-offset arithmetic is replaced with checked 64-bit arithmetic. dng_render_task::ProcessArea now indexes its temporary RGBTables/mask buffers in source-area coordinates and explicitly requires the src and dst areas to be the same size.",
      "confidence": "medium",
      "backport_notes": [
       "The same commit was applied to 14, 15 and 16. In all three, the pre-fix dng_flags.h shows build 2471 even though README.version said 1.4.0. The new helpers depend on SafeInt64Mult, ThrowOverflow, SafeInt32Sub and ConvertUint32ToInt32 being present in the tree.",
       "Renaming AspectRatio to BaseAspectRatio changes the API. Check for downstream callers before taking the commit verbatim."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "52ff589c0c55941630811c5f1af165ed7c6a63e5",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2026-28590",
     "osv_id": "ASB-A-483126552",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix makes the LE Security Manager (SMP) reject a peer Pairing Request/Response whose Maximum Encryption Key Size is below the 7-octet minimum (and adds the same check on the SMP-over-BR/EDR path), routing all minimum-key-size checks through a new btm_sec_get_min_enc_key_size() accessor.",
      "confidence": "high",
      "backport_notes": [
       "The functional core is the two smp_act.cc hunks plus a linkable btm_sec_get_min_enc_key_size(); the btu_hcif.cc/btm_sec.cc hunks only redirect the existing BR/EDR check. Locate read_encryption_key_size_complete_after_* in btu_hcif.cc (14) or btm_sec.cc (15/16) and remove the local MIN_KEY_SIZE, since the new header constant has the same name.",
       "Each branch's fix adds a btm_sec_get_min_enc_key_size() stub to system/test/mock/mock_stack_btm_sec.cc (plus the mockcify struct in the .h on 15 and 16); test binaries linking against the btm_sec mock likely need it."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "3475f39c4c160ff9c5fd275ddbef1d72bfa18164",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28604",
     "osv_id": "ASB-A-499053500",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport pointer. The thread now captures a weak-pointer callback id plus the transport's connection object, and re-enters the fdevent looper to check that the transport is still alive before touching transport state. The fix also splits FdConnection::DoTlsHandshake into host and daemon variants and adds a 3-second socket timeout to the daemon handshake.",
      "confidence": "high",
      "backport_notes": [
       "Apply the hardening change (Ic419a1fb: fdevent_check_looper and extract in the transport_to_callback_arg/transport_from_callback_arg helpers) before the UAF fix (I8ed271ab). The fix uses these helpers and fdevent_run_on_looper. On Android 14, the auth.cpp diff of the UAF fix starts from the blob produced by the hardening commit.",
       "The resolver reports a 7/8 line match on every security tag. Check that adbd_auth_tls_handshake in the target tree matches the post-fix shape, rather than relying on the line count."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/adb",
       "sha": "c7995e05cc7b82f15548b879bf6e22173edcd40a",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/adb",
       "sha": "e96f6790c785fdb7034fbded89c28859b7fede91",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/modules/adb"
     ]
    },
    {
     "cve": "CVE-2026-28639",
     "osv_id": "ASB-A-508389703",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (MIFARE Classic NDEF read handling), the else branch that logs SafetyNet ID 178725766 for an NDEF length inflation attempt now also sets failed = true, so the read operation is marked as failed instead of continuing. Per the commit message, this prevents a heap out-of-bounds write.",
      "confidence": "medium",
      "backport_notes": [
       "Find the 'else { android_errorWriteLog(0x534e4554, \"178725766\"); }' branch in rw_mfc_handle_read_op and add 'failed = true;'. The code excerpt shows another 178725766 check that already sets failed = true, so check which branch the target tree has before patching. On 16/17 the file is in platform/packages/modules/Nfc under libnfc-nci/."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "e884267aee53224ccbf18416466a69f5b7620b4e",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-28653",
     "osv_id": "ASB-A-508389055",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a range check before the loop in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (src/nfc/tags/rw_t3t.cc). The first block number plus the block count can no longer go past the 16-bit range, where the commit says the uint16 block_id loop counter could wrap and cause an unbounded heap out-of-bounds write.",
      "confidence": "medium",
      "backport_notes": [
       "Consider the form shown at the 14 security tag instead of the raw diff: `(uint32_t)first_block + count > 0x10000`, GKI_freebuf(p_cmd_buf) before `return NFC_STATUS_FAILED`, and a (uint32_t) cast in the loop bound. The raw diff does not free the GKI buffer on the error path.",
       "On Android 16+ the file is in platform/packages/modules/Nfc at libnfc-nci/src/nfc/tags/rw_t3t.cc; on 14/15 it is platform/system/nfc src/nfc/tags/rw_t3t.cc."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "bea12a1b3e256c563fd6fc7071bb65cfe93f2469",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-33636",
     "osv_id": "ASB-A-496616027",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restricts the NEON palette-expansion loops in external/libpng arm/palette_neon_intrinsics.c to full chunks only (loop condition i + pixels_per_chunk <= row_width), drops the post-loop count correction, and undoes the *ddp pre-adjustment before handing the remaining pixels to the scalar fallback.",
      "confidence": "high",
      "backport_notes": [
       "This is a single commit touching one file. 14, 15 and 16 share pre-image blob 92c7d6f9f. On 17 the file differs only by a one-line offset at the top.",
       "The handoff change assumes the scalar fallback in pngrtran.c consumes *ssp/*ddp as returned. Check the pngrtran.c caller in the target tree before backporting."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libpng",
       "sha": "843457e59be71bd24f4a77a9c04645a381a5decf",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libpng"
     ]
    },
    {
     "cve": "CVE-2026-45515",
     "osv_id": "ASB-A-503554165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Passes the correct frame_size (the number of samples per channel the output buffer can hold, A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t))) to both opus_decode() calls in a2dp_vendor_opus_decoder_decode_packet instead of the buffer size in bytes. It also adds a channel-count check, removes the per-frame re-decode loop and makes frame_len use the configured stereo output.",
      "confidence": "high",
      "backport_notes": [
       "A single self-contained commit that touches one function. The only branch-specific adaptation is the logging API (LOG_ERROR/LOG_INFO on 14, log::error/log::info on 15+). It requires A2DP_OPUS_CODEC_OUTPUT_CHS and A2DP_OPUS_DECODE_BUFFER_LENGTH to be defined. The new unit test calls the libopus encoder API (opus_encoder_create/opus_encode)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "4f7ed05b16604309856a8d1dadd0c15207262de8",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-45531",
     "osv_id": "ASB-A-508645975",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens fsck.exfat boot-sector validation in read_boot_region() against integer overflow and underflow from the on-disk vol_length, clu_offset and clu_count fields, and makes the shared DIV_ROUND_UP macro compute in 64-bit.",
      "confidence": "medium",
      "backport_notes": [
       "The functional fix is self-contained in fsck/fsck.c:read_boot_region() and include/libexfat.h. The build commit (host_supported) is independent and is only needed to build host variants for the tests. On branches where read_boot_region() guards exfat_err with 'if (verbose)' (as on 16), use the 16 version of the hunks.",
       "Keep the sector-size validation (EXFAT_SECTOR_SIZE in 512..4KB) ahead of the new 'bd->size >> bs->bsx.sect_size_bits' expression."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/exfatprogs",
       "sha": "20f47ac2509f45bf6c6456b19bb0c2bbbba7676a",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/external/exfatprogs",
       "sha": "54cf500d4abd9b941e51cf7558efbfa0da398c1c",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/external/exfatprogs"
     ]
    },
    {
     "cve": "CVE-2026-49879",
     "osv_id": "ASB-A-503544522",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Widens block_id to uint32_t and recomputes the block-range bounds checks and loop limits in uint32_t in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (Type 3 Tag NDEF update/check), and frees the GKI command buffer when the bounds check fails.",
      "confidence": "medium",
      "backport_notes": [
       "The hunks assume the earlier guard (`count > UINT16_MAX - first` with android_errorWriteLog 0x534e4554/\"508389055\") is already in both functions. On a tree without it, add the check, GKI_freebuf(p_cmd_buf) and the return together, not just the type change. On 16 and later the file is libnfc-nci/src/nfc/tags/rw_t3t.cc in packages/modules/Nfc.",
       "No fork comparison was provided, so no per-file fork verdict can be given."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "c1fa4599a13ec64e01c3bf72df9f3779523c69aa",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-49882",
     "osv_id": "ASB-A-503553866",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (Mifare Classic reader in the NFC stack), the fix saves ndef_length and ndef_start_pos just before rw_nfc_decodeTlv on the first block read. It then treats a decode failure, or any growth of ndef_length, as a failure and restores both fields, so an inflated length no longer stays in the control block where it bounds the copy into p_ndef_buffer.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in rw_mfc_handle_read_op. Across versions only two things differ: the file path (src/nfc/tags/rw_mfc.cc in platform/system/nfc on 14/15, libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc on 16/17) and the debug log macro (DLOG_IF vs LOG(VERBOSE)). Trees without the earlier saved_length/else-branch mitigation will need the hunk applied by hand."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "7eaf8e16d9adf34f47bd712b0c03adb786563723",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-49884",
     "osv_id": "ASB-A-503557185",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (Mifare Classic NDEF read), the fix snapshots p_mfc->ndef_length and p_mfc->ndef_start_pos before rw_nfc_decodeTlv, treats an inflated ndef_length as a failure, and rolls both fields back to their pre-decode values. This keeps the inflated length out of the control block.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk in rw_mfc_handle_read_op. The only per-branch difference is the logging macro (DLOG_IF on 14, LOG(VERBOSE) on 15+). On 16+ the file is under libnfc-nci/ in packages/modules/Nfc instead of system/nfc, so the patch path must be adjusted."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "7eaf8e16d9adf34f47bd712b0c03adb786563723",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-49919",
     "osv_id": "ASB-A-503556301",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces an unchecked `size = rows * pitch; FT_ALLOC(buf, size)` in tt_face_colr_blend_layer (src/sfnt/ttcolr.c) with FT_ALLOC_MULT(buf, rows, pitch), so the FreeType memory layer detects the multiplication overflow instead of the code allocating an undersized buffer.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained change with no dependencies. It applies as long as the tree has the `size = rows * pitch; FT_ALLOC(buf, size)` pattern in tt_face_colr_blend_layer and provides FT_ALLOC_MULT; Google applied the same hunk on all four branches. Check whether the local `size` variable becomes unused, which matters if unused-variable warnings are treated as errors (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "ab9caf444825ac18db8d3f2d31b48c9e7590c53f",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-49921",
     "osv_id": "ASB-A-503554490",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the fixed 400-byte (SDP_MAX_ATTR_LEN) heap buffer in sdpu_build_partial_attrib_entry with an allocation sized to the serialized attribute. It also rejects attributes whose payload exceeds SDP_MAX_ATTR_LEN in that helper, in both SDP server request handlers and at database insertion, and adds an offset-vs-length check before the partial memcpy.",
      "confidence": "high",
      "backport_notes": [
       "Each branch has a single commit with no dependency chain. For forks based on 14, the hunk placement differs from 15+. On 14 the server-side check sits after attr_len is computed and the old partial-branch checks are deleted. On 15+ the check sits directly under 'if (p_attr)'. Either placement guards both the continuation and first-partial calls into sdpu_build_partial_attrib_entry.",
       "The sdp_utils.cc change is self-contained and protects the helper regardless of its callers. On 14 the accompanying stack_sdp_utils_test.cc change also maps the osi_malloc/osi_free mocks to malloc/free in the fixture."
      ],
      "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_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "51b0cc33b16c19963b1922a3d438ff656ddd6449",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-49932",
     "osv_id": "ASB-A-513581684",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens PduParser.parseParts in frameworks/base telephony/common so malformed MMS multipart PDUs are rejected (parse returns null) instead of causing OutOfMemoryError, StackOverflowError, NullPointerException or ArrayIndexOutOfBoundsException. It rejects a declared part dataLength larger than the bytes left in the stream, caps recursion into multipart/vnd.wap.multipart.alternative parts at depth 30, and checks that the nested result is non-null and non-empty before taking its first part.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The PduParser.java hunks are identical across 14/15/16/17 and only touch the constants block and parseParts. The protected parseParts(ByteArrayInputStream) signature is preserved.",
       "The new PduParserTest.java needs the tests/TelephonyCommonTests module in the target tree. Vendor trees that carry their own PduParser copy (for example a Messaging mmslib copy, per index search) need to be checked and patched separately."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8325de4e11e96d466efe5fa6efe139cfd6119f09",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55256",
     "osv_id": "ASB-A-513581389",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds null checks in PduParser.parsePartHeaders so that a null Content-Disposition token-text value, a null filename parameter, or a null custom text header name or value is no longer passed on or dereferenced, which prevents NullPointerExceptions while parsing an MMS PDU.",
      "confidence": "high",
      "backport_notes": [
       "The two commits are in different repos and carry the same hunks; neither depends on the other. The pre-fix blob of telephony/common PduParser.java (ecab9562f72e) is the same on 14, 15 and 16, so the patch should apply cleanly to trees that have that version of the file.",
       "No 'Fix in fork' section was provided, so no per-file fork verdict can be reported."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/development",
       "sha": "6aa9341a76952d9fb6b66e723a5a7664dac08b89",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "bcba9a47c23e10f6e5cf7d39c114a4aea04cbe0c",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/development",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48564",
     "osv_id": "ASB-A-403565650",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that single string to both the IPackageManager.canForwardTo check and a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload, so the type used for the cross-profile check is the same as the type used for target resolution; the frameworks/base copy also uses getLaunchedFromPackage() for the privileged skip-consent check, and both copies sanitize the intent selector on the resolver path.",
      "confidence": "medium",
      "backport_notes": [
       "Order matters: land the frameworks/base commit (new PackageManager/ApplicationPackageManager resolveActivityAsUser(Intent, String, ...) overloads) before the IntentResolver module commit, whose InjectorImpl calls the new overload. Update every caller of canForward (CrossProfileIntentsChecker / AbstractMultiProfilePagerAdapter) in the same change, because its signature changes.",
       "Per index search, the IntentResolver module canForward in public android-14.0.0_r75 has a different shape from the one the r24 security diff patches: it checks a single intentToCheck (the selector if present, otherwise the root intent) and resolves the type from intentToCheck, instead of the two-call canForwardInner form. A backport onto that shape needs manual adaptation so that one resolvedType feeds both canForwardTo and the later resolveActivityAsUser."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "042b52a8551fa84a0e17e9ad8db2fcf30002569a",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "000c3577880ac9b39169b46d842da24cdf6fbe85",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "2fadc52d7f1845e3e68f9f1a2015c9d353b9c243",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "96057a4e7e4000f8b2e6a59d1d064d376083ef86",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "0ed9b52b9d32630b5d34175ac80b6974c43051ad",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48565",
     "osv_id": "ASB-A-407763772",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the intent MIME type once and passes that same string to both the IPackageManager.canForwardTo cross-profile check and the target-user activity resolution (via a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload), uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-user-consent check, and (after a follow-up on 14) sanitizes intent selectors (package, component, nested selector) in every sanitizeIntent call.",
      "confidence": "high",
      "backport_notes": [
       "Apply in order: 042b52a8551f (new PackageManager/ApplicationPackageManager overloads and Injector overload; TestInjector and any PackageManager subclasses must implement them), then 2fadc52d7f18 (changes Injector), then 96057a4e7e40 whose diff base is the result of 2fadc52d7f18.",
       "packages/modules/IntentResolver has its own IntentForwarderActivity.canForward that still takes a ContentResolver and resolves the type from the intent (per index search); it is not covered by these frameworks/base commits."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "042b52a8551fa84a0e17e9ad8db2fcf30002569a",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "2fadc52d7f1845e3e68f9f1a2015c9d353b9c243",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "96057a4e7e4000f8b2e6a59d1d064d376083ef86",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "0ed9b52b9d32630b5d34175ac80b6974c43051ad",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48566",
     "osv_id": "ASB-A-397216638",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the received intent's MIME type once and passes that same string both to the IPackageManager.canForwardTo check and to a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload. It also sanitizes the received intent's selector and uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-consent check.",
      "confidence": "high",
      "backport_notes": [
       "Apply the main commit (042b52a8551f, Change-Id I8453f25ed5ddd938718f4d2be4983c881778f281) first. The test follow-up (Change-Id Iade781e0758c477ca888bd1541e0c53e2dee3761) builds on its test changes and corrects its inverted assertions. Any downstream Injector implementation must add the new resolveActivityAsUser(Intent, String, int, int) method. A PackageManager subclass that does not override the ResolveInfoFlags variant will throw UnsupportedOperationException if that variant is called.",
       "canForward's parameter changed from ContentResolver to String resolvedType. The diff updates AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker. Any other callers, including vendor code, need the same change."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r24",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "042b52a8551fa84a0e17e9ad8db2fcf30002569a",
       "tag": "android-security-14.0.0_r24",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "2fadc52d7f1845e3e68f9f1a2015c9d353b9c243",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2026-0010",
     "osv_id": "ASB-A-379695596",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In BnDrmManagerService::onTransact, the fix replaces a fixed 16-byte stack buffer that sprintf(\"%lu\") filled with the file descriptor value by a dynamically sized String8::format(\"%d\", fd). This removes a possible stack buffer overflow when the descriptor value is negative.",
      "confidence": "medium",
      "backport_notes": [
       "Single-file, single-hunk change with no dependencies shown. The pre-image (char buffer[16]; sprintf \"%lu\"; String8(buffer)) is the same in the 14, 15 and 16 diffs. String8::format is a long-standing libutils API (general platform knowledge). The material includes no fork comparison."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r25",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "8bf16f1ae904b860f13be25ad7f65673c14444c1",
       "tag": "android-security-14.0.0_r25",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 183,
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-0054",
     "osv_id": "ASB-A-467684755",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an onTransact-level caller check to SystemUI's WalletContextualLocationsService binder so that only privileged apps holding the android.app.role.SYSTEM_UI_INTELLIGENCE role can invoke IWalletContextualLocationsService methods; all other callers get a SecurityException.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The only source difference across 14/15/16 is the formatting around the binder stub, so apply the onTransact override, isCallerAllowed(), the four imports and the ROLE_SYSTEM_UI_INTELLIGENCE constant to whatever stub layout the target branch has.",
       "The check calls RoleManager.getRoleHolders and ApplicationInfo.isPrivilegedApp from SystemUI; SystemUI is a privileged system app (general platform knowledge). If no package holds the SYSTEM_UI_INTELLIGENCE role on the target build, the role holder list is empty and the code rejects every caller."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r28",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r28",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a83edee34b0d15f0c1ca5523c340b79b8c07c3e3",
       "tag": "android-security-14.0.0_r28",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 92,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0065",
     "osv_id": "ASB-A-464526458",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The BAL \"bound by foreground UID\" exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned (non-PiP) window for activity starts (FGS starts keep the old any-visible-window predicate), and on Android 14/15 additionally requires appSwitchState != APP_SWITCH_DISALLOW unconditionally.",
      "confidence": "high",
      "backport_notes": [
       "The fix depends on VisibleActivityProcessTracker.hasVisibleNotPinnedActivity(int) existing in the target tree. The diff calls it but does not add it, so check that it is present before cherry-picking.",
       "Adjust the surrounding condition to the branch's shape: on 14 add the appSwitchState check, on 15 drop the Flags gating, on 16 keep checkConfiguration. Any other code that constructs BackgroundLaunchProcessController (e.g. wmtests) must pass the extra predicate."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r28",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r28",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "407d7b0830109236c9558ac3d9ca55e5fc729ff4",
       "tag": "android-security-14.0.0_r28",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 92,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28581",
     "osv_id": "ASB-A-479515555",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Telecom's CallIntentProcessor.fixInitiatingUserIfNecessary now decides whether a managed (work) profile has a dialer by asking RoleManager for a ROLE_DIALER holder in that user and verifying with PackageManager that the holder package exists and is enabled, instead of checking whether any activity in the profile resolves ACTION_DIAL via DefaultDialerManager.getInstalledDialerApplications.",
      "confidence": "high",
      "backport_notes": [
       "Apply the two commits in order. The first introduces doesUserHaveDialerRoleHolder and switches fixInitiatingUserIfNecessary to it. The second replaces the size()>0 return with the null/empty check plus the getApplicationInfo(...).enabled check, and depends on the first commit's method and RoleManager import.",
       "On 15 and 16 the surrounding function signature differs (FeatureFlags parameter, 3-argument UserUtil.isManagedProfile, context.getSystemService(UserManager.class)). Only the noDialerInstalled line and the new helper need to change."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r28",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "13272bca51ee23c6608ea0d738c608a9eec7cce5",
       "tag": "android-security-14.0.0_r28",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "48d04bd3344164aca79e53db7dd3a93bb0289c5e",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 92,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/services/Telecomm"
     ]
    },
    {
     "cve": "CVE-2026-28582",
     "osv_id": "ASB-A-487522353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ConfirmDeviceCredentialActivity now finishes immediately when it receives KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through any component other than the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity, so the remote-validation flow can no longer be entered by targeting the underlying activity class directly.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in onCreate, placed right after the 'remoteValidation' boolean. It uses ComponentName and Log; the Log import is visible in the file context, but check the ComponentName import on the target branch. Before applying, confirm that the target branch's Settings manifest declares the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity under that exact name. Otherwise legitimate remote-validation callers would be finished.",
       "The Robolectric test added on 15+ depends on the ShadowLockPatternUtils.getCredentialTypeForUser shadow from the same commit. The Android 14 commit omits both."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "4b69be85e55de1dcebba56f39eb8dabd208459c4",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28583",
     "osv_id": "ASB-A-486597445",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Corrects validate_camera_metadata_structure in system/media camera_metadata.c so that entry_capacity is converted from an entry count to bytes (times sizeof(camera_metadata_buffer_entry_t)), with an overflow check, before verifying that the entry region ends at or before data_start.",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching one file (camera/src/camera_metadata.c). The hunks are identical across 14, 15 and 16, so they are expected to apply to trees that still have the original entries_start + entry_capacity check."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/media",
       "sha": "a9dc2a964e84510b96edfaa9bfce985d460c1413",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/media"
     ]
    },
    {
     "cve": "CVE-2026-28593",
     "osv_id": "ASB-A-388272624",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Live TV app's \"Send Feedback\" settings action now builds an explicit intent targeting com.google.android.feedback/com.google.android.feedback.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent, so the intent can no longer resolve to an arbitrary app that registers for that action.",
      "confidence": "high",
      "backport_notes": [
       "Single-file change with an identical pre-image (762a190c) on all four branches. It should apply cleanly wherever SettingsFragment.java still builds `new Intent(Intent.ACTION_APP_ERROR)` in the send-feedback ActionItem. If a fork ships a different feedback app, point FEEDBACK_PACKAGE/FEEDBACK_CLASS at that trusted target rather than going back to an implicit intent."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "2e9c27acedc2d01dc9a4da35bfe9d2ff24169074",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28596",
     "osv_id": "ASB-A-480975165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds OutOfMemoryError to the catch clause around the game-mode-config XML meta-data parsing in GameManagerService, so an oversized package XML resource no longer crashes the process; the service falls back to the default game mode flags instead.",
      "confidence": "high",
      "backport_notes": [
       "Trivial single-hunk backport: in GameManagerService.java's meta-data parsing method, find `catch (NameNotFoundException | XmlPullParserException | IOException ex)` and add `| OutOfMemoryError`. The hunk applied unchanged across 14, 15 and 16 with only line-number drift."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "12567c8df8f3aab3dcaab2e9a13056b10d30d8bc",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28602",
     "osv_id": "ASB-A-466379087",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ClipboardService now reads and writes the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS secure setting for the explicit userId (getIntForUser/putIntForUser) instead of getInt/putInt without a user argument, so the clipboard-access notification decision uses the setting of the given user.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; both hunks rely on an existing in-scope userId variable. Where the notification function was refactored (as in Android 17), apply the getInt -> getIntForUser(..., userId) substitution in the function that performs the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS check."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6f90eac7f9e52631d8894eb5987b986d5f8e8db3",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28603",
     "osv_id": "ASB-A-491253353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the vetted copy of the app-supplied custom restrictions activity Intent now has all four FLAG_GRANT_*_URI_PERMISSION flags removed and its data and clipData cleared before Settings calls startActivityForResult on it.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk inside assertSafeToStartCustomActivity. It applies anywhere that method exists with the setComponent/setPackage lines. The EventLog tag 223578534 at the top of the method suggests the method came from an earlier fix. The robolectric test depends on ReflectionHelpers and on the RestrictionsResultReceiver(String, AppRestrictionsPreference, boolean) constructor; drop or adapt the test if the test infrastructure differs."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "0f9f6238cf25959db289a9c2e5bb21b8cd8c64f1",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28609",
     "osv_id": "ASB-A-485377744",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "announced-not-published",
     "detail": "platform/frameworks/av",
     "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": [],
      "projects": [
       {
        "project": "platform/frameworks/av",
        "fetched_ok_in_tags": 30,
        "ingested_tags": 30,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-28613",
     "osv_id": "ASB-A-329233015",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ChannelImpl.initAppLinkTypeAndIntent in the Live TV app now refuses to build a channel app-link Intent whose resolved target activity is exported and declares an android:permission. This is in addition to the existing rejection of activities inside the TV app's own package.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, single-hunk Java change with the same pre-image (5be1179d) on all four branches, so a straight cherry-pick should apply. The only context dependency is the existing isProtectedActivity / CommonConstants.BASE_PACKAGE check just above it. If a fork lacks that check, add the isPermissionProtected check right before the mAppLinkIntent = intent assignment."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "e64e4b90fc776b8e5dd3b9aa60d863a4a292c87f",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28614",
     "osv_id": "ASB-A-486385459",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a caller check to SystemUI's SlicePermissionActivity.onCreate so the slice permission dialog is only shown when the activity was actually launched by the slice provider's package, SystemUI itself, or the \"android\" package; any other launcher is finished immediately.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the Java hunk applies to any tree where onCreate still calls verifyCallingPkg() right after resolving mProviderPkg. The fix relies on Activity.getLaunchedFromPackage(); SystemUI is platform code and can use this API even where it is not public SDK, but confirm it exists and returns the launcher package on the target branch (general platform knowledge).",
       "The new Kotlin test uses reflection on Activity internals (mActivityInfo, mFragments, mApplication, mWindow) and OnBackInvokedDispatcher; it may need adjustment on forks with customized Activity or SysuiTestCase, and can be dropped without affecting the fix."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c6b5038a9e234a4792dfae5d96a1111be77cb56c",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28626",
     "osv_id": "ASB-A-332883816",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a launching-package identity check (getLaunchedFromPackage() on SDK 34+) to SetupPassthroughActivity so that the embedded setup Intent is only forwarded when the activity was launched by CommonConstants.BASE_PACKAGE; otherwise the activity finishes without forwarding.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix with an identical diff on 14 to 17 (same pre/post blob hashes), so it should apply cleanly to any tree where SetupPassthroughActivity.java matches blob 2a4a556f. Activity.getLaunchedFromPackage() is public API only from API level 34 (general platform knowledge), which matches the SDK_INT >= 34 guard. On a branch compiled against an older SDK the call would not compile."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "738d2cc1f03e5349aab6cf1f63b6e1f185835a5c",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 25c98c910675",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28631",
     "osv_id": "ASB-A-461500706",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds getWindow().setHideOverlayWindows(true) to the mini resolver consent dialog that IntentForwarderActivity shows before redirecting an intent into the managed profile, so overlay windows are hidden over the consent UI. A companion commit moves the executor and device-provisioned lookups behind the Injector so the activity test can run.",
      "confidence": "medium",
      "backport_notes": [
       "Apply cb471de52128 first. It is the security change and stands alone: one line after setContentView(R.layout.miniresolver). Find the spot by that call, because the enclosing method differs between 14 and 15. Then apply 2fadc52d7f18 if you want the test. Its HIDE_OVERLAY_WINDOWS manifest permission is needed, or the test crashes once the overlay call exists.",
       "On 16 and 17 the test commit also edits core/tests/coretests/Android.bp to remove the IntentForwarderActivityTest exclude filter. The 14 commit has no such hunk, so expect that file to differ when porting across majors."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2fadc52d7f1845e3e68f9f1a2015c9d353b9c243",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "cb471de521286bd2dedb32fec986783bda7ec611",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28633",
     "osv_id": "ASB-A-430284594",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In safe mode, VoiceInteractionManagerService no longer parses the recognition service metadata of third-party (non-FLAG_SYSTEM) recognizers in RecognitionServiceInfo.getAvailableServices, nor the metadata of the already-configured current recognizer, to prevent a malicious large metadata file from causing boot issues (OOM) in safe mode.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the RecognitionServiceInfo.java hunks are identical across 14 through 17 (blob 05d4b9305d30 -> 376ba571f790), and only the VoiceInteractionManagerService.java line offsets differ. The fix relies on the existing mSafeMode field in VoiceInteractionManagerService; any extra downstream callers of getAvailableServices must pass the new parameter."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "20ecd72dd4c83f4fd964ae4dbd2e4e85d572e0f0",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28634",
     "osv_id": "ASB-A-471796626",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a check to PhoneInterfaceManager.handleUssdRequest that the calling user is associated with the subscription. A caller that passes enforceCallPermission can no longer send USSD requests on a subscription that is not associated with its user.",
      "confidence": "high",
      "backport_notes": [
       "The production change is 5 self-contained lines. The target tree must have TelephonyPermissions.checkSubscriptionAssociatedWithUser(Context, int, UserHandle) and mApp. If the target's handleUssdRequest contains enforceTelephonyFeatureWithException, place the check after it, as the 15/16/17 commits do. Per index search, android-14.0.0_r75 has that call; android-security-14.0.0_r29 does not.",
       "The test changes depend on TelephonyTestBase.replaceInstance and the SubscriptionManagerService singleton. They vary by branch and can be adapted separately from the production fix."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "2a177c06ec4824062c47e130536332d0bc036358",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/services/Telephony"
     ]
    },
    {
     "cve": "CVE-2026-28636",
     "osv_id": "ASB-A-484148949",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "announced-not-published",
     "detail": "platform/packages/apps/DocumentsUI",
     "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": [],
      "projects": [
       {
        "project": "platform/packages/apps/DocumentsUI",
        "fetched_ok_in_tags": 30,
        "ingested_tags": 30,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2026-28638",
     "osv_id": "ASB-A-470260357",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider's XMP redaction for ISO (MP4-style) media is extended to redact EXIF GPS fields stored as XML attributes (not only as element names) and, on Android 14, to process every XMP box in the container rather than only the first one.",
      "confidence": "high",
      "backport_notes": [
       "The 14 patch targets the older XmpInterface (parsing in the constructor, fromContainer calling iso.getBoxBytes). On 15+ parsing lives in XmpDataParser and IsoInterface.mFlattened is type-filtered, so the attribute check belongs in isSensitiveTagOrAttribute()-style logic there. BOX_XMP must also be in the retained-box switch, or plain XMP boxes are never seen."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "15168c2e6f84ee7430db08c9cae2f89be8f3a471",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-28642",
     "osv_id": "ASB-A-460778875",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In ActivityStarter, when the Background Activity Launch (BAL) check returns a blocking verdict, the fix clears the mLaunchTaskBehind flag on the newly built ActivityRecord so a blocked background start does not perform the launch-behind animation that would make the activity visible.",
      "confidence": "high",
      "backport_notes": [
       "Single-hunk change inserted right after the ActivityRecord.Builder(...).build() call and before `mLastStartActivityRecord = r`. On 14 the BAL result is `balCode` compared to `BAL_BLOCK`; on 15/16 it is `balVerdict` with `.blocks()`. Pick the form matching the BAL API in the target tree; ActivityStarter already uses Slog and TAG.",
       "The commit carries two Cherrypick-From references (34bbada3964e, 95f41dbd68ab) and Change-Id Iab9bd9fc6ebe7291d2eeec7f697cb877240d1c9e; no fork comparison was provided."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d94f8b27bc84deb51ae665ab138273121a825027",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28644",
     "osv_id": "ASB-A-471797575",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ActivityTaskManagerService.startNextMatchingActivity() now forwards the original caller's identity (launchedFromUid/launchedFromPackage/launchedFromFeatureId) to the next resolved activity only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID; otherwise the calling UID and package are reset to the intermediary's own.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. The only version-specific difference is whether the ActivityStarter chain includes .setUserId(userId) (present on 15/16, absent on 14). When backporting, keep setUserId in the chain before the check and move only the three setCalling* calls under the new condition. The test changes depend on WindowTestsBase.ActivityBuilder.setIntent(), which the same commit adds."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bb74132474550c7e497ecd8a906544c08561b59c",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28650",
     "osv_id": "ASB-A-493714520",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowState.setHiddenWhileSuspended now checks the window's base type (getBaseType()) instead of its own mAttrs.type. As a result, sub-windows whose top parent is a system alert window or toast of a suspended app are hidden, and later shown again, like their parent.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, two-line functional change, no dependencies. getBaseType() and isSystemAlertWindowType are already used in WindowState on the shown branches. Only the test helper differs: createWindow(parent, type, name) on 14/15 versus newWindowBuilder(name, type).setParent(parent).build() on 16/17."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ce479a9a57bd9471e7b476056dbfebf3481c90ed",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28655",
     "osv_id": "ASB-A-477278752",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks RemoteViews parceling so that a single ApplicationInfoCache (typed list) is written once by the root RemoteViews and nested RemoteViews reference it by int id, and removes the Parcel.allowSquashing()/restoreAllowSquashing() wrapping of RemoteViews.writeToParcel together with the ALTERNATIVE_DEFAULT ReadWriteHelper trick that forced immediate (non-lazy) Bundle unparceling in ReflectionAction.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the Revert^2 ApplicationInfoCache commit first, then the allowSquashing removal: the second diff's pre-image blob (f0e6c3e09518) is the first diff's post-image, and the second commit's message assumes mApplicationInfoCache already exists. Check whether the target branch still contains the earlier revert 532a0a90b30a or the original change before cherry-picking.",
       "Adjust for branch-specific context in the RemoteViews(Parcel) constructor and writeToParcel (mCollectionCache / intentsToIgnore on 15+, mValue and mHasDrawInstructions on 16+, mOriginalDensity on 17); the wire-format change requires reader and writer to agree."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "aa6e28b4fd2d8dfea7f15792c12e71529bdb6f8b",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "ae484e3d7a2091c9ed502072ea383fff55e5e9ff",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28656",
     "osv_id": "ASB-A-463364901",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the AppOps user-restriction based overlay suppression (OP_SYSTEM_ALERT_WINDOW / OP_TOAST_WINDOW toggled on show/hide or resume/pause) with the window-level Window#setHideOverlayWindows(true) / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS flag. The change covers the autofill UI (FillUi, SaveUi, DialogFillUi) and the DeviceAdminAdd screens in Settings, Car Settings and TvSettings.",
      "confidence": "high",
      "backport_notes": [
       "This fix spans several projects (frameworks/base, Settings, Car/Settings, TvSettings), all under bug 463364901. The material shows no ordering dependency between the commits. Window#setHideOverlayWindows requires the HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission (general platform knowledge), so check that each app you patch holds it."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c441211a26c95b5a02a8f9675a10075ce8562c51",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "ccb13e0864d40c695bb661abdca35383447d0da5",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "e115f7703748205c25c71cca3af744b6d1e94e86",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "0f3428291e25e29621d53751d9dbc6eaaef6ed18",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/Car/Settings",
      "platform/packages/apps/Settings",
      "platform/packages/apps/TvSettings",
      "platform/vendor/google_clockwork"
     ]
    },
    {
     "cve": "CVE-2026-28658",
     "osv_id": "ASB-A-507575871",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountsDb.findMetaAuthUid now strips the fixed \"auth_uid_for_type:\" prefix from the meta_key by length instead of splitting on \":\" and taking the second token, so authenticator types containing a colon are no longer truncated when the type-to-UID map is built.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching one file; it applies cleanly to any tree that still has META_KEY_DELIMITER and the TextUtils.split(...)[1] pattern in findMetaAuthUid. If a fork renamed the prefix constant, keep META_KEY_PREFIX_LEN derived from the actual stored prefix string."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "051431afaec5aaa8e0a0e21fcec6a0c04358d3fa",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28664",
     "osv_id": "ASB-A-489675413",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In platform/art, RuntimeImage::WriteImageToDisk now calls fchmod() to set the newly created runtime-generated app image (.art) file to 0444 right after creation, and fails image generation if that call fails.",
      "confidence": "medium",
      "backport_notes": [
       "One self-contained hunk plus one include, applied the same way on 14 through 17. Place the fchmod after the file is created and before image->FillData(), so the generation window is covered. ART is a mainline module (com.android.art), so devices can also receive ART fixes through module updates (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/art",
       "sha": "f8f7936106447a7cfbfa6cc0b580ca50dd0fa8cd",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2026-28668",
     "osv_id": "ASB-A-501541998",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that when a growing realloc is rejected by the Android allocation limit the original pointer is left untouched instead of being freed while the caller still holds it.",
      "confidence": "high",
      "backport_notes": [
       "Small change in LimitRealloc (two lines removed, one comment added); the hunk context is identical on 14 through 17 with only line offsets differing. The accompanying test change verifies the original pointer stays valid."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/bionic",
       "sha": "cd1be6e8bfb8e0ea88c2640ab35a89d12d280ec6",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/bionic"
     ]
    },
    {
     "cve": "CVE-2026-28671",
     "osv_id": "ASB-A-395640294",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider.updateInternal's file-movement path now checks, before Os.rename, whether a database row already claims the destination _data path. If no file backs that row it deletes the row; if a file exists it enforces write permission on the row. The SQLiteConstraintException handler in updateAllowingReplace now deletes a conflicting row at the active rename destination and retries the update in the same transaction.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit and adds no imports. The target tree must therefore already have queryForSingleItem, enforceCallingPermission(Uri, Bundle, boolean), LocalCallingIdentity clear/restore and DatabaseHelper.runWithTransaction, plus java.util.Objects and java.io.FileNotFoundException imported in MediaProvider.java. Any other caller of updateAllowingReplace must be updated for the new renameAfterPath parameter. Index search found none, but verify this.",
       "MediaProvider ships as a Mainline module (com.android.mediaprovider APEX), so devices may receive this via module update rather than the platform security tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "e27ea39d718435851addcb4a620b952f21e2e013",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45518",
     "osv_id": "ASB-A-393377017",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an OPTION_CHECK_BRACKETS mode to android.database.sqlite.SQLiteTokenizer that rejects SQL fragments with unbalanced parentheses, and makes ContactsProvider's SelectionBuilder.addClause() validate every clause with it so a caller-supplied selection cannot close the parentheses that SelectionBuilder wraps around each clause.",
      "confidence": "high",
      "backport_notes": [
       "Two-repo dependency: the frameworks/base SQLiteTokenizer change (new OPTION_CHECK_BRACKETS constant, @Nullable checker, and the `&=` to `&` correction) must land before or together with the ContactsProvider SelectionBuilder change, which references both the constant and the null-checker form; do not drop the `&=` fix, since without it the bracket option is cleared and the check is ineffective. SQLiteTokenizer lives in the core framework jar rather than a mainline module, so both pieces ship as platform updates (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "9183bfe6574b24fd7876a14b89fc9cad0d1d15f9",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "3f606a02d7e63272a34ea58af5906c8bcac5e2c1",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/providers/ContactsProvider"
     ]
    },
    {
     "cve": "CVE-2026-45520",
     "osv_id": "ASB-A-490286986",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricsSettingsBase now honors the EXTRA_USER_ID and gatekeeper password handle (EXTRA_KEY_GK_PW_HANDLE) intent extras only when the hosting SettingsActivity was launched by the Settings package itself. Otherwise it uses the current user and ignores the handle, so the device credential confirmation flow is enforced.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with no ordering dependency. The diff uses SettingsActivity.getInitialCallingPackage() and the ShadowPasswordUtils test shadow but does not add them, so the tree must already have both.",
       "Make sure android.app.Activity and android.text.TextUtils are imported in BiometricsSettingsBase.java. The 14 commit adds the Activity import; the 15 to 17 commits do not."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "a88ca4587208a069720a3e4d9ee7e73d5a425147",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-45521",
     "osv_id": "ASB-A-491748298",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid() and AppFuseBridge.openFile rejects the request unless the calling UID equals the uid recorded in the AppFuse MountScope for the requested mountId, preventing cross-app access to another app's AppFuse proxy file descriptors.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the AppFuseBridge.java hunk is identical across 14 through 17 (same blob ids 292314840efb -> d839b30ddf44) and the StorageManagerService hunk only shifts by line number, so it should apply where openProxyFileDescriptor still calls mAppFuseBridge.openFile(mountId, fileId, mode). Update any additional callers of AppFuseBridge.openFile for the new uid parameter."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "0d2ee8e16afcb7b2e9adc71df57aa7665040eb65",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45525",
     "osv_id": "ASB-A-470265726",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes MediaProvider's ISO/MP4 parser keep parsing when an XMP box is too large to load. It also makes the XMP redaction logic redact the whole XMP byte range, instead of nothing, when the XMP payload could not be loaded, so location metadata is no longer left unredacted for oversized XMP boxes.",
      "confidence": "medium",
      "backport_notes": [
       "Pick the variant that matches the tree. Trees with XmpDataParser and IsoInterface.allocateBuffer should take the 15+ patch. Trees where XmpInterface does its own parsing (14 style) need the 14 patch, and must check that fromContainer passes a null buffer together with the box ranges when the box data was skipped; otherwise the whole-range branch never runs.",
       "MediaProvider is a Mainline module (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "93cf0194cff299d1f5f518ccb884b17fd51c15fd",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45528",
     "osv_id": "ASB-A-504573577",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp with the target package name and appInfo.uid instead of calling PendingIntent.getActivity on a package context created inside system_server with the calling identity cleared.",
      "confidence": "high",
      "backport_notes": [
       "The new code requires ActivityManagerInternal.getPendingIntentActivityAsApp(int, Intent, int, Bundle, String, int) to be available in the target branch and imports for ActivityManagerInternal and LocalServices in StorageManagerService.java (no import changes appear in the diff); the material does not show that method's definition, so verify it exists before backporting. The 16/17 variant of the removed block differs only in the createPackageContextAsUser call."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "51b647175642fac5f967f7b886170d352eae2015",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45529",
     "osv_id": "ASB-A-418776187",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsSmsProvider.query() now restricts the simple-conversations, conversation recipients/subject, search and searchSuggest URI paths to messages whose sub_id belongs to the calling user's subscriptions, returning an empty cursor when the user has no associated subscriptions.",
      "confidence": "high",
      "backport_notes": [
       "The change depends on selectionBySubIds already being computed in MmsSmsProvider.query() (from SubscriptionManager.getSubscriptionInfoListAssociatedWithUser, per the test mock) and on the emptyCursor local; trees lacking that prior logic need it first.",
       "For forks with extra getTextSearchQuery parameters (as in Android 17) add selectionBySubIds as the last parameter and insert the table-qualified clauses before the words.table_to_use conditions."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "475853ddf6b38b0993e5a12f125083ba5641924e",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2026-49895",
     "osv_id": "ASB-A-503551046",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Raises the EHT Operation element minimum-length constant in src/common/ieee802_11_defs.h from 1 to 5 so the length check used by get_eht_operation_channel_width() accounts for the fixed-size fields that precede the EHT Operation Information field.",
      "confidence": "high",
      "backport_notes": [
       "This is a single constant change. On older trees, check the macro name (EHT_OPERATION_IE_MIN_LEN vs EHT_OPERATION_ELEM_MIN_LEN) and confirm that struct ieee80211_eht_operation contains basic_eht_mcs_nss_set[4]: per the commit message, the fixed fields before oper_info are 5 octets because of that field. wpa_supplicant_8 is not a mainline module, so the fix ships with the platform image (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "0ce1024749979e41fc205922884f74fa43e35bfa",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/wpa_supplicant_8"
     ]
    },
    {
     "cve": "CVE-2026-49913",
     "osv_id": "ASB-A-505452999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now compares the mmap() result for the client-supplied source SharedBuffer against MAP_FAILED instead of NULL, and installs a ScopeGuard that munmap()s the mapping on every exit path after the check.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The hunk is the same on all four majors and should apply wherever cas/aidl/default/DescramblerImpl.cpp has the `srcPtr == NULL` check. It needs android-base/scopeguard.h, so check that the fork's module build depends on libbase."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/interfaces",
       "sha": "8858a5521ba4079710a1d904363e7b11d87749b6",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/interfaces"
     ]
    },
    {
     "cve": "CVE-2026-55290",
     "osv_id": "ASB-A-499091487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ResStringPool::setTo in libandroidfw now checks stringsStart and stylesStart against the chunk size unconditionally. It also checks that the style-offset array (styleCount * 4 bytes) fits inside the data buffer and ends before stringsStart. This closes an out-of-bounds heap read in ResStringPool::styleAt.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained in ResStringPool::setTo and was applied verbatim on 14 through 17. A backport has to do three things:\n- add the early stringsStart/stylesStart checks;\n- replace the old start-only `(int)size` check with the new extent and overlap checks;\n- remove the two later checks that the new ones replace.\nThe new tests build mock headers by hand with util::HostToDevice32/16, htodl and offsetof."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1eb9fc572c8670f1554bd7152470c5101d1f1de0",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55294",
     "osv_id": "ASB-A-505000651",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In libhevc's ihevcd_get_tu_data_size(), the tu_map part of the TU data size is now budgeted with num_tu (luma plus chroma TUs) instead of num_luma_tu, so the size matches the space the initialization logic consumes.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained one-line change with no visible dependencies. The hunk and its context lines are identical on all four branches, though the file blobs differ. It should apply to any libhevc tree that still has 'num_luma_tu * sizeof(UWORD8)' for the tu_map term."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libhevc",
       "sha": "d9977ef8294ac3f4932f5a6d2b186cb84c9e21a3",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libhevc"
     ]
    },
    {
     "cve": "CVE-2026-58822",
     "osv_id": "ASB-A-506109174",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds early size guards to FreeType's LCD and LCD-vertical smooth rasterizer entry points (ft_smooth_raster_lcd, ft_smooth_raster_lcdv) so bitmaps whose width/3 or rows/3 exceed 0x7FFF are rejected with Raster_Overflow instead of producing truncated 16-bit span coordinates that drive an out-of-bounds write.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit with no dependencies; apply the two checks at the top of ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c (only line offsets differ between branches). Note the added comment text contains a non-ASCII character; strip it if the target tree enforces ASCII sources."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "51c2994abfc55f3dc460380806733870a44e8b3a",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-58839",
     "osv_id": "ASB-A-466091304",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Shrinks the stack-allocated read buffer in the static forEachLine() helper of incfs/MountRegistry.cpp from 128 KiB to 32 KiB, adds a compile-time maxLineLength template parameter with a static_assert against the buffer size, and marks the buffer __attribute__((uninitialized)).",
      "confidence": "high",
      "backport_notes": [
       "The diff is identical across 14, 15 and 16 (same pre/post blobs 376eaf2..eaf97dc), so the single commit is expected to apply onto trees where forEachLine in incfs/MountRegistry.cpp still has `static constexpr auto kBufSize = 128 * 1024;`.",
       "`__attribute__((uninitialized))` is a clang attribute that works with the clang used for Android platform builds (general platform knowledge). Removing it would not change the stack-size reduction."
      ],
      "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_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/incremental_delivery",
       "sha": "b6d06e7ccfc2c776619f119e0ff313477c01dfb3",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/incremental_delivery"
     ]
    },
    {
     "cve": "CVE-2026-58874",
     "osv_id": "ASB-A-459460951",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds the missing subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony). When the calling user is not associated with the subscription, the send is rejected with RESULT_USER_NOT_ALLOWED. The fix also makes the destAddr parameter of TelephonyPermissions.checkSubscriptionAssociatedWithUser nullable (frameworks/base) so the new callers can pass a null address.",
      "confidence": "high",
      "backport_notes": [
       "Apply the frameworks/base TelephonyPermissions change together with the opt/telephony change, because the new gate can pass a null destAddr and the base commit is what adds the explicit null guard.",
       "On trees where SmsController already declares callingUser locals (as in Android 16), follow the b77a21c16113 shape to avoid duplicate declarations. The SmsController_fixed_methods.txt file in the 16 commit is not needed."
      ],
      "meta": {
       "generated": "2026-10-03",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r29",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2e8edff2ab3404a421859b9f017d3a3eaace8cbf",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "2c10fa519945e9690ba56cbdc2e08aa58a698934",
       "tag": "android-security-14.0.0_r29",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/frameworks/opt/telephony"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r29"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "503553199"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "8cf7d2e9937bc46ceef2a257e62dadd9cbdd7ef1",
       "subject": "Fix race conditions in CryptoHal plugin usage",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "503541238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "099e8f68899c0951903d0f2278e47fc59bc91b58",
       "subject": "Fix MediaBuffer size-inflation off-by-32 bug",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "505827492"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "d708d1ed113033cc43422c128f790d16fb4c45ab",
       "subject": "Fix OOB in DynamicsProcessing MBC band processing",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "396471524",
      "482172329"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "7a05282afe4230ba4c6d2a162d2ea2237d778c3c",
       "subject": "[media] Fix heap-buffer-overflow in AudioAttributes unmarshalling",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "485020640"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "db3b431dd8aa0ba006499d20fe3cd607c40ac9ad",
       "subject": "[media] Fix heap out-of-bounds write in MatroskaSource::read",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "525133623"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e7028d348ee54410e9900fdf13f782d421b9bd82",
       "subject": "[udc-staging] Mitigate BAL bypass via Companion Device Manager",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "472955222"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "129f80c33f1f0e414f23523c1a44d410e439d60f",
       "subject": "Sanitize labels in GrantCredentialsPermissionActivity",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "492093013"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "fdeb1aefefd59806c68704b072fe60862058c55d",
       "subject": "Truncate long device admin descriptions",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "cb28e38fe13797263822c2d3c4257992959ba0c4",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "778a381887cacb12d3e2f142fd125ac0a41adb04",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "014727fb26a698de7b13dbc555903520d12112be",
       "subject": "Use loadDescriptionSafe for DeviceAdmin descriptions.",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "429417238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1453b34c0ea53e44b15e0955c04b667674c7f2b4",
       "subject": "Limit knownActivityEmbeddingCerts in manifest",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "505067993"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "dc19b329d86514f52662b1f7e7b46147fcf258e7",
       "subject": "Use byte count rather than string length for NMS component name check",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "472968114"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e5d1fb2c34cea1400b8954fdafebd131fa3ae7b8",
       "subject": "SystemUi UsbDialog: fix label vulnerability",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "495409704"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2a2f6ec579543076c6383743409d02ac9cb650a6",
       "subject": "Fix path traversal and missing ownership check in LocaleManagerService",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "490083213"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a042adf885380cc6a0278320b4b74d7cf3b08e65",
       "subject": "Add Task-level input sink to block cross-task tap pass-through",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      },
      {
       "project": "platform/frameworks/base",
       "sha": "7599b48d00eea824cc0cdd28d51be07047fae6b2",
       "subject": "Move input sink on top of the Activity when needed",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "498857374"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b2142726cb61fa1440ade04384c55897dfde3160",
       "subject": "Check notification permission in getAuthToken(notifyOnAuthFailure).",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "451602444"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "01bf19b34e45c3cd374c75d19fac119bb5ea2385",
       "subject": "Add size validation and trimming for ShortcutInfo objects.",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "478725173"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8dd2f9d4de6474d638d7c32ff83c3ffaa3dd3992",
       "subject": "Hide media on smartspace view when setting is disabled",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "501447778"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a4ecf872409762a53ebf67018fc25f3f686b76ed",
       "subject": "[LocalImageResolver] Fix security check bypass",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "bulletin_history": [
      {
       "bulletin": "2026-09",
       "cve": "CVE-2026-28666",
       "severity": "Critical",
       "versions": [
        "14",
        "15",
        "16",
        "16-qpr2",
        "17"
       ],
       "status": "removed",
       "removed_seen": "2026-10-13",
       "removed_in_version": "1.3"
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "465446148"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3519be0f11c7464d9e84f22680739174d00d1b6e",
       "subject": "Autofill SaveUi URL validation and FLAG_IMMUTABLE",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "382064697"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "59a2f1fa1a7c7a5b83d8696799339cad73a32cff",
       "subject": "CVE-2025-22442: set profile user restrictions earlier",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "463362836"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "58f827bf9397d571ad046e25418b3d4cb66ac046",
       "subject": "RESTRICT AUTOMERGE [ExternalStorageProvider] Revoke URI permissions by path",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "fb453d9cb06200457ce34d59f675c11dfe1e183c",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions for specific document paths",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "277a42f6430fb6ba1a84ff0a5183a5850a84838d",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions on file path updates",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "262884935"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a8279722faf7077d7f0965df0eb7e544fc18cc1f",
       "subject": "Fix URI grant persistence bypass",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "475232460"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2cd960f154ec9032480c59adf006010c9a9b99df",
       "subject": "RESTRICT AUTOMERGE Filter ignorable Unicode codepoints in ExternalStorageProvider",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "517377278"
     ],
     "commits": [
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "8ae36cfb0cf4aae8b0150e8ff49bb96e68c9f323",
       "subject": "Fix ArrayIndexOutOfBoundsException in SIMRecords due to invalid EF_CFIS/EF_CFF",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "394083030"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "03a0f940149e03b0d1566facc4768ed17fbaac93",
       "subject": "Check getLaunchedFromPackage for CE Storage",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "482024712"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/ManagedProvisioning",
       "sha": "65cfe16ed60d52b0d11edbf517492210547506b5",
       "subject": "Prevent Setup Wizard from starting provisioning if the device is already provisioned.",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "471797085"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "a94d23c25954963779c72b00ac881870826b3bb9",
       "subject": "Fix confused deputy in Bluetooth settings dashboard",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "488559440"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "a101a6cf443aa62d78b50d0cdc0dd8e4f2349805",
       "subject": "Sanitize package labels in SettingsApplication",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "465140520"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "f2741f1c70f81d2e5f61de30dc6147a794e98237",
       "subject": "GATT: Fix notifications sent to remote device without security checks",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "497102083",
      "497851840",
      "510038786"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "e3dba33c968219424fe46410cdf5872fd995e892",
       "subject": "[RESTRICT AUTOMERGE] Enforce incoming CTKD security requirements",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "413510838",
      "415689442"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "1ac33292ff3bcaa14ff0a5f4797f80295cab5cc4",
       "subject": "Handle concurrency issues in SDP record creation",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "444443479"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "d07a326e6a028c7f878c55448d0db366278c2c38",
       "subject": "Sanitize alternate intents.",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "486763102"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "1ba2557aea2f9fe028b1530df1717f7b2336c87b",
       "subject": "Restrict max size for Note.NOTE field",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "482947423"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "8c7db1173431c91b66485039e6dfb8ddf38011cb",
       "subject": "RESTRICT AUTOMERGE Fix ZWSP path bypass in DownloadProvider",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "498162425"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "8397bd246e60592915e8542bc14fa488041482a2",
       "subject": "RESTRICT AUTOMERGE Fix DownloadProvider completed download security bypass",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "490768196"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "c9b9ee7acf7feee4bc8d6b0a08ec042366b35ec2",
       "subject": "RESTRICT AUTOMERGE Fix path traversal vulnerability in DownloadStorageProvider",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "494725584"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "e0b82b04365d1e02be3e1ee096f837f62b148fd9",
       "subject": "TelephonyProvider: Fix SQL injection in projection and sortOrder",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "503542266"
     ],
     "commits": [
      {
       "project": "platform/packages/services/BuiltInPrintService",
       "sha": "2ec9f87450f598fa97efcd455d19254c40a68af9",
       "subject": "Limit media-supported values",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "504612754"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "4ac0b6201cc08296dce8448637fa6c2aa9886a74",
       "subject": "Check caller in CSW#queryRemoteConnectionServices",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "508585261"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "12f9b433c1ae28565b154bcdfda71ded191d948d",
       "subject": "Fix MMI check bypass via leading whitespace in tel URI.",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "356604577"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "9639413a5dccd25a5e971a14ed962ace336f78fe",
       "subject": "Resolve cross account user ringtone validation.",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "490916793"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "787ad217f9a9e8321bc1342f54341376591df775",
       "subject": "[Telephony] Secure contact URI access in Call Forwarding",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    },
    {
     "bug_ids": [
      "477022787"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "d24a3491f06ffa09fb4f761413a23d94a9ad2d24",
       "subject": "Prevent SDK Sandbox from bypassing isSystemApp check",
       "tag": "android-security-14.0.0_r29",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-14.0.0_r29"
     ],
     "tag": "android-security-14.0.0_r29"
    }
   ],
   "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-2026-27280": {
     "cve": "CVE-2026-27280",
     "summary": "Updates external/dng_sdk from DNG SDK 1.7.1 build 2471 to build 2502. In OptimizeOrder (dng_pixel_buffer.cpp), unchecked 32-bit pointer-offset arithmetic is replaced with checked 64-bit arithmetic. dng_render_task::ProcessArea now indexes its temporary RGBTables/mask buffers in source-area coordinates and explicitly requires the src and dst areas to be the same size.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK), pixel buffer reordering and DNG render pipeline",
     "weakness": "Integer overflow / incorrect pointer arithmetic leading to out-of-bounds memory access (OptimizeOrder); mismatched coordinate space when indexing pixel buffers (ProcessArea)",
     "root_cause": "In source/dng_pixel_buffer.cpp OptimizeOrder, the source and destination extents (sRange/dRange) were computed in uint32. The pointer rebase for negative steps was computed as (int32)(count - 1) * step * (int32)pixelSize in 32-bit signed arithmetic, so large counts, steps or pixel sizes could overflow and move sPtr/dPtr to the wrong address. A count of 0 made (count - 1) wrap, and negating a step equal to INT32_MIN was not checked. In source/dng_render.cpp dng_render_task::ProcessArea, the RGBTables mask and background-mask code indexed the temporary buffers with dstArea.t/dstArea.l and dstArea.W(), while the row loop and the per-row RGBTables buffer use srcArea. Nothing required srcBuffer.fArea and dstBuffer.fArea to be the same size. The material does not say which of these bundled changes is the one tracked as CVE-2026-27280.",
     "fix_analysis": [
      "source/dng_pixel_buffer.cpp:OptimizeOrder: returns early if count0, count1 or count2 is 0, which prevents the (count - 1) wrap. sRange and dRange are now computed in uint64, with explicit casts of Abs_int32(step) and (count - 1).",
      "source/dng_pixel_buffer.cpp adds three static helpers. ComputeRebaseDelta returns an int64 from SafeInt64Mult of (count - 1), step and pixelSize. OffsetPointer applies an int64 delta to const and non-const pointers. NegateStepChecked calls ThrowOverflow if step == INT32_MIN. All six rebase blocks (the dStep0/1/2 and sStep0/1/2 branches) now use these helpers instead of the inline int32 multiplication and raw negation. <climits> is included for INT32_MIN.",
      "source/dng_render.cpp:dng_render_task::ProcessArea: adds DNG_REQUIRE (srcArea.Size () == dstArea.Size (), \"area size mismatch\"). It introduces cols = srcArea.W() and colsSigned = ConvertUint32ToInt32(cols) and uses them throughout. buffer.fPlaneStep now uses ConvertUint32ToInt32 instead of a raw int32 cast. The two tempMaskBuffer DirtyPixel calls and the background-mask row loop (row range, ConstPixel_real32, DirtyPixel_real32, column count) switch from dstArea to srcArea coordinates. The per-row RGBTables buffer rect is renamed srcRowArea. The ProfileGainTableMap selection and the exposureWeightGain computation move out of the row loop; they do not depend on the row.",
      "source/dng_point.h adds a unary operator- for dng_point, using SafeInt32Sub(0, v/h), and one for dng_point_real64. In source/dng_big_table.cpp, dng_image_table::PutCompressedStream now uses -fImage->Bounds().TL() instead of dng_point(0,0) - TL().",
      "The remaining changes are not security-related. dng_camera_profile.cpp calls ClearFingerprint() in SetFourColorBayer, SetProfileGainTableMap and both SetMaskedRGBTables overloads. dng_negative::AspectRatio is renamed BaseAspectRatio, and its callers in dng_negative.cpp ConvertToProxy and dng_render.cpp Render are updated. dng_show_message_f gets a printf format attribute on Mac/iPhone builds. The build version strings go to 2502, and README.version changes from 1.4.0 to 1.7.1."
     ],
     "completeness": [
      "OptimizeOrder: all six negative-step rebase sites (dimensions 0 to 2, in both the dRange >= sRange branch and the else branch) now use checked 64-bit arithmetic. Both range sums are widened. A zero count is handled by an early return, not by a throw.",
      "ProcessArea: within the diff hunks, the dstArea-based indexing of the temporary RGBTables/mask buffers is replaced with srcArea. dstArea.l remains in the final write into dstBuffer (DirtyPixel_real32 (..., dstArea.l, dstPlane)), and the new DNG_REQUIRE enforces equal area sizes for that translation. The rest of the function is not shown.",
      "In ProcessArea, buffer.fRowStep = SafeInt32Mult (buffer.fPlaneStep, int32 (buffer.fPlanes)) still uses a raw int32 cast of fPlanes (fNumTableTransformPlanes), while the cast of fArea.W() next to it was changed to ConvertUint32ToInt32.",
      "The commit is a vendor SDK rebase (2471 -> 2502). The material has no changelog that isolates the CVE-specific change, so the other bundled fixes cannot be judged one by one."
     ],
     "behavior_changes": [
      "OptimizeOrder now throws (ThrowOverflow) when a step equals INT32_MIN. When any count is 0, it returns without reordering. Overflow in the 64-bit rebase delta is handled by SafeInt64Mult, whose behaviour is not shown in the material.",
      "dng_render_task::ProcessArea now fails a DNG_REQUIRE when the source and destination tile areas differ in size.",
      "Negating a dng_point with the new unary operator- goes through SafeInt32Sub(0, ...) for each component.",
      "Camera profile fingerprints are cleared when SetFourColorBayer, SetProfileGainTableMap or SetMaskedRGBTables is called.",
      "The ProfileGainTableMap lookup now runs once per ProcessArea call instead of once per row.",
      "dng_negative::AspectRatio is renamed BaseAspectRatio, so any out-of-tree caller using the old name will fail to compile."
     ],
     "preconditions": [
      "A DNG image is rendered through the DNG SDK (dng_render_task::ProcessArea), or pixel buffers are processed by code that calls OptimizeOrder with negative steps.",
      "The changed ProcessArea mask code sits in the fRGBTablesData (masked RGB tables) path.",
      "Per index search, frameworks/base/core/jni/android_hardware_camera2_DngCreator.cpp (DngCreator_setup, DngCreator_nativeWriteInputStream) is platform code related to DNG handling. Those functions use TiffWriter and the index match may be noisy, so the material does not establish which Android caller reaches the affected paths."
     ],
     "per_major": {
      "14": "same fix: platform/external/dng_sdk 52ff589c0c55 'Update to DNG SDK 1.7.1 2502' in android-security-14.0.0_r29 (Change-Id I98050384600e0f4ac77f4378e00eee9be4830aa7); 6/8 lines of the patched region detected at that tag.",
      "15": "same fix: identical diff as a23ee6aeb0ee in android-security-15.0.0_r17; 6/8 lines of the patched region detected.",
      "16": "same fix: identical diff as c4ffcdb3285d in android-security-16.0.0_r8; 6/8 lines of the patched region detected.",
      "17": "not affected per the bulletin, which does not list 17. 6/8 lines of the patched region are detected at android-security-17.0.0_r1. The material does not show which SDK build 17 carries."
     },
     "backport_notes": [
      "The same commit was applied to 14, 15 and 16. In all three, the pre-fix dng_flags.h shows build 2471 even though README.version said 1.4.0. The new helpers depend on SafeInt64Mult, ThrowOverflow, SafeInt32Sub and ConvertUint32ToInt32 being present in the tree.",
      "Renaming AspectRatio to BaseAspectRatio changes the API. Check for downstream callers before taking the commit verbatim."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which change in the 2471 -> 2502 rebase is the one assigned CVE-2026-27280 (OptimizeOrder arithmetic, ProcessArea coordinate handling, or dng_point negation).",
      "Why the bulletin rates this Critical EoP. The material gives no crash details and does not show which Android process hosts the vulnerable code path.",
      "How buffer.fArea is defined in the RGBTables block of ProcessArea, which determines whether and how far the old dstArea-based indexing could go out of bounds.",
      "How SafeInt64Mult and the single-argument ConvertUint32ToInt32 behave on overflow; their implementations are not in the material.",
      "Why only 6/8 lines of the patched region are detected at each tag."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28590": {
     "cve": "CVE-2026-28590",
     "summary": "The fix makes the LE Security Manager (SMP) reject a peer Pairing Request/Response whose Maximum Encryption Key Size is below the 7-octet minimum (and adds the same check on the SMP-over-BR/EDR path), routing all minimum-key-size checks through a new btm_sec_get_min_enc_key_size() accessor.",
     "component": "platform/packages/modules/Bluetooth, system/stack/smp (SMP pairing) and system/stack/btm / system/stack/btu (encryption key size enforcement)",
     "weakness": "Missing validation of a peer-supplied security parameter (encryption key size), allowing negotiation of an encryption key shorter than the specification minimum",
     "root_cause": "In system/stack/smp/smp_act.cc, smp_proc_pair_cmd() parsed the peer's Maximum Encryption Key Size into p_cb->peer_enc_size but only failed pairing with SMP_ENC_KEY_SIZE when p_cb->cert_failure was set to SMP_ENC_KEY_SIZE; it did not compare peer_enc_size against the 7-octet minimum. smp_br_process_pairing_command() (SMP over BR/EDR) had no key-size check before smp_command_has_invalid_parameters(). The 7-octet minimum (file-local MIN_KEY_SIZE in btu_hcif.cc on Android 14) was enforced on BR/EDR via the HCI Read Encryption Key Size completion handlers, and per the commit message the minimum was not enforced for LE.",
     "fix_analysis": [
      "system/stack/btm/btm_sec.h: adds constexpr MIN_KEY_SIZE = 7, MIN_KEY_SIZE_DEFAULT = MIN_KEY_SIZE, MAX_KEY_SIZE = 16 and declares uint8_t btm_sec_get_min_enc_key_size(). Also changes the btm_proc_sp_req_evt declaration to take const uint8_t* (declaration-only, no functional effect shown).",
      "system/stack/btm/btm_sec.cc: new btm_sec_get_min_enc_key_size() returns a static min_key_size initialised to MIN_KEY_SIZE_DEFAULT (7) on Android 14; no sysprop is consulted on this branch.",
      "system/stack/smp/smp_act.cc:smp_proc_pair_cmd: after reading the six pairing fields, fetches min_key_size = btm_sec_get_min_enc_key_size() and, if p_cb->cert_failure == SMP_ENC_KEY_SIZE OR p_cb->peer_enc_size < min_key_size, logs a warning and raises SMP_AUTH_CMPL_EVT with status SMP_ENC_KEY_SIZE, returning before smp_command_has_invalid_parameters() and before any IO-cap/pairing continuation. This function handles the SMP pairing request/response from the peer.",
      "system/stack/smp/smp_act.cc:smp_br_process_pairing_command: adds the equivalent check for SMP over BR/EDR, raising SMP_BR_AUTH_CMPL_EVT with status SMP_ENC_KEY_SIZE when peer_enc_size < min_key_size.",
      "system/stack/btu/btu_hcif.cc: removes the file-local constexpr MIN_KEY_SIZE and makes read_encryption_key_size_complete_after_encryption_change() and read_encryption_key_size_complete_after_key_refresh() compare against btm_sec_get_min_enc_key_size(); includes stack/btm/btm_sec.h. Threshold of the BR/EDR check is unchanged (7 on this branch).",
      "system/gd/hci/controller.cc: cosmetic only; kMinEncryptionKeySize is copied into a local before building SetMinEncryptionKeySizeBuilder, no functional change.",
      "system/test/mock/mock_stack_btm_sec.cc: adds a stub btm_sec_get_min_enc_key_size() returning MIN_KEY_SIZE_DEFAULT."
     ],
     "completeness": [
      "Covers peer Pairing Request and Pairing Response on LE because smp_proc_pair_cmd processes the pairing request/response from the peer, and covers the SMP-over-BR/EDR pairing command path (smp_br_process_pairing_command).",
      "The existing BR/EDR HCI key-size checks are preserved and now share the same minimum via the accessor; the threshold itself is unchanged (7).",
      "The check is performed at pairing time only; nothing in the diff re-validates the key size of LTKs already stored from earlier bonds, nor does it touch the local loc_enc_size value or the encryption-start path for LE links.",
      "On Android 14 and 15 the minimum is a hard-coded 7; only the Android 16 variant honours the android::sysprop::bluetooth::Gap::min_key_size property, clamped to [MIN_KEY_SIZE, MAX_KEY_SIZE].",
      "The cert_failure branch is merged into the new condition and now always reports SMP_ENC_KEY_SIZE, which is the same value it previously carried."
     ],
     "behavior_changes": [
      "LE pairing with a peer advertising Maximum Encryption Key Size < 7 now fails with SMP status SMP_ENC_KEY_SIZE (SMP_AUTH_CMPL_EVT) and a warning log.",
      "SMP over BR/EDR pairing commands with key size < 7 now fail with SMP_ENC_KEY_SIZE via SMP_BR_AUTH_CMPL_EVT.",
      "The new key-size check runs before smp_command_has_invalid_parameters(), so an undersized key is reported as SMP_ENC_KEY_SIZE rather than SMP_INVALID_PARAMETERS even if other parameters are also invalid.",
      "btu_hcif.cc key-size disconnect logic now calls btm_sec_get_min_enc_key_size() instead of a file-local constant; same threshold on Android 14."
     ],
     "preconditions": [
      "Bluetooth enabled and an LE (or BR/EDR with SMP cross-transport) connection with a remote device that engages in SMP pairing with the local device.",
      "The remote device's SMP Pairing Request or Pairing Response carries a Maximum Encryption Key Size field below 7 octets; smp_proc_pair_cmd handles the peer's pairing request/response (the context shows peripheral-role handling of a peer-initiated Pairing Request).",
      "Whether user interaction (pairing confirmation) is required before the vulnerable negotiation completes is not shown in the material."
     ],
     "per_major": {
      "14": "fix 3475f39c4c16 in android-security-14.0.0_r29 (cherry-pick of 55c96018e4b4): accessor returns constant 7; key-size handlers patched in system/stack/btu/btu_hcif.cc where they live on this branch; plain mock stub; cosmetic controller.cc edit.",
      "15": "adapted fix (f161543f33de in android-security-15.0.0_r17): same smp_act.cc changes (int min_key_size, log::warn); handlers patched in system/stack/btm/btm_sec.cc where they live on this branch; constants are constexpr int; mockcify-style mock struct added in mock_stack_btm_sec.cc/.h; no controller.cc change.",
      "16": "adapted fix (8b61335e4984 in android-security-16.0.0_r8): same smp_act.cc changes; the pre-existing static get_min_enc_key_size() (sysprop Gap::min_key_size clamped to [7,16]) is renamed/exported as btm_sec_get_min_enc_key_size() and constants moved to btm_sec.h; mockcify mock added.",
      "17": "not affected: not listed by the bulletin per the per-major facts; at android-security-17.0.0_r1, 4/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The functional core is the two smp_act.cc hunks plus a linkable btm_sec_get_min_enc_key_size(); the btu_hcif.cc/btm_sec.cc hunks only redirect the existing BR/EDR check. Locate read_encryption_key_size_complete_after_* in btu_hcif.cc (14) or btm_sec.cc (15/16) and remove the local MIN_KEY_SIZE, since the new header constant has the same name.",
      "Each branch's fix adds a btm_sec_get_min_enc_key_size() stub to system/test/mock/mock_stack_btm_sec.cc (plus the mockcify struct in the .h on 15 and 16); test binaries linking against the btm_sec mock likely need it."
     ],
     "confidence": "high",
     "unknowns": [
      "The concrete security consequence that justified the Critical EoP rating is not described in the material beyond 'key sizes smaller than 7 octets are forbidden'.",
      "The bulletin's affected list includes '17-next' while the per-major facts say Android 17 is not listed; how 17-next relates to android-security-17.0.0_r1 is not shown.",
      "Whether existing bonds created with an undersized key before the fix are revalidated or invalidated (nothing in the diff does so).",
      "What smp_command_has_invalid_parameters() validates (including any key-size range) is not shown in the material.",
      "What sets p_cb->cert_failure is not shown in the material.",
      "Why the btm_proc_sp_req_evt declaration was changed to const uint8_t* in the 14 header; the corresponding definition is not in the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28604": {
     "cve": "CVE-2026-28604",
     "summary": "Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport pointer. The thread now captures a weak-pointer callback id plus the transport's connection object, and re-enters the fdevent looper to check that the transport is still alive before touching transport state. The fix also splits FdConnection::DoTlsHandshake into host and daemon variants and adds a 3-second socket timeout to the daemon handshake.",
     "component": "platform/packages/modules/adb, adbd daemon TLS authentication (daemon/auth.cpp, transport.cpp)",
     "weakness": "Use-after-free (race between a detached background thread holding a raw atransport* and transport destruction)",
     "root_cause": "In daemon/auth.cpp, adbd_auth_tls_handshake(atransport* t) spawned a detached std::thread that captured the raw pointer t. The thread blocked in t->connection()->DoTlsHandshake(rsa_pkey, &auth_key), with no socket timeout on the daemon side. Afterwards it dereferenced t directly from the background thread (t->IsTcpDevice(), t->auth_key = ..., adbd_wifi_secure_connect(t), adbd_auth_verified(t), adbd_notify_framework_connected_key(t), t->Kick()). Nothing verified that the transport still existed, and the transport state changes happened off the fdevent looper thread. If the transport was destroyed while the handshake was pending, the thread could use freed memory. The existing transport_to_callback_arg/transport_from_callback_arg weak-pointer mechanism in the same file was not used for this path.",
     "fix_analysis": [
      "Commit e96f6790c785 (Change-Id Ic419a1fb..., applied first, daemon/auth.cpp 05f51801 -> 0be3f995) documents the global 'transports' map (uint32_t id -> weak_ptr<atransport>) and the transport-to-callback flow. It adds fdevent_check_looper() to transport_to_callback_arg() and transport_from_callback_arg(). It also rewrites transport_from_callback_arg() to use transports->extract(id_u32), so the entry is always removed from the map on lookup. Before, find() was followed by erase(it) only on the success path. The function returns nullptr when the node is empty or the weak_ptr is dead.",
      "Commit c7995e05cc7b (Change-Id I8ed271ab..., daemon/auth.cpp 0be3f995 -> afcf6da1), daemon/auth.cpp:adbd_auth_tls_handshake: before spawning the thread, the function computes void* arg = transport_to_callback_arg(t) and auto connection = t->connection(). The detached thread captures only [arg, connection] and runs bool connected = connection->DoTlsHandshake(rsa_pkey, &auth_key). It then posts a lambda via fdevent_run_on_looper. On the looper, the lambda calls transport_from_callback_arg(arg). If that returns nullptr, the result is dropped, with a log if the handshake had succeeded. If !connected, it calls t->Kick(). Otherwise it runs the original success path: IsTcpDevice -> set t->auth_key and call adbd_wifi_secure_connect(t); else adbd_auth_verified(t) and adbd_notify_framework_connected_key(t), now annotated as likely dead code.",
      "Same commit, transport.cpp:FdConnection::DoTlsHandshake: the single function with internal #if ADB_HOST blocks is split into two whole-function variants. The ADB_HOST client variant keeps the same logic. The daemon variant (Role::Server, adbd_tls_verify_cert, adbd_tls_client_ca_list) now sets SO_RCVTIMEO and SO_SNDTIMEO to 3 seconds on fd_ via adb_setsockopt before tls_->DoHandshake(); setsockopt failures are only PLOG'd. It resets both timeouts to 0 afterwards. On failure, tls_ is reset and false is returned, as before.",
      "On Android 17 the same commit also inserts CHECK_NOT_LOOPER_THREAD() at the start of both DoTlsHandshake variants. This is not in the 14/15/16 diffs."
     ],
     "completeness": [
      "Covers the daemon-side adbd_auth_tls_handshake path: the raw pointer capture is removed, and all transport dereferences move to the fdevent looper behind a weak_ptr liveness check.",
      "The handshake socket timeout is added only to the non-ADB_HOST (daemon) variant of FdConnection::DoTlsHandshake. The host client variant has no timeout.",
      "If adb_setsockopt fails when installing the timeout, the failure is logged and the handshake proceeds without a timeout.",
      "Per index search (android-14.0.0_r75), the host-side client/auth.cpp:adb_auth_tls_handshake still has the original shape: a detached thread capturing raw atransport* t, then t->connection()->DoTlsHandshake and t->Kick(). These commits do not touch it."
     ],
     "behavior_changes": [
      "The daemon TLS handshake socket now has 3-second SO_RCVTIMEO/SO_SNDTIMEO timeouts during tls_->DoHandshake(), cleared back to 0 afterwards. A failed handshake leads to t->Kick() on the looper.",
      "Handshake results that arrive after the transport was destroyed are discarded, with LOG(ERROR) 'TLS handshake succeeded for deleted transport, ignoring' when the handshake succeeded.",
      "Transport state updates and Kick() after the handshake now run on the fdevent looper thread instead of the handshake thread.",
      "transport_to_callback_arg/transport_from_callback_arg now assert that they run on the fdevent thread (fdevent_check_looper()). Map entries are always removed on lookup via extract().",
      "Android 17 only: DoTlsHandshake asserts CHECK_NOT_LOOPER_THREAD()."
     ],
     "preconditions": [
      "adbd (non-host build) receives an A_STLS packet on a transport, and handle_packet in adb.cpp calls adbd_auth_tls_handshake(t) (per index search).",
      "The transport is destroyed while the background TLS handshake is in flight or before its result is applied.",
      "Before the fix, no timeout bounded the daemon-side handshake."
     ],
     "per_major": {
      "14": "same fix: both commits (e96f6790c785 hardening, then c7995e05cc7b UAF fix) are in android-security-14.0.0_r29. The hardening uses fdevent_check_looper(), while the commit message names CHECK_LOOPER_THREAD(). The resolver reports 7/8 patched-region lines present.",
      "15": "same fix: 119250e41944 in android-security-15.0.0_r17. The auth.cpp hunk is identical to Android 14 (LOG(INFO) auth_key line); transport.cpp line offsets differ slightly. The resolver reports 7/8 lines present.",
      "16": "same fix: f487f473c82d in android-security-16.0.0_r8, with identical logic; the context line uses VLOG(AUTH) instead of LOG(INFO). The resolver reports 7/8 lines present.",
      "17": "adapted fix: 12700491ebb3 in android-security-17.0.0_r1. Same auth.cpp change (VLOG(AUTH) context), plus CHECK_NOT_LOOPER_THREAD() at the top of both DoTlsHandshake variants. The resolver reports 7/8 lines present."
     },
     "backport_notes": [
      "Apply the hardening change (Ic419a1fb: fdevent_check_looper and extract in the transport_to_callback_arg/transport_from_callback_arg helpers) before the UAF fix (I8ed271ab). The fix uses these helpers and fdevent_run_on_looper. On Android 14, the auth.cpp diff of the UAF fix starts from the blob produced by the hardening commit.",
      "The resolver reports a 7/8 line match on every security tag. Check that adbd_auth_tls_handshake in the target tree matches the post-fix shape, rather than relying on the line count."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact trigger sequence, and whether the bulletin's RCE rating comes from this UAF alone, are not shown in the material.",
      "Whether the hardening commit (Ic419a1fb) was applied on Android 15/16/17, and in what form; only the Android 14 diff of that commit is provided.",
      "The return type of atransport::connection(), and therefore whether the captured connection keeps the connection object alive during the handshake.",
      "Which line of the patched region accounts for the 7/8 match on each tag.",
      "Whether TlsConnection::DoHandshake returns a failure when the socket timeout fires."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28639": {
     "cve": "CVE-2026-28639",
     "summary": "In rw_mfc_handle_read_op (MIFARE Classic NDEF read handling), the else branch that logs SafetyNet ID 178725766 for an NDEF length inflation attempt now also sets failed = true, so the read operation is marked as failed instead of continuing. Per the commit message, this prevents a heap out-of-bounds write.",
     "component": "platform/system/nfc (libnfc-nci), src/nfc/tags/rw_mfc.cc, MIFARE Classic (MFC) tag reader/writer; on Android 16/17 the same file is in platform/packages/modules/Nfc under libnfc-nci/",
     "weakness": "Missing failure propagation after a length check (heap out-of-bounds write, per the commit message)",
     "root_cause": "In rw_mfc_handle_read_op, the branch that detects an NDEF length inflation attempt only emitted android_errorWriteLog(0x534e4554, \"178725766\") and did not mark the operation as failed. The function then went on with its normal processing. Per the commit message, this allowed a heap out-of-bounds write. In the READ_NDEF path shown in the excerpt, data is copied into p_mfc->p_ndef_buffer up to p_mfc->ndef_length, and when that length has not been reached the next block is read. The exact condition of the patched branch and the buffer sizing are not visible in the material.",
     "fix_analysis": [
      "src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: in the else branch that logs the length inflation attempt (android_errorWriteLog(0x534e4554, \"178725766\")), add 'failed = true;' so the operation is explicitly marked as failed. How the existing code acts on failed afterwards is not shown in full.",
      "Single-line, single-commit fix. The hunk is the same in all four majors; only the line number and, on 16/17, the file path differ."
     ],
     "completeness": [
      "Covers only the length-inflation else branch in rw_mfc_handle_read_op.",
      "The patch does not change how ndef_length is validated or rolled back in that branch. The code excerpt shows a different place (work_offset == 0 check) that already sets failed = true with an explicit roll back, so the full surrounding control flow of the patched branch is not visible.",
      "Other MFC paths (RW_MFC_STATE_DETECT_TLV, write paths, rw_nfc_decodeTlv) are not touched by this commit."
     ],
     "behavior_changes": [
      "An MFC NDEF read that hits the length-inflation branch is now marked as failed instead of continuing. The SafetyNet log entry for 178725766 is still emitted. How the failure is reported to the caller is not shown."
     ],
     "preconditions": [
      "NFC active and an MIFARE Classic tag being read through the libnfc-nci MFC reader path (rw_mfc_handle_read_op), where the tag presents an inflated NDEF length. The exact state and calling path are not shown, and the index search found no callers outside rw_mfc.cc."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc e884267aee53 in android-security-14.0.0_r29 (cherry-pick of d43cc29f19da), src/nfc/tags/rw_mfc.cc line ~1060; 2/2 lines of the patched region present.",
      "15": "same fix: platform/system/nfc e22f0f7b91dd in android-security-15.0.0_r17, src/nfc/tags/rw_mfc.cc line ~1057; 2/2 lines present.",
      "16": "same fix, relocated: platform/packages/modules/Nfc ca73224fb2c2 in android-security-16.0.0_r8, libnfc-nci/src/nfc/tags/rw_mfc.cc line ~1292; 2/2 lines present.",
      "17": "same fix, relocated: platform/packages/modules/Nfc 9f4efdd67acb in android-security-17.0.0_r1, libnfc-nci/src/nfc/tags/rw_mfc.cc line ~1381; 2/2 lines present."
     },
     "backport_notes": [
      "Find the 'else { android_errorWriteLog(0x534e4554, \"178725766\"); }' branch in rw_mfc_handle_read_op and add 'failed = true;'. The code excerpt shows another 178725766 check that already sets failed = true, so check which branch the target tree has before patching. On 16/17 the file is in platform/packages/modules/Nfc under libnfc-nci/."
     ],
     "confidence": "medium",
     "unknowns": [
      "The code excerpt (lines 1013-1072) does not contain the patched else branch. The if condition of that branch, and whether ndef_length is rolled back there, are not visible.",
      "How failed is handled later in the function, and how the failure reaches the caller (event/status), are not shown.",
      "How p_ndef_buffer is sized relative to ndef_length is not shown.",
      "How this relates to the earlier bug 178725766 referenced by the log ID is not described.",
      "The caller path and the exact tag interaction needed to reach the branch are not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28653": {
     "cve": "CVE-2026-28653",
     "summary": "Adds a range check before the loop in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (src/nfc/tags/rw_t3t.cc). The first block number plus the block count can no longer go past the 16-bit range, where the commit says the uint16 block_id loop counter could wrap and cause an unbounded heap out-of-bounds write.",
     "component": "platform/system/nfc (Android 14/15) and platform/packages/modules/Nfc libnfc-nci (Android 16/17), NFC Type 3 Tag (T3T) reader/writer, NDEF CHECK/UPDATE command builder",
     "weakness": "Integer wrap of a 16-bit loop counter leading to a heap out-of-bounds write (missing bounds check)",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, the block descriptor loop runs `for (block_id = first_block_to_write; block_id < (first_block_to_write + ndef_blocks_to_write); block_id++)` (and the CHECK equivalent with first_block_to_read / cur_blocks_to_read). Each iteration appends a 2- or 3-byte block descriptor with UINT8_TO_STREAM / UINT16_TO_STREAM into the command buffer through p. The commit describes this as a uint16 loop counter wrap: when the first block plus the block count reaches past 65535, block_id wraps, and the commit says this leads to an unbounded heap out-of-bounds write. The block count comes from ndef_blocks_remaining and blocks_per_update, and blocks_per_update is capped by p_cb->ndef_attrib.nbw.",
     "fix_analysis": [
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: after the UPDATE header is written and the timeout is computed, the fix adds `if (ndef_blocks_to_write > UINT16_MAX - first_block_to_write)`. On that path it logs an error, emits android_errorWriteLog(0x534e4554, \"508389055\") and returns NFC_STATUS_FAILED before the block descriptor loop runs.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: the same check, `if (cur_blocks_to_read > UINT16_MAX - first_block_to_read)`, with the same log, errorWriteLog and NFC_STATUS_FAILED return before the CHECK block descriptor loop.",
      "The code at android-security-14.0.0_r29 shows a different form of the guard (7/8 lines of the patched region present). The condition is `(uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000`, GKI_freebuf(p_cmd_buf) is called before returning NFC_STATUS_FAILED, and the loop condition casts the sum to uint32_t. The material does not explain why the tag code differs from the linked diff."
     ],
     "completeness": [
      "Both T3T NDEF command builders with this loop (UPDATE and CHECK) are guarded. The material shows no other loop of this shape.",
      "The guard in the diff returns NFC_STATUS_FAILED without freeing the command buffer p_cmd_buf. The code at the 14.0.0_r29 tag calls GKI_freebuf(p_cmd_buf) before the return.",
      "The check only rejects the wrapping case. It does not otherwise check that the descriptors fit in the command buffer. That relies on the existing caps: blocks_per_update limited by p_cb->ndef_attrib.nbw, and the block count written as a single uint8 field.",
      "The check runs after the header bytes have been streamed into p. With the raw diff the buffer then leaks on the failure path. How callers handle NFC_STATUS_FAILED is not shown."
     ],
     "behavior_changes": [
      "NDEF UPDATE or CHECK on a T3T tag whose block range would go past 65535 now fails with NFC_STATUS_FAILED instead of building the command.",
      "The rejected path logs a new LOG(ERROR) message \"<func>: Heap out-of-bounds\" and an android_errorWriteLog event tagged 0x534e4554 / 508389055.",
      "In the code at the 14 tag, the GKI command buffer is freed on the new failure path."
     ],
     "preconditions": [
      "The NFC stack's rw_t3t layer is reading (CHECK) or writing (UPDATE) NDEF data on a Type 3 tag.",
      "The first block number plus the block count (derived from ndef_blocks_remaining and blocks_per_update, which is capped by ndef_attrib.nbw) goes past 65535."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc bea12a1b3e25 in android-security-14.0.0_r29 (cherry-pick of b33444ec), src/nfc/tags/rw_t3t.cc. The code at the tag shows a different form (uint32_t compare against 0x10000, GKI_freebuf); 7/8 lines of the patched region present.",
      "15": "same fix: platform/system/nfc 089a1a4123db in android-security-15.0.0_r17, same two hunks in src/nfc/tags/rw_t3t.cc; 7/8 lines of the patched region present at the tag (the actual code there is not shown).",
      "16": "same fix: platform/packages/modules/Nfc 4648c160b825 in android-security-16.0.0_r8, at libnfc-nci/src/nfc/tags/rw_t3t.cc; identical hunks; 7/8 lines of the patched region present.",
      "17": "same fix: platform/packages/modules/Nfc e55c93242c56 in android-security-17.0.0_r1, at libnfc-nci/src/nfc/tags/rw_t3t.cc; identical hunks; 7/8 lines of the patched region present."
     },
     "backport_notes": [
      "Consider the form shown at the 14 security tag instead of the raw diff: `(uint32_t)first_block + count > 0x10000`, GKI_freebuf(p_cmd_buf) before `return NFC_STATUS_FAILED`, and a (uint32_t) cast in the loop bound. The raw diff does not free the GKI buffer on the error path.",
      "On Android 16+ the file is in platform/packages/modules/Nfc at libnfc-nci/src/nfc/tags/rw_t3t.cc; on 14/15 it is platform/system/nfc src/nfc/tags/rw_t3t.cc."
     ],
     "confidence": "medium",
     "unknowns": [
      "The commit that produced the different check seen at the 14 tag (GKI_freebuf, 0x10000 comparison, uint32_t loop cast) is not in the material. Whether 15/16/17 have the same form is not shown.",
      "The declared types of block_id, first_block_to_write/first_block_to_read and the block count variables are not shown. Only the commit title says the loop counter is uint16.",
      "Where ndef_attrib (nbw, NDEF length) and the block offsets come from, and whether a tag controls them, is not shown.",
      "How callers handle the new NFC_STATUS_FAILED return is not shown.",
      "The material does not explain why the bulletin classifies this as DoS."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-33636": {
     "cve": "CVE-2026-33636",
     "summary": "Restricts the NEON palette-expansion loops in external/libpng arm/palette_neon_intrinsics.c to full chunks only (loop condition i + pixels_per_chunk <= row_width), drops the post-loop count correction, and undoes the *ddp pre-adjustment before handing the remaining pixels to the scalar fallback.",
     "component": "platform/external/libpng, ARM NEON palette expansion (arm/palette_neon_intrinsics.c)",
     "weakness": "Missing bounds check in a vectorized loop, leading to out-of-bounds read and write (the loop over fixed-size chunks does not guard the final partial chunk)",
     "root_cause": "png_do_expand_palette_rgba8_neon and png_do_expand_palette_rgb8_neon iterate `for (i = 0; i < row_width; i += pixels_per_chunk)`. Each iteration loads palette entries indexed by source bytes at sp and stores a full vector (vst1q_u32 of a uint32x4_t for rgba8, i.e. 4 pixels; vst3_u8 for rgb8, 8 pixels per the 'Seeking this back by 8 pixels x 3 bytes' comment) at dp. When row_width is not a multiple of the chunk size, the last iteration still runs with less than a full chunk left. Per the commit message, this allows out-of-bounds reads and writes. The post-loop `if (i != row_width) i -= pixels_per_chunk;` only corrected the returned pixel count after the loop had finished. In addition, *ddp is pre-adjusted at entry by (pixels_per_chunk * element size - 1), and that offset was not undone before the pointer was handed back for the scalar path.",
     "fix_analysis": [
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: the loop condition changes from `i < row_width` to `i + pixels_per_chunk <= row_width`, so the NEON body only runs while a full chunk remains. The trailing partial chunk is left to the scalar code.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: the `if (i != row_width) i -= pixels_per_chunk;` adjustment is removed because i no longer overshoots row_width.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: before the final `*ssp = *ssp - i; *ddp = *ddp - (i << 2);`, the entry pre-adjustment is reverted with `*ddp = *ddp + (pixels_per_chunk * 4 - 1);`. Per the added comment, this makes the dp handed to the scalar fallback in pngrtran.c point to the correct position.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgb8_neon: the same changes are made for the RGB path. The loop condition becomes `i + pixels_per_chunk <= row_width`, the post-loop adjustment is removed, and `*ddp = *ddp + (pixels_per_chunk * 3 - 1);` is added before `*ssp = *ssp - i; *ddp = *ddp - ((i << 1) + i);`.",
      "The return value i is still the number of pixels processed by NEON, and is now always a multiple of pixels_per_chunk."
     ],
     "completeness": [
      "Both NEON palette expansion functions in the diff (rgba8 and rgb8) are fixed with the same pattern.",
      "The fix relies on the scalar fallback in pngrtran.c to process the remaining row_width - i pixels from the handed-off *ssp/*ddp. The pngrtran.c side is not part of the diff and is not shown in the material, so its correctness with the new handoff pointer cannot be checked here.",
      "png_riffle_palette_neon in the same file is unchanged by the diff. Only its first lines are shown."
     ],
     "behavior_changes": [
      "For rows whose width is not a multiple of the chunk size, the trailing pixels are now left to the scalar code instead of being processed by a final NEON iteration.",
      "The *ddp value handed back to the caller no longer carries the (pixels_per_chunk * element size - 1) pre-adjustment.",
      "The diff adds no new error codes and makes no API changes."
     ],
     "preconditions": [
      "libpng built with PNG_ARM_NEON_IMPLEMENTATION == 1 (NEON path enabled).",
      "Decoding a palette image with palette expansion requested. For example, frameworks/base/libs/androidfw/Png.cpp readPng calls png_set_palette_to_rgb for PNG_COLOR_TYPE_PALETTE images (per index search).",
      "Row width not a multiple of the NEON chunk size."
     ],
     "per_major": {
      "14": "same fix: commit 843457e59be7 in android-security-14.0.0_r29. Identical diff to arm/palette_neon_intrinsics.c (Change-Id I56e19b3e1a226de21e91e400124e36021357689a). 7/8 lines of the patched region are present at that tag.",
      "15": "same fix: commit fa7e33a0a0cc in android-security-15.0.0_r17. Identical diff with the same blob ids (92c7d6f9f..bdd15849d). 6/8 lines of the patched region are present at that tag.",
      "16": "same fix: commit 0b9147198ed6 in android-security-16.0.0_r8. Identical diff with the same blob ids as 14/15. 6/8 lines of the patched region are present at that tag.",
      "17": "same fix with shifted offsets: commit 7d98050ef8d9 in android-security-17.0.0_r1. The file base differs (3068e9b6e, no leading blank line), so hunk offsets move by one line. The code changes are identical. 6/8 lines of the patched region are present at that tag."
     },
     "backport_notes": [
      "This is a single commit touching one file. 14, 15 and 16 share pre-image blob 92c7d6f9f. On 17 the file differs only by a one-line offset at the top.",
      "The handoff change assumes the scalar fallback in pngrtran.c consumes *ssp/*ddp as returned. Check the pngrtran.c caller in the target tree before backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "The pngrtran.c scalar fallback that consumes the handed-off pointers is not in the material. It is therefore not shown whether the old, un-reverted *ddp offset caused incorrect access there or was compensated for by the caller.",
      "The material does not say which Android process or context the bulletin's Critical EoP rating refers to.",
      "The content of the two internal Cherrypick-From commits is not shown.",
      "Whether png_riffle_palette_neon or other code in the file has a similar loop pattern is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45515": {
     "cve": "CVE-2026-45515",
     "summary": "Passes the correct frame_size (the number of samples per channel the output buffer can hold, A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t))) to both opus_decode() calls in a2dp_vendor_opus_decoder_decode_packet instead of the buffer size in bytes. It also adds a channel-count check, removes the per-frame re-decode loop and makes frame_len use the configured stereo output.",
     "component": "platform/packages/modules/Bluetooth, system/stack/a2dp (A2DP sink Opus vendor codec decoder)",
     "weakness": "Heap buffer overflow caused by a unit mismatch in a size argument (a byte count passed where samples per channel are expected), i.e. a wrong bound given to the decoder",
     "root_cause": "In system/stack/a2dp/a2dp_vendor_opus_decoder.cc, a2dp_vendor_opus_decoder_decode_packet called opus_decode(handle, pBuffer, bufferSize, a2dp_opus_decoder_cb.decode_buf, A2DP_OPUS_DECODE_BUFFER_LENGTH, 0). Per the commit message, opus_decode expects samples per channel as its frame_size, but A2DP_OPUS_DECODE_BUFFER_LENGTH is the buffer size in bytes. The output is stereo int16 (the commit message gives a fixed channel count of 2), so the bound was larger than decode_buf's real capacity by the factor A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t). A crafted Opus packet with a large frame duration could therefore overflow the heap buffer. The new test uses a real 60 ms, 2880 samples per channel packet. The packet-loss-concealment call opus_decode(handle, NULL, 0, ...) had the same wrong bound. The code also had three other problems: it looped numFrames times decoding the same whole packet, it computed frame_len from the packet's numChannels instead of the configured output channel count, and it never validated numChannels.",
     "fix_analysis": [
      "system/stack/a2dp/a2dp_vendor_opus_decoder.cc:a2dp_vendor_opus_decoder_decode_packet: both opus_decode() calls (the normal decode and the NULL/0 concealment retry) now pass A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t)) as frame_size. The decoder's bound is now the number of stereo int16 samples per channel that decode_buf can hold.",
      "Same function: adds an early return false with an error log when opus_packet_get_nb_channels() returns anything other than 1 or 2. This also rejects negative error codes stored in the uint32_t numChannels.",
      "Same function: removes the 'for (frame = 0; frame < numFrames; ++frame)' loop, which re-decoded the entire packet and called decode_callback numFrames times. The packet is now decoded exactly once.",
      "Same function: frame_len passed to decode_callback is now ret_val * A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(decode_buf[0]) (configured stereo output) instead of ret_val * numChannels (the packet's channel count). The unused 'num_frames = pBuffer[0] & 0xf' is dropped, and two LOG_ERROR lines are replaced by one LOG_INFO line.",
      "system/stack/test/a2dp/a2dp_opus_unittest.cc: adds <opus.h> and a new test, decode_packet_60ms_overflow_repro. It encodes a 60 ms stereo 48 kHz Opus packet, prepends the 1-byte A2DP header and passes it to decoder_iface_->decode_packet()."
     ],
     "completeness": [
      "Both opus_decode() call sites in the function (the primary decode and the concealment retry) get the corrected frame_size. The shown material contains no other opus_decode calls.",
      "The bound comes from the constant A2DP_OPUS_CODEC_OUTPUT_CHS (stereo), and a code comment says Opus is always configured as stereo. The fix is only correct if the decoder handle created in a2dp_vendor_opus_decoder_init uses that channel count and decode_buf is allocated with A2DP_OPUS_DECODE_BUFFER_LENGTH bytes. The diff shows neither.",
      "The fix adds no explicit post-decode check that ret_val * A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t) <= A2DP_OPUS_DECODE_BUFFER_LENGTH. It relies entirely on opus_decode honoring frame_size.",
      "Existing input handling in the same function is unchanged. pBuffer = p_buf->data + p_buf->offset + 1 and bufferSize = p_buf->len - 1 are computed after only a p_buf->len == 0 check. A packet with len == 1 therefore gives bufferSize 0, and opus_packet_get_nb_channels(pBuffer) is still called on the byte after the A2DP header. The fix adds no minimum-length check.",
      "numFrames, frameSize and frameLen are still computed, now only for logging. Error returns from those opus_packet_* helpers are stored in uint32_t and not validated, but they no longer affect control flow."
     ],
     "behavior_changes": [
      "opus_decode now receives a frame_size that matches decode_buf's capacity. When the primary decode returns an error, the concealment call runs with the same corrected bound, and the packet is dropped (return false) only if that call also fails.",
      "Packets reporting a channel count other than 1 or 2 are rejected with 'Invalid numChannels' and return false.",
      "A multi-frame Opus packet is decoded and delivered to decode_callback once instead of numFrames times.",
      "The decode_callback length is always computed with the stereo output channel count, so mono-flagged packets now report the stereo byte length.",
      "Per-packet diagnostic logging drops from two LOG_ERROR lines to one LOG_INFO line."
     ],
     "preconditions": [
      "The device acts as A2DP sink with the vendor Opus codec selected, so incoming media packets reach a2dp_vendor_opus_decoder_decode_packet. That function is declared in system/stack/include/a2dp_vendor_opus_decoder.h as the decoder's decode entry point (per index search).",
      "A connected remote A2DP source sends an Opus packet whose frame duration produces more PCM than decode_buf can hold. The material shows no further user interaction.",
      "a2dp_vendor_opus_decoder_init must have succeeded (opus_handle and decode_buf allocated)."
     ],
     "per_major": {
      "14": "Same fix: commit 4f7ed05b1660 in android-security-14.0.0_r29 (RESTRICT AUTOMERGE cherry-pick of e87caba4b8df). It uses the printf-style LOG_ERROR/LOG_INFO macros and also reformats the frameSize/frameLen lines. In the test file, <stdio.h> is replaced by <opus.h>.",
      "15": "Same fix: commit b837ddeb7125 in android-security-15.0.0_r17. Identical logic, adapted to fmt-style log::error/log::info logging. The test adds <stdio.h> and <opus.h>.",
      "16": "Same fix: commit 5eefb7cb0b0c in android-security-16.0.0_r8. Identical logic with log::* logging. The hunk is smaller because the frameSize/frameLen lines were already single-line on this branch.",
      "17": "Same fix: commit 3b78dbef3ec6 in android-security-17.0.0_r1. The hunks are the same as Android 16, and the test file has the same blob indexes. The decoder file's base blob differs."
     },
     "backport_notes": [
      "A single self-contained commit that touches one function. The only branch-specific adaptation is the logging API (LOG_ERROR/LOG_INFO on 14, log::error/log::info on 15+). It requires A2DP_OPUS_CODEC_OUTPUT_CHS and A2DP_OPUS_DECODE_BUFFER_LENGTH to be defined. The new unit test calls the libopus encoder API (opus_encoder_create/opus_encode)."
     ],
     "confidence": "high",
     "unknowns": [
      "The numeric values of A2DP_OPUS_DECODE_BUFFER_LENGTH and A2DP_OPUS_CODEC_OUTPUT_CHS, where they are defined, and how decode_buf is allocated in a2dp_vendor_opus_decoder_init are not shown.",
      "How opus_decode handles a packet whose duration exceeds frame_size (error code or other behavior), and whether the concealment retry then succeeds, are not shown.",
      "The bulletin lists the issue as EoP. The material does not state the privilege boundary crossed or the process in which the decoder runs.",
      "The call path from the AVDTP/A2DP sink media receive path into a2dp_vendor_opus_decoder_decode_packet is not shown in the material (only the header declaration and the unit test)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45531": {
     "cve": "CVE-2026-45531",
     "summary": "Hardens fsck.exfat boot-sector validation in read_boot_region() against integer overflow and underflow from the on-disk vol_length, clu_offset and clu_count fields, and makes the shared DIV_ROUND_UP macro compute in 64-bit.",
     "component": "platform/external/exfatprogs, fsck.exfat (fsck/fsck.c) and shared header include/libexfat.h",
     "weakness": "Integer overflow/underflow due to insufficient validation of on-disk boot sector fields (missing bounds checks)",
     "root_cause": "In fsck/fsck.c:read_boot_region(), the size checks on the exFAT boot sector used arithmetic that could wrap. vol_length (64-bit, from disk) was multiplied by EXFAT_SECTOR_SIZE(bs) before the comparison with bd->size, so a large vol_length could wrap past the check. Nothing checked that clu_offset is smaller than vol_length, so a later subtraction of clu_offset from vol_length could underflow (per the fix's comment). clu_count was not capped to EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5), so later computations on clu_count could overflow (per the fix's comment). DIV_ROUND_UP in include/libexfat.h also did the (__i + __d - 1) addition in the operand's own type, so that addition could overflow for large inputs.",
     "fix_analysis": [
      "Commit 20f47ac2509f (ANDROID: Add support for building exfat tests) changes Android.bp, fsck/Android.bp and lib/Android.bp. It adds host_supported: true to libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat. This is a build-only change with no change to the code.",
      "Commit 54cf500d4abd, fsck/fsck.c:read_boot_region(): the vol_length check changes from 'vol_length * EXFAT_SECTOR_SIZE(bs) > bd->size' to 'vol_length > (bd->size >> bs->bsx.sect_size_bits)'. The comparison now shifts the device size instead of multiplying the untrusted field. The earlier check limiting EXFAT_SECTOR_SIZE(bs) to 512..4KB comes before it.",
      "fsck/fsck.c:read_boot_region(): a new check takes the error path (goto err, with ret set to -EINVAL earlier) when le32_to_cpu(bs->bsx.clu_offset) >= le64_to_cpu(bs->bsx.vol_length). This prevents underflow when clu_offset is subtracted from vol_length.",
      "fsck/fsck.c:read_boot_region(): a new check rejects the image when le32_to_cpu(bs->bsx.clu_count) > EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5). It runs before the existing 'clu_count * EXFAT_CLUSTER_SIZE(bs) > bd->size' check.",
      "include/libexfat.h: DIV_ROUND_UP now casts __i to uint64_t, so the addition and division happen in 64-bit for every user of the macro.",
      "tests/clu_count_overflow/exfat.img.tar.xz: a regression image is added."
     ],
     "completeness": [
      "All new checks run once, in read_boot_region(), on the boot sector fields. Code later in fsck.exfat that uses these fields relies on the new bounds.",
      "The DIV_ROUND_UP change is in the shared header include/libexfat.h, so every file that includes it gets the 64-bit arithmetic. The material does not show which tools call it. The other macros in the header (round_up, round_down) were not changed.",
      "The existing 'le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > bd->size' comparison is left as is. The new cap limits clu_count but not the product. Whether the product can wrap depends on types the material does not show.",
      "The fix validates only the fields named in the commit. Other boot sector fields (for example fat_offset, fat_length, root_cluster) are not touched, and the material does not show how they are validated."
     ],
     "behavior_changes": [
      "fsck.exfat now fails in read_boot_region() with an exfat_err message on images where clu_offset >= vol_length or clu_count > 0xFFFFFFF5. The check that vol_length does not exceed the device size in sectors is now done without a multiplication.",
      "DIV_ROUND_UP now yields a 64-bit (uint64_t) result for all callers, which changes the result type of expressions that use it.",
      "Build: libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat gain host_supported: true, so host variants are now built."
     ],
     "preconditions": [
      "fsck.exfat must process an exFAT image or block device with a crafted boot sector. Inside read_boot_region(), the patched checks run only after boot_region_checksum() passes and after the sector size, cluster size, fs_version and num_fats checks."
     ],
     "per_major": {
      "14": "Same fix: 54cf500d4abd (fsck.c, libexfat.h, test image) plus build commit 20f47ac2509f at android-security-14.0.0_r29. The resolver reports 7/8 lines of the patched region present.",
      "15": "Resolved at android-security-15.0.0_r17 with 8/8 lines of the patched region present. The material shows only the build commit 2957a37c1d7f (host_supported, plus a whitespace fix to the exfat2img header entry). The functional fsck.c/libexfat.h commit for 15 is not shown.",
      "16": "Adapted fix: c0e2d5cc6945 at android-security-16.0.0_r8. It adds the same checks as on 14, but wraps each exfat_err in 'if (verbose)' to match this branch's read_boot_region(). The resolver reports 6/8 lines of the patched region present.",
      "17": "Resolved at android-security-17.0.0_r1 with 8/8 lines of the patched region present. The material shows only the build commit a6faf5dfd40a. The functional commit is not shown."
     },
     "backport_notes": [
      "The functional fix is self-contained in fsck/fsck.c:read_boot_region() and include/libexfat.h. The build commit (host_supported) is independent and is only needed to build host variants for the tests. On branches where read_boot_region() guards exfat_err with 'if (verbose)' (as on 16), use the 16 version of the hunks.",
      "Keep the sector-size validation (EXFAT_SECTOR_SIZE in 512..4KB) ahead of the new 'bd->size >> bs->bsx.sect_size_bits' expression."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show which later computation is the memory-unsafe sink (the clu_offset subtraction, a DIV_ROUND_UP user, or a clu_count-based allocation). It shows only read_boot_region() and the macro.",
      "The functional fsck.c commits for Android 15 and 17 are not in the material, only the build commits. The per-major facts still report the patched region present.",
      "Why the resolver reports 7/8 lines on 14 and 6/8 on 16 is not explained.",
      "The types of EXFAT_CLUSTER_SIZE(bs) and bd->size are not shown, so the material cannot confirm whether the remaining clu_count * EXFAT_CLUSTER_SIZE(bs) comparison can still wrap.",
      "The material does not show how fsck.exfat is invoked on the device (which caller, with what privileges, on which storage).",
      "Which exfatprogs tools use DIV_ROUND_UP 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-2026-49879": {
     "cve": "CVE-2026-49879",
     "summary": "Widens block_id to uint32_t and recomputes the block-range bounds checks and loop limits in uint32_t in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (Type 3 Tag NDEF update/check), and frees the GKI command buffer when the bounds check fails.",
     "component": "platform/system/nfc (Android 14/15) and platform/packages/modules/Nfc libnfc-nci (Android 16/17), Type 3 Tag reader/writer (src/nfc/tags/rw_t3t.cc)",
     "weakness": "Integer overflow in 16-bit block range / index arithmetic guarding a heap command buffer (per the commit message and the 'Heap out-of-bounds' log), plus a command-buffer leak on the error path",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, the block-list loop used a uint16_t block_id running from first_block_to_write/first_block_to_read up to first + count. The guard before the loop (`count > UINT16_MAX - first`, logged as \"Heap out-of-bounds\" with tag 508389055) returned on error without freeing p_cmd_buf. The commit message says the range calculations and loop conditions needed to be done in uint32_t to prevent integer overflow. The material does not show the exact overflow condition that remained with the earlier guard in place, because the loop body and the derivation of the block values are not shown.",
     "fix_analysis": [
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: block_id moves from a uint16_t declaration to uint32_t (declared alongside timeout), so the loop index can hold 0x10000 without wrapping.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: the bounds check becomes `(uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000`. The sum is computed in 32 bits, and the range may end at block 65535 inclusive. On failure, GKI_freebuf(p_cmd_buf) is now called before logging and returning.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: the loop condition becomes `block_id < ((uint32_t)first_block_to_write + ndef_blocks_to_write)`.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: the same changes for the check path. block_id is widened to uint32_t, the check becomes `(uint32_t)first_block_to_read + cur_blocks_to_read > 0x10000` with GKI_freebuf(p_cmd_buf) on failure, and the loop limit is cast to uint32_t.",
      "The android_errorWriteLog tag in both error paths stays \"508389055\" rather than this commit's bug 503544522."
     ],
     "completeness": [
      "Both Type 3 Tag NDEF block-list builders in the diff (update and check) get the same three-part change: wider index, 32-bit range check, and buffer free.",
      "The new guard accepts one more block than the old one: a sum of exactly 0x10000 was rejected before and is now accepted. The widened uint32_t block_id lets the loop end at 65536.",
      "The fix does not change how first_block_to_write/first_block_to_read or the block counts are derived, or how p_cmd_buf is sized. Other command builders in rw_t3t.cc are not touched and are not shown in the material."
     ],
     "behavior_changes": [
      "When a block range is rejected, the GKI command buffer is now released instead of leaked. The function still logs \"Heap out-of-bounds\" and takes the same error return.",
      "Block ranges ending exactly at 0x10000 (last block 65535) are now accepted rather than rejected.",
      "No new error codes or API changes are visible in the diff."
     ],
     "preconditions": [
      "NFC reader/writer interaction with a Type 3 Tag during an NDEF update (rw_t3t_send_next_ndef_update_cmd) or an NDEF check (rw_t3t_send_next_ndef_check_cmd)."
     ],
     "per_major": {
      "14": "Same fix: platform/system/nfc c1fa4599a13e in android-security-14.0.0_r29, src/nfc/tags/rw_t3t.cc (Cherrypick-From / Merged-In of Change-Id I8daf5de0b16c0f17a8c0b01f65359cde2d302bff). 7 of 8 patched-region lines are present at that tag.",
      "15": "Same fix: platform/system/nfc 78dd34a6a026 in android-security-15.0.0_r17, same hunks in src/nfc/tags/rw_t3t.cc. 7 of 8 patched-region lines are present at that tag.",
      "16": "Same fix in a different location: platform/packages/modules/Nfc 92d884176dcd in android-security-16.0.0_r8, libnfc-nci/src/nfc/tags/rw_t3t.cc, same hunks. 7 of 8 patched-region lines are present at that tag.",
      "17": "Same fix in a different location: platform/packages/modules/Nfc 442e948e4be2 in android-security-17.0.0_r1, libnfc-nci/src/nfc/tags/rw_t3t.cc, same hunks. 7 of 8 patched-region lines are present at that tag."
     },
     "backport_notes": [
      "The hunks assume the earlier guard (`count > UINT16_MAX - first` with android_errorWriteLog 0x534e4554/\"508389055\") is already in both functions. On a tree without it, add the check, GKI_freebuf(p_cmd_buf) and the return together, not just the type change. On 16 and later the file is libnfc-nci/src/nfc/tags/rw_t3t.cc in packages/modules/Nfc.",
      "No fork comparison was provided, so no per-file fork verdict can be given."
     ],
     "confidence": "medium",
     "unknowns": [
      "How first_block_to_write/first_block_to_read and the block counts are derived (for example, from tag-provided NDEF attribute data), and how large p_cmd_buf is. The surrounding function bodies are not in the material.",
      "Which overflow condition remained with the earlier guard in place: the diff alone does not show a wrap that the old guard allowed.",
      "Whether the main impact addressed by this CVE is the overflow handling or the command-buffer leak on the error path.",
      "Why the bulletin rates this as Critical EoP.",
      "Which line of the patched region differs in each latest security tag (7/8 lines present).",
      "No test is included (Test: Manual)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49882": {
     "cve": "CVE-2026-49882",
     "summary": "In rw_mfc_handle_read_op (Mifare Classic reader in the NFC stack), the fix saves ndef_length and ndef_start_pos just before rw_nfc_decodeTlv on the first block read. It then treats a decode failure, or any growth of ndef_length, as a failure and restores both fields, so an inflated length no longer stays in the control block where it bounds the copy into p_ndef_buffer.",
     "component": "NFC tag reader/writer, Mifare Classic NDEF read (rw_mfc.cc): platform/system/nfc src/nfc/tags/rw_mfc.cc on Android 14/15, platform/packages/modules/Nfc libnfc-nci/src/nfc/tags/rw_mfc.cc on Android 16/17",
     "weakness": "Heap out-of-bounds write (commit title: OOB write; CWE-787). An NDEF length decoded from tag TLV data was detected as inflated but not reverted. This left stale state that bounds the copy into p_ndef_buffer. The fix refines an earlier mitigation that logged SafetyNet id 178725766.",
     "root_cause": "In rw_mfc_handle_read_op, on the first block of an NDEF read (p_mfc->work_offset == 0), rw_nfc_decodeTlv(data) parses TLV data from the tag and updates p_mfc->ndef_length and p_mfc->ndef_start_pos in the control block. Before the fix, the code saved only the length (saved_length, re-read from p_mfc->ndef_length on every call). If the decoded length was larger, it logged SafetyNet id 178725766 and set failed = true. It did not restore p_mfc->ndef_length or p_mfc->ndef_start_pos, so the inflated value stayed in p_mfc. That value is the bound of the copy loop (p_mfc->p_ndef_buffer[p_mfc->work_offset] = p[offset] while work_offset < ndef_length) and of the completion check (work_offset >= ndef_length). Because saved_length was taken from the already-inflated field on each call, the comparison would no longer catch the inflation on any later call.",
     "fix_analysis": [
      "src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: the function-scope uint16_t saved_length and its unconditional assignment are removed. Inside the work_offset == 0 branch, uint16_t saved_len = p_mfc->ndef_length and uint8_t saved_pos = p_mfc->ndef_start_pos are now captured immediately before rw_nfc_decodeTlv(data).",
      "The condition becomes `!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len`, so a TLV decode failure or any increase in ndef_length counts as a failure. If the length grew, android_errorWriteLog(0x534e4554, \"178725766\") is emitted. Then p_mfc->ndef_length = saved_len and p_mfc->ndef_start_pos = saved_pos restore the control-block state, and failed = true is set.",
      "The copy guard is simplified from `!failed && saved_length >= p_mfc->ndef_length` to `!failed`, and the separate else branch (log + failed = true) is deleted. The copy loop body is unchanged (bounded by offset < len and work_offset < ndef_length). Within the shown region, ndef_length now cannot exceed its value before decoding.",
      "The commit message says control flow was also changed to stop further block reads on failure. The visible diff (1 file, +11/-11) has no separate hunk for this beyond the rollback and the failed flag."
     ],
     "completeness": [
      "Covers the site in rw_mfc_handle_read_op where rw_nfc_decodeTlv rewrites ndef_length/ndef_start_pos. Both fields are now restored on failure. Before the fix, the length was compared and nothing was restored.",
      "Decoded lengths smaller than or equal to the saved length are still accepted. Only growth is rejected.",
      "The fix relies on p_mfc->ndef_length at entry matching the size of p_mfc->p_ndef_buffer. The diff adds no explicit check of the copy destination against a buffer size, and the allocation and initialisation of p_ndef_buffer and ndef_length are not in the material.",
      "rw_nfc_decodeTlv itself is not modified. The fix is a rollback in the caller, not a change to the TLV parser.",
      "This commit does not touch other users of rw_nfc_decodeTlv or of ndef_length in rw_mfc.cc. The material does not show whether they have the same pattern."
     ],
     "behavior_changes": [
      "If the first-block TLV decode yields a larger NDEF length, the read now fails and the original ndef_length and ndef_start_pos are restored. Before, the inflated values stayed in the control block.",
      "The SafetyNet log android_errorWriteLog(0x534e4554, \"178725766\") is now emitted only when inflation is detected. The old else branch also emitted it on a plain TLV decode failure.",
      "The 'FAILED finding TLV' debug message is now also logged for the inflation case.",
      "The per-block copy loop bounds are unchanged."
     ],
     "preconditions": [
      "NFC is enabled, a Mifare Classic tag is presented, and the stack performs an NDEF read on it, so block read responses reach rw_mfc_handle_read_op.",
      "The relevant decision happens on the first block of the read (p_mfc->work_offset == 0), where rw_nfc_decodeTlv parses TLV data supplied by the tag and can set ndef_length larger than its value before decoding.",
      "The code runs in the NFC stack (the commit message names com.android.nfc). The bulletin rates this Critical, type RCE."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc 7eaf8e16d9ad in android-security-14.0.0_r29, src/nfc/tags/rw_mfc.cc, log macro DLOG_IF(INFO, nfc_debug_enabled). Resolved; 7/8 lines of the patched region present at that tag.",
      "15": "same fix: platform/system/nfc 5dd24f8b4a6d in android-security-15.0.0_r17, identical hunk using LOG(VERBOSE). Resolved; 7/8 lines present.",
      "16": "same fix in a different repository: platform/packages/modules/Nfc b79e5fd8cf14 in android-security-16.0.0_r8, libnfc-nci/src/nfc/tags/rw_mfc.cc, identical hunk at line offsets 1197/1266. Resolved; 7/8 lines present.",
      "17": "same fix in a different repository: platform/packages/modules/Nfc 19fc329133fe in android-security-17.0.0_r1, libnfc-nci/src/nfc/tags/rw_mfc.cc, identical hunk at 1286/1355. Resolved; 7/8 lines present."
     },
     "backport_notes": [
      "Single self-contained hunk in rw_mfc_handle_read_op. Across versions only two things differ: the file path (src/nfc/tags/rw_mfc.cc in platform/system/nfc on 14/15, libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc on 16/17) and the debug log macro (DLOG_IF vs LOG(VERBOSE)). Trees without the earlier saved_length/else-branch mitigation will need the hunk applied by hand."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of rw_mfc_handle_read_op (how 'failed' is handled after the shown region) is not in the material. Neither is how p_ndef_buffer and ndef_length are allocated and initialised, so the buffer size the copy depends on is not visible.",
      "Whether later block reads still happened after the old failure path is not shown. The commit message says control flow changed to prevent further reads, but no separate hunk for this is visible.",
      "The body of rw_nfc_decodeTlv is not shown. It is not confirmed whether ndef_start_pos can change in a way that matters independently of the length; the fix rolls it back anyway.",
      "Which of the two bug ids (503557185, 503553866) corresponds to which report."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49884": {
     "cve": "CVE-2026-49884",
     "summary": "In rw_mfc_handle_read_op (Mifare Classic NDEF read), the fix snapshots p_mfc->ndef_length and p_mfc->ndef_start_pos before rw_nfc_decodeTlv, treats an inflated ndef_length as a failure, and rolls both fields back to their pre-decode values. This keeps the inflated length out of the control block.",
     "component": "platform/system/nfc (Android 14/15) and platform/packages/modules/Nfc libnfc-nci (Android 16/17), NFC reader/writer Mifare Classic tag handling (rw_mfc.cc)",
     "weakness": "Heap out-of-bounds write (per commit message): a tag-derived length (ndef_length) that failed validation was left in the control block, and that length bounds the writes into p_ndef_buffer (missing rollback after failed validation)",
     "root_cause": "In rw_mfc.cc rw_mfc_handle_read_op, when work_offset == 0 the code calls rw_nfc_decodeTlv(data), which re-parses the NDEF TLV from the tag response and updates p_mfc->ndef_length and p_mfc->ndef_start_pos. The old code compared a local copy (saved_length) with the new p_mfc->ndef_length and set failed = true when the length grew. It did not restore the control block fields, so the inflated, tag-controlled values stayed in p_mfc. The copy loop writes p[offset] into p_mfc->p_ndef_buffer[p_mfc->work_offset] and is bounded only by p_mfc->ndef_length. Later use of that persisted length could therefore lead to writes past the end of p_ndef_buffer. The material does not show the exact sequence or the buffer size, so this part is inferred. The commit message calls it a heap buffer overflow in com.android.nfc during Mifare Classic NDEF reads.",
     "fix_analysis": [
      "src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: removes the function-scope saved_length. Inside the work_offset == 0 branch it now snapshots saved_len = p_mfc->ndef_length and saved_pos = p_mfc->ndef_start_pos immediately before rw_nfc_decodeTlv(data).",
      "Same function: the failure condition becomes `!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len`. On failure it logs android_errorWriteLog(0x534e4554, \"178725766\") when inflation is detected, restores p_mfc->ndef_length = saved_len and p_mfc->ndef_start_pos = saved_pos, and sets failed = true. Because offset = p_mfc->ndef_start_pos is assigned after this block, offset also gets the restored start position.",
      "Same function: the copy loop guard becomes `if (!failed)`. The old `saved_length >= p_mfc->ndef_length` comparison and its else branch (log + failed = true) are deleted. The loop body that writes into p_mfc->p_ndef_buffer[p_mfc->work_offset] is unchanged."
     ],
     "completeness": [
      "Covers the re-derivation of ndef_length shown in the diff (the work_offset == 0 TLV decode). After a failed decode, the control block keeps the values it had before that decode.",
      "A decoded ndef_length smaller than or equal to the original is still accepted. Only growth is rejected. A change of ndef_start_pos without length growth is also accepted. Rollback happens only on failure.",
      "The copy loop still bounds writes only by p_mfc->ndef_length against work_offset. This function has no independent check against the p_ndef_buffer allocation size.",
      "The commit message says control flow was updated to prevent further block reads on failure. The diff contains only the hunk shown. The code after `if (p_mfc->work_offset >= p_mfc->ndef_length)` is not in the material, so how `failed` ends the operation cannot be verified here."
     ],
     "behavior_changes": [
      "When a Mifare Classic tag's first-block TLV decodes to a larger NDEF length than before, the read now fails (failed = true) and the original ndef_length/ndef_start_pos are restored. Previously the inflated values stayed in the control block.",
      "The android_errorWriteLog(0x534e4554, \"178725766\") event is now emitted only for the inflation case, not for plain rw_nfc_decodeTlv failures.",
      "The debug/verbose log line \" FAILED finding TLV\" now also fires for the inflation case."
     ],
     "preconditions": [
      "NFC reader mode processing a Mifare Classic tag through rw_mfc.cc, with an NDEF read in progress (rw_mfc_handle_read_op handling data with work_offset == 0).",
      "The tag returns block data whose NDEF TLV decodes to a larger length than the ndef_length previously held in the control block.",
      "The bulletin rates the issue Critical, type RCE. The commit message places it in com.android.nfc."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc 7eaf8e16d9ad in android-security-14.0.0_r29, src/nfc/tags/rw_mfc.cc, log macro DLOG_IF(INFO, nfc_debug_enabled).",
      "15": "same fix: platform/system/nfc 5dd24f8b4a6d in android-security-15.0.0_r17. The hunk is identical except for LOG(VERBOSE) logging.",
      "16": "same fix: platform/packages/modules/Nfc b79e5fd8cf14 in android-security-16.0.0_r8, file at libnfc-nci/src/nfc/tags/rw_mfc.cc. Identical hunk with LOG(VERBOSE).",
      "17": "same fix: platform/packages/modules/Nfc 19fc329133fe in android-security-17.0.0_r1, libnfc-nci/src/nfc/tags/rw_mfc.cc. Identical hunk with LOG(VERBOSE)."
     },
     "backport_notes": [
      "Single self-contained hunk in rw_mfc_handle_read_op. The only per-branch difference is the logging macro (DLOG_IF on 14, LOG(VERBOSE) on 15+). On 16+ the file is under libnfc-nci/ in packages/modules/Nfc instead of system/nfc, so the patch path must be adjusted."
     ],
     "confidence": "medium",
     "unknowns": [
      "The code after the hunk is not in the material: how `failed` and `done` end the read, and what reads the next block. So the exact path from the persisted inflated ndef_length to the overflow is inferred.",
      "Where p_ndef_buffer is allocated and with what size is not shown.",
      "The internals of rw_nfc_decodeTlv and what it validates are not shown.",
      "Why the resolver reports only 7/8 lines of the patched region present at every tag, including the tags that contain the fix, is not explained by the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49919": {
     "cve": "CVE-2026-49919",
     "summary": "Replaces an unchecked `size = rows * pitch; FT_ALLOC(buf, size)` in tt_face_colr_blend_layer (src/sfnt/ttcolr.c) with FT_ALLOC_MULT(buf, rows, pitch), so the FreeType memory layer detects the multiplication overflow instead of the code allocating an undersized buffer.",
     "component": "platform/external/freetype, sfnt driver, COLR (color glyph layer) blending in src/sfnt/ttcolr.c",
     "weakness": "Integer overflow in an allocation size computation, leading to an undersized allocation",
     "root_cause": "In src/sfnt/ttcolr.c tt_face_colr_blend_layer, the temporary buffer was allocated as FT_ALLOC(buf, size), where size was the plain product rows * pitch with no overflow check. If that product wraps, FT_ALLOC receives a small size and the allocation succeeds with an undersized buffer. Later code in the function works with buf and dstSlot->bitmap.buffer (only the start of that code is shown). The commit message says the overflow is now detected in ft_mem_qrealloc and references upstream issue #1389.",
     "fix_analysis": [
      "src/sfnt/ttcolr.c:tt_face_colr_blend_layer: removes the local computation `size = rows * pitch;` and replaces `FT_ALLOC( buf, size )` with `FT_ALLOC_MULT( buf, rows, pitch )`. FT_ALLOC_MULT passes the count and item size separately to the FreeType memory layer, which (per the commit message) detects the multiplication overflow in ft_mem_qrealloc. On failure the function returns `error` before reaching the code that follows the allocation.",
      "Single file, single hunk, one line added and two removed; no other code paths are touched."
     ],
     "completeness": [
      "Covers only the one allocation of `buf` in tt_face_colr_blend_layer. The overflow-prone product is no longer computed by hand there.",
      "The diff adds no explicit validation of rows or pitch. It relies only on the allocator's overflow check at allocation time.",
      "The material does not show the rest of tt_face_colr_blend_layer or the other FT_ALLOC sites in ttcolr.c. Whether other hand-computed sizes exist in the same file cannot be judged from the material.",
      "The declaration of the local `size` variable is not shown in the hunk. If it is still declared and now unused, the diff does not remove it."
     ],
     "behavior_changes": [
      "When rows * pitch would overflow, tt_face_colr_blend_layer now returns the error from the allocator instead of continuing with a small buffer. For such fonts the color glyph layer blend is aborted.",
      "No behavior change for sizes that do not overflow."
     ],
     "preconditions": [
      "A font with a COLR table whose glyph layer rendering reaches tt_face_colr_blend_layer with rows and pitch values whose product wraps. The specific triggering geometry is not shown in the material.",
      "Per index search, the platform reaches FreeType for font rendering: frameworks/base/libs/hwui/utils/TypefaceUtils.h exposes FreeTypeFontMgr() to turn bytes into typefaces via FreeType, and minikin (LayoutCore.cpp, Font.cpp) handles font loading and shaping. Whether a given path renders COLR layers is not shown."
     ],
     "per_major": {
      "14": "same fix: platform/external/freetype ab9caf444825 in android-security-14.0.0_r29, identical hunk at line 1803 (cherry-pick of 4581ed553986, Change-Id I2a11ad1dcce5).",
      "15": "same fix: platform/external/freetype 9f67316aa41d in android-security-15.0.0_r17, identical hunk at line 1803.",
      "16": "same fix: platform/external/freetype 6770ef4d9faf in android-security-16.0.0_r8, identical hunk at line 1803 (same pre/post blob ids as Android 15).",
      "17": "same fix: platform/external/freetype 65f3c6ba20e7 in android-security-17.0.0_r1, identical hunk shifted to line 1805."
     },
     "backport_notes": [
      "Self-contained change with no dependencies. It applies as long as the tree has the `size = rows * pitch; FT_ALLOC(buf, size)` pattern in tt_face_colr_blend_layer and provides FT_ALLOC_MULT; Google applied the same hunk on all four branches. Check whether the local `size` variable becomes unused, which matters if unused-variable warnings are treated as errors (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of tt_face_colr_blend_layer (how rows and pitch are derived, the copy loop and its bounds) is not in the provided context. The context shown is tt_face_load_colr and tt_face_free_colr.",
      "Which out-of-bounds access, if any, followed the undersized allocation before the fix.",
      "The exact error code returned when ft_mem_qrealloc detects the overflow.",
      "Whether the local `size` variable is still declared or used elsewhere in the function after the change.",
      "Which Android font-rendering paths actually exercise COLR layer blending through FreeType (Skia/hwui vs other consumers)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49921": {
     "cve": "CVE-2026-49921",
     "summary": "Replaces the fixed 400-byte (SDP_MAX_ATTR_LEN) heap buffer in sdpu_build_partial_attrib_entry with an allocation sized to the serialized attribute. It also rejects attributes whose payload exceeds SDP_MAX_ATTR_LEN in that helper, in both SDP server request handlers and at database insertion, and adds an offset-vs-length check before the partial memcpy.",
     "component": "platform/packages/modules/Bluetooth, system/stack/sdp (SDP server: sdp_server.cc, sdp_utils.cc, sdp_db.cc)",
     "weakness": "Heap buffer overflow from a fixed-size allocation with missing or misplaced bounds checks (out-of-bounds write, then out-of-bounds read)",
     "root_cause": "sdpu_build_partial_attrib_entry (system/stack/sdp/sdp_utils.cc) allocated exactly SDP_MAX_ATTR_LEN (400) bytes. It then called sdpu_build_attrib_entry to serialize the whole attribute (type/length header plus p_attr->len payload bytes) into that buffer, with no check that the serialized entry fits. The only guard was in the callers process_service_attr_req and process_service_search_attr_req (sdp_server.cc): a check 'attr_len >= SDP_MAX_ATTR_LEN' that existed only in the 'rem_len < attr_len' (first partial send) branch. It was absent from the continuation branch ('if (p_ccb->cont_info.attr_offset)'), which also calls sdpu_build_partial_attrib_entry. In addition, SDP_AddAttributeToRecord (sdp_db.cc) only truncated against the record pad (SDP_MAX_PAD_LEN - free_pad_ptr), so an attribute with a payload longer than SDP_MAX_ATTR_LEN could be stored in the local database. An oversized attribute reaching the helper overflowed the 400-byte heap buffer during serialization. The following memcpy from &p_attr_buff[*offset] could then read beyond the buffer, and the caller check (attr_len < attr_offset) did not reject *offset == attr_len.",
     "fix_analysis": [
      "system/stack/sdp/sdp_utils.cc:sdpu_build_partial_attrib_entry: first computes attr_len = sdpu_get_attrib_entry_len(p_attr). It returns p_out unchanged if p_attr->len > SDP_MAX_ATTR_LEN, allocates osi_malloc(attr_len) (the exact serialized size instead of a fixed 400) and returns on nullptr, and only then calls sdpu_build_attrib_entry. It adds 'if (*offset >= attr_len)', which frees the buffer and returns p_out, so the memcpy source range always lies inside the allocation. The existing clamp of 'len' to SDP_MAX_ATTR_LEN is kept.",
      "system/stack/sdp/sdp_server.cc:process_service_attr_req and process_service_search_attr_req (14): after attr_len = sdpu_get_attrib_entry_len(p_attr), a new 'if (p_attr->len > SDP_MAX_ATTR_LEN)' check logs, calls sdpu_build_n_send_error(p_ccb, trans_num, SDP_NO_RESOURCES, NULL) and returns. It runs before the continuation, first-partial and full-entry branches. The old 'attr_len >= SDP_MAX_ATTR_LEN' checks inside the first-partial branches, which compared the full entry length rather than the payload and covered only one branch, are removed. This is most likely the 'mistaken bounds checks' correction that the commit message mentions.",
      "system/stack/sdp/sdp_db.cc:SDP_AddAttributeToRecord: the overflow condition becomes 'attr_len > SDP_MAX_ATTR_LEN || free_pad_ptr + attr_len >= SDP_MAX_PAD_LEN'. For TEXT_STR_DESC_TYPE the value is truncated to max_allowed_len = min(SDP_MAX_ATTR_LEN, SDP_MAX_PAD_LEN - free_pad_ptr) and NUL-terminated at p_val[max_allowed_len - 1]. Other types get attr_len = 0 as before. This keeps attributes with a payload over 400 bytes out of the local server database.",
      "Tests: stack_sdp_test.cc adds write_read_max_attr_len_slicing (a 400-byte attribute served over continuation chunks with max_list_len 50) and SDP_AddAttribute__exceed_max_attr_len_truncation. stack_sdp_utils_test.cc adds exceeds_max_attr_len and within_bounds tests for sdpu_build_partial_attrib_entry. On 14 it also maps the osi_malloc/osi_free mocks to malloc/free in the fixture."
     ],
     "completeness": [
      "Covers all three layers shown: the helper itself, both server handlers that call it (process_service_attr_req and process_service_search_attr_req), and database insertion via SDP_AddAttributeToRecord.",
      "The helper is protected independently of its callers: it rejects payloads over SDP_MAX_ATTR_LEN, allocates exactly attr_len bytes and rejects *offset >= attr_len.",
      "The full-entry branch in the handlers (sdpu_build_attrib_entry writing directly into p_ccb->rsp_list when rem_len >= attr_len) is unchanged in body, but the new p_attr->len check now runs before it (on 14 after attr_len is computed, on 15+ at the top of the if (p_attr) block).",
      "When sdpu_build_partial_attrib_entry rejects, it returns p_out without advancing *offset. On 14, in the continuation branch, if the offset is below attr_len the caller breaks with cont_info.attr_offset unchanged. If *offset == attr_len, the caller sees attr_offset == attr_len and resets attr_offset to 0. There is no caller-side error response for the helper's offset rejection.",
      "sdp_db.cc still writes a NUL terminator into the caller-supplied p_val buffer during truncation (existing behavior, only the index computation changed)."
     ],
     "behavior_changes": [
      "On 14, ServiceAttribute and ServiceSearchAttribute requests that reach a stored attribute with a payload over SDP_MAX_ATTR_LEN now get an SDP_NO_RESOURCES error response on every path. Before, this happened only in the first-partial branch and was based on entry length.",
      "On 14, attributes whose serialized entry length is >= 400 but whose payload is <= 400 were rejected with SDP_NO_RESOURCES when they needed partial sending. They are now served across continuation chunks (exercised for a 400-byte payload by write_read_max_attr_len_slicing).",
      "SDP_AddAttribute now truncates TEXT_STR_DESC_TYPE values to at most SDP_MAX_ATTR_LEN (400) bytes, logging a warning. It stores other types longer than that with attr_len = 0.",
      "sdpu_build_partial_attrib_entry allocates attr_len bytes instead of a fixed 400. It returns p_out unchanged (no copy, offset unchanged) for oversized attributes, allocation failure, or *offset >= attr_len."
     ],
     "preconditions": [
      "A local SDP record must contain an attribute whose serialized entry exceeds 400 bytes. The diff shows SDP_AddAttributeToRecord could previously store a payload longer than SDP_MAX_ATTR_LEN when the record pad had room. Per index search it is reached from SDP_AddAttribute and SDP_AddProfileDescriptorListToRecord in sdp_db.cc.",
      "A remote SDP client over L2CAP sends a ServiceAttribute or ServiceSearchAttribute request, handled by process_service_attr_req or process_service_search_attr_req, such that the attribute is sent partially or through a continuation request. The previously unguarded path was the continuation branch.",
      "Bluetooth must be enabled and the SDP server reachable. The bulletin classifies this as remote code execution, severity Critical."
     ],
     "per_major": {
      "14": "adapted fix (51b0cc33b16c in android-security-14.0.0_r29). The new p_attr->len check is inserted after the attr_len computation in both handlers, and the old 'attr_len >= SDP_MAX_ATTR_LEN' checks in the partial branches are removed. In sdpu_build_partial_attrib_entry the offset check comes after the len clamp. Uses SDP_TRACE_ERROR logging and SDP_NO_RESOURCES. 7/8 lines of the patched region are present at the tag.",
      "15": "adapted fix (c738041c8ce8 in android-security-15.0.0_r17). Same three-layer change, but the p_attr->len check sits at the top of the 'if (p_attr)' block, before the AVRCP/HFP version tweaks. The offset check comes before the len clamp, and logging uses log::error/log::warn. The diff does not show removal of the old partial-branch checks. 8/8 lines of the patched region are present.",
      "16": "adapted fix (947e8bbe7dc6 in android-security-16.0.0_r8). Same shape as 15, using tSDP_STATUS::SDP_NO_RESOURCES and reinterpret_cast. The test uses mock_stack_l2cap_interface_ EXPECT_CALL. 7/8 lines of the patched region are present.",
      "17": "adapted fix (4a9e8875e210 in android-security-17.0.0_r1). Same shape as 16. The database function is the static sdp_add_attribute_to_record instead of SDP_AddAttributeToRecord. 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Each branch has a single commit with no dependency chain. For forks based on 14, the hunk placement differs from 15+. On 14 the server-side check sits after attr_len is computed and the old partial-branch checks are deleted. On 15+ the check sits directly under 'if (p_attr)'. Either placement guards both the continuation and first-partial calls into sdpu_build_partial_attrib_entry.",
      "The sdp_utils.cc change is self-contained and protects the helper regardless of its callers. On 14 the accompanying stack_sdp_utils_test.cc change also maps the osi_malloc/osi_free mocks to malloc/free in the fixture."
     ],
     "confidence": "high",
     "unknowns": [
      "The numeric value of SDP_MAX_PAD_LEN, and therefore how far beyond 400 bytes a stored attribute could have been before the fix.",
      "Which local profiles or app-facing APIs can register an attribute longer than SDP_MAX_ATTR_LEN. The material only shows the stack-level SDP_AddAttribute entry point.",
      "Whether the old 'attr_len >= SDP_MAX_ATTR_LEN' checks in the partial branches still exist on 15/16/17 (their diffs do not show removal).",
      "Why the per-major resolver reports 7/8 patched-region lines on 14, 16 and 17 (not verifiable from the material).",
      "The width of tSDP_ATTRIBUTE::len relative to the uint16_t attr_len returned by sdpu_get_attrib_entry_len."
     ],
     "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-2026-49932": {
     "cve": "CVE-2026-49932",
     "summary": "Hardens PduParser.parseParts in frameworks/base telephony/common so malformed MMS multipart PDUs are rejected (parse returns null) instead of causing OutOfMemoryError, StackOverflowError, NullPointerException or ArrayIndexOutOfBoundsException. It rejects a declared part dataLength larger than the bytes left in the stream, caps recursion into multipart/vnd.wap.multipart.alternative parts at depth 30, and checks that the nested result is non-null and non-empty before taking its first part.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parser (com.google.android.mms.pdu.PduParser)",
     "weakness": "Missing input validation in a parser: an untrusted declared length is used for allocation without a bounds check, recursion on nested multipart parts is unbounded, and the recursive result is indexed without a null/empty check",
     "root_cause": "In PduParser.parseParts (telephony/common/com/google/android/mms/pdu/PduParser.java), when a part's dataLength was > 0 the parser immediately did `new byte[dataLength]` without comparing it to pduDataStream.available(). A hostile length could therefore cause an oversized allocation (OutOfMemoryError, per the commit message). When the part content type was ContentType.MULTIPART_ALTERNATIVE, parseParts recursed on the part data with no depth limit, so deeply nested parts could exhaust the stack (StackOverflowError). The recursive result childBody was then used as childBody.getPart(0) without checking that childBody was non-null (NullPointerException) or had at least one part (ArrayIndexOutOfBoundsException). The new tests feed short PDUs, labelled as NPE, AIOOBE and OOM triggers, through PduParser.parse() and expect null.",
     "fix_analysis": [
      "PduParser.java: adds the constant MAX_PARSING_DEPTH = 30, with a comment explaining the value (real-world nesting below 5, background thread stacks 256KB-512KB).",
      "PduParser.java:parseParts(ByteArrayInputStream): the existing protected method becomes a thin wrapper that calls a new private parseParts(ByteArrayInputStream, int depth) with depth 0. The external signature is unchanged.",
      "PduParser.java:parseParts(stream, depth): right after the existing null-stream check, logs with Log.e and returns null if depth > MAX_PARSING_DEPTH, which bounds the recursion.",
      "PduParser.java:parseParts(stream, depth): inside `if (dataLength > 0)`, before `new byte[dataLength]`, logs with Log.e and returns null if dataLength > pduDataStream.available(). The allocation and the following read are now bounded by the input actually remaining.",
      "PduParser.java:parseParts(stream, depth): the multipart/alternative branch now recurses with depth + 1, and logs with Log.e and returns null if childBody == null || childBody.getPartsNum() == 0. childBody.getPart(0) is therefore only reached on a non-empty body.",
      "tests/TelephonyCommonTests/.../PduParserTest.java (new): three tests (testAioobePayload, testNpePayload, testOomPayload) build PduParser(payload, true) and assert that parse() returns null for the AIOOBE, NPE and OOM trigger inputs. Run with atest TelephonyCommonTests:PduParserTest."
     ],
     "completeness": [
      "All three guards are confined to PduParser.parseParts. The depth limit applies only to the self-recursion through multipart/vnd.wap.multipart.alternative, which is the only recursive call visible in the diff.",
      "The dataLength bound only covers the part-data allocation in parseParts. headerLength and other lengths parsed elsewhere in the file are not visible in the diff and are not changed.",
      "The commit message lists a fourth item ('Optimize skipWapValue and parameter skipping to use InputStream.skip instead of allocating temporary byte arrays'). The provided diff has no changes to skipWapValue or parameter skipping: the 32 changed lines in PduParser.java are all in the constants block and parseParts.",
      "On any of the new failure conditions the whole body parse fails (return null); the offending part is not skipped.",
      "Only the frameworks/base copy is fixed. Per index search, separate PduParser copies exist in packages/apps/Messaging and development/samples/ApiDemos, and this commit does not touch them (see potential_variants)."
     ],
     "behavior_changes": [
      "PduParser.parse() now returns null, instead of throwing or exhausting memory or stack, for PDUs whose multipart/alternative nesting exceeds the depth limit, whose declared part dataLength exceeds the remaining stream bytes, or whose nested multipart/alternative body fails to parse or has zero parts.",
      "Three new Log.e messages under LOG_TAG: 'parseParts: recursion depth limit exceeded', 'parseParts: dataLength ... exceeds available stream bytes', 'parseParts: nested multipart alternative parsing failed or empty'.",
      "Stricter validation can reject PDUs the old code accepted, for example a part whose declared length slightly exceeds the remaining bytes.",
      "New test class PduParserTest added to the TelephonyCommonTests module."
     ],
     "preconditions": [
      "Attacker-controlled MMS PDU bytes must reach PduParser.parse(). Per index search, packages/services/Mms MmsService.parsePduForAnyCarrier calls new PduParser(data, true).parse(). Its catch only covers RuntimeException, so NPE/AIOOBE would be caught there but OutOfMemoryError/StackOverflowError (Errors) would not. Where that data comes from is not shown.",
      "Per index search, the parse() implementations in the Messaging and ApiDemos copies call parseParts only for MESSAGE_TYPE_SEND_REQ and MESSAGE_TYPE_RETRIEVE_CONF. The frameworks/base parse() is not shown.",
      "The recursion, NPE and AIOOBE paths need a part with content type multipart/vnd.wap.multipart.alternative and dataLength > 0. The OOM path needs a declared dataLength larger than the remaining input."
     ],
     "per_major": {
      "14": "same fix: RESTRICT AUTOMERGE cherry-pick 8325de4e11e9 in android-security-14.0.0_r29 (Change-Id If97a6540b86380a01700fca887813ff117922e58); 8/8 patched lines present.",
      "15": "same fix: RESTRICT AUTOMERGE commit b86eef8c2ffe in android-security-15.0.0_r17; identical diff and identical pre/post blob hashes (62eac7ac942a..ecab9562f72e) to Android 14; 8/8 lines present.",
      "16": "same fix: RESTRICT AUTOMERGE commit a28fb127a57b in android-security-16.0.0_r8; identical diff and blob hashes to 14/15; 8/8 lines present.",
      "17": "same fix: RESTRICT AUTOMERGE commit 1c37e9ef69ea in android-security-17.0.0_r1; same hunks at the same line offsets but a different base blob (ffd2a8b419bb..15448a6a77f4); 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit. The PduParser.java hunks are identical across 14/15/16/17 and only touch the constants block and parseParts. The protected parseParts(ByteArrayInputStream) signature is preserved.",
      "The new PduParserTest.java needs the tests/TelephonyCommonTests module in the target tree. Vendor trees that carry their own PduParser copy (for example a Messaging mmslib copy, per index search) need to be checked and patched separately."
     ],
     "confidence": "high",
     "unknowns": [
      "Which process the bulletin's Critical DoS rating refers to; the material does not say.",
      "Whether the 'skipWapValue / parameter skipping' optimization named in the commit message landed in a separate change; it is not in any of the provided diffs.",
      "The full set of callers of PduParser.parse() in each release, and the source of the data passed to MmsService.parsePduForAnyCarrier.",
      "Whether the TelephonyCommonTests module exists and builds on every security branch the change was applied to.",
      "Whether the Messaging and ApiDemos PduParser copies contain the same unguarded allocation and recursion; the index snippets are cut off."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55256": {
     "cve": "CVE-2026-55256",
     "summary": "Adds null checks in PduParser.parsePartHeaders so that a null Content-Disposition token-text value, a null filename parameter, or a null custom text header name or value is no longer passed on or dereferenced, which prevents NullPointerExceptions while parsing an MMS PDU.",
     "component": "platform/frameworks/base, telephony MMS PDU parser (telephony/common/com/google/android/mms/pdu/PduParser.java); mirrored copy in platform/development samples/ApiDemos mmslib",
     "weakness": "Missing null check on parser return value (NULL pointer dereference leading to uncaught NullPointerException)",
     "root_cause": "In PduParser.parsePartHeaders, values from the parser were used without a null check in three places. The parseWapString(pduDataStream, TYPE_TEXT_STRING) result for the Token-text form of Content-Disposition went straight to part.setContentDisposition(...). The parseWapString result for the P_FILENAME parameter went straight to part.setFilename(...). In the custom text header branch, tempHeader was dereferenced via new String(tempHeader) in the Content-Transfer-Encoding comparison, and tempValue (a parseWapString result) was passed to part.setContentTransferEncoding. The commit message describes these as potential NPEs, and the bulletin rates the issue as a Critical DoS. The added guards imply these values can be null; the conditions under which parseWapString returns null are not shown.",
     "fix_analysis": [
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (frameworks/base bcba9a47c23e): the Content-Disposition Token-text branch now stores the parseWapString result in a local byte[] contentDisposition and calls part.setContentDisposition only when it is non-null.",
      "Same function: the P_FILENAME branch stores the parseWapString result in byte[] filename and calls part.setFilename only when it is non-null.",
      "Same function: the custom text header branch replaces 'if (true == CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader)))' with 'if (null != tempHeader && null != tempValue && ...)'. As a result, new String(tempHeader) is never evaluated on null and setContentTransferEncoding is never passed null.",
      "samples/ApiDemos/src/com/example/android/mmslib/pdu/PduParser.java:parsePartHeaders (development 6aa9341a7695): the same three hunks are applied to the sample copy of the parser.",
      "In all three cases a null value is now skipped (the field is not set on the PduPart) and parsing continues. The diff adds no error return."
     ],
     "completeness": [
      "Covers the three spots in parsePartHeaders named in the commit message: Content-Disposition Token-text, the filename parameter, and the custom text header name/value pair.",
      "Only parsePartHeaders is changed. The context shows parseHeaders calling new String(bVal) on a parseWapString result inside an 'if (LOCAL_LOGV)' block without a null check (see potential_variants). This fix leaves it unchanged.",
      "The fix does not change parseWapString or the PduPart setters. It only guards these call sites.",
      "The production copy (frameworks/base) and the ApiDemos sample copy (development) both received the same change."
     ],
     "behavior_changes": [
      "If the Content-Disposition Token-text value or the filename parameter comes back null, the PduPart now has that field unset instead of throwing an exception.",
      "A custom text header with a null name or value is now ignored instead of throwing. Content-Transfer-Encoding is set only when both the name and the value are non-null.",
      "The diff adds no error codes or logging. Parsing continues past the affected field."
     ],
     "preconditions": [
      "Parsing must reach PduParser.parsePartHeaders, the per-part header parsing path.",
      "The part headers must contain a Content-Disposition in Token-text form, a filename parameter, or a custom text header for which the parsed value or name is null.",
      "How a PDU reaches the parser (for example an incoming MMS) is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: frameworks/base bcba9a47c23e and development 6aa9341a7695, both in android-security-14.0.0_r29. Both are cherry-picks with identical hunks (Merged-In I4cad171d30b14f3b6fd9dd2a0ef6bb1e5320665d / Ia8ea444778992ac7a3c2cdfab855e3b1f7e9c53f).",
      "15": "same fix: frameworks/base e2b80df1360a in android-security-15.0.0_r17, identical diff (same blob ids ecab9562f72e -> 6b3135945e53).",
      "16": "same fix: frameworks/base 01f51483eb2f in android-security-16.0.0_r8, identical diff.",
      "17": "same fix: development 21dbe0e7ba1f in android-security-17.0.0_r1 (ApiDemos copy, identical diff). The resolver reports 17 as resolved, but the material does not show a frameworks/base commit for 17."
     },
     "backport_notes": [
      "The two commits are in different repos and carry the same hunks; neither depends on the other. The pre-fix blob of telephony/common PduParser.java (ecab9562f72e) is the same on 14, 15 and 16, so the patch should apply cleanly to trees that have that version of the file.",
      "No 'Fix in fork' section was provided, so no per-file fork verdict can be reported."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the NPE comes from the PduPart setters (setContentDisposition/setFilename) or only from new String(tempHeader); the setter bodies are not in the material.",
      "The body of parseWapString, and which malformed input makes it return null, are not shown.",
      "Where tempHeader is assigned is not in the shown context.",
      "Which process would crash, and so why the bulletin rates this Critical, is not shown. The delivery path to PduParser is not in the material.",
      "No frameworks/base commit is listed for Android 17, and the meaning of '6/8 lines of the patched region present' at each tag is not explained.",
      "Which copy of the parser (frameworks/base or the ApiDemos sample) is actually used on devices is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48564": {
     "cve": "CVE-2025-48564",
     "summary": "IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that single string to both the IPackageManager.canForwardTo check and a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload, so the type used for the cross-profile check is the same as the type used for target resolution; the frameworks/base copy also uses getLaunchedFromPackage() for the privileged skip-consent check, and both copies sanitize the intent selector on the resolver path.",
     "component": "platform/frameworks/base (core: IntentForwarderActivity, PackageManager/ApplicationPackageManager) and platform/packages/modules/IntentResolver (IntentForwarderActivity, cross-profile intents checker)",
     "weakness": "Time-of-check/time-of-use: the intent's resolved type was derived separately for the cross-profile canForwardTo check and for the later activity resolution. Two related gaps were also fixed: the intent selector was not sanitized on one path, and getCallingPackage was used for a privilege check.",
     "root_cause": "In IntentForwarderActivity.onCreate, the forwarding decision and the target resolution each derived the intent's type on their own. canForwardInner called intent.resolveTypeIfNeeded(contentResolver) and passed the result to IPackageManager.canForwardTo, which per index search enforces INTERACT_ACROSS_USERS_FULL in ComputerEngine.canForwardTo. After that, mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which called intent.resolveTypeIfNeeded(mContext.getContentResolver()) again before mPM.resolveIntent. The commit message states that 'allowing the intent to declare its own type can lead to a security vulnerability if the intent changes its type after the IntentForwarder#canForward check'. Two related gaps were fixed in the same change: the privileged EXTRA_SKIP_USER_CONFIRMATION path identified the caller with getCallingPackage() instead of getLaunchedFromPackage(), and on the path that hands intentReceived to the resolver via startActivityAsCaller only the outer intent was sanitized, not its selector.",
     "fix_analysis": [
      "frameworks/base core/java/android/content/pm/PackageManager.java: adds two hidden overloads, resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The javadoc says resolvedType is 'specified explicitly when the data type contained in the intent cannot be trusted'; null falls back to Intent.resolveTypeIfNeeded.",
      "frameworks/base core/java/android/app/ApplicationPackageManager.java: implements the new overloads. The terminal resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) passes resolvedType to mPM.resolveIntent when it is non-null and only calls intent.resolveTypeIfNeeded when it is null. The existing resolveActivity/resolveActivityAsUser overloads now delegate with resolvedType = null.",
      "frameworks/base core/java/com/android/internal/app/IntentForwarderActivity.java onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...) and then to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). The last parameter of canForward and canForwardInner changes from ContentResolver to String resolvedType, and they no longer call resolveTypeIfNeeded. The same resolvedType is used for the root intent check and the selector canForwardTo check. Injector and InjectorImpl gain the matching resolveActivityAsUser(Intent, String, int, int) method.",
      "frameworks/base IntentForwarderActivity.java (same commit): the privileged-caller skip-user-consent path now uses getLaunchedFromPackage() instead of getCallingPackage(). On the resolver launch path, after sanitizeIntent(intentReceived), the selector is also sanitized when intentReceived.getSelector() != null, before startActivityAsCaller.",
      "frameworks/base core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents: adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver).",
      "packages/modules/IntentResolver java/src/com/android/intentresolver/IntentForwarderActivity.java and AbstractMultiProfilePagerAdapter.java (commit 000c3577880a): the module copy of the forwarder gets the single resolvedType, canForward/canForwardInner taking a String, an Injector overload that calls the new PackageManager resolveActivityAsUser(intent, resolvedType, flags, userId), and selector sanitization on the resolver path. This commit does not contain the getLaunchedFromPackage() change. It calls the overload added by the frameworks/base commit.",
      "frameworks/base commit 2fadc52d7f18 (r29) is in effect test-only. It makes the executor and the DEVICE_PROVISIONED check injectable (Injector.getExecutorService/isDeviceProvisioned, whose production implementations keep the previous behaviour), adds HIDE_OVERLAY_WINDOWS to the coretests manifest, and corrects the backwards assertNull assertions added to IntentForwarderActivityTest by the first commit."
     ],
     "completeness": [
      "Covers the forwarder's own two consumers of the type: canForwardTo (root intent and selector) and the follow-up resolveActivityAsUser now share one resolvedType string captured in onCreate.",
      "The selector check in canForwardInner now receives the root intent's resolvedType rather than a type resolved from the selector itself. The material does not show whether a selector can carry its own data or type that would need separate handling.",
      "AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker still resolves the type at call time for each intent. This is an adaptation to the new signature.",
      "The fix does not change the final launch (startActivityAsCaller of newIntent after the resolve future completes). The material does not show whether the standard activity-start path derives the type again independently.",
      "The existing resolveActivity/resolveActivityAsUser overloads still resolve the type from the intent; only callers that use the new overload get the single-resolution behaviour.",
      "The first commit's test assertions were inverted (assertNull on captured intents that had to be non-null); they were only corrected in the r29 test commit."
     ],
     "behavior_changes": [
      "New hidden PackageManager overloads resolveActivityAsUser(Intent, String, int, int) and (Intent, String, ResolveInfoFlags, int). A PackageManager subclass that does not override the ResolveInfoFlags variant throws UnsupportedOperationException when it is called.",
      "IntentForwarderActivity.canForward signature changed (ContentResolver replaced by String resolvedType) in both frameworks/base and the IntentResolver module. The Injector interface gains a new method, and after r29 in frameworks/base also getExecutorService/isDeviceProvisioned.",
      "In frameworks/base, the privileged EXTRA_SKIP_USER_CONFIRMATION handling now uses getLaunchedFromPackage() rather than getCallingPackage().",
      "On the resolver hand-off path, intent selectors are now passed through sanitizeIntent as well as the outer intent.",
      "The forwarder's resolve for the target user uses the type computed at check time instead of deriving it again."
     ],
     "preconditions": [
      "A device with a profile relationship (managed profile or profile parent), so that intents resolve to IntentForwarderActivity (ForwardIntentToParent / ForwardIntentToManagedProfile).",
      "The intent's derived type (Intent.resolveTypeIfNeeded) must differ between the canForwardTo check and the later resolve. How this is achieved is not shown in the material.",
      "IntentForwarderActivity is what calls IPackageManager.canForwardTo (INTERACT_ACROSS_USERS_FULL enforced in ComputerEngine.canForwardTo, per index search). Telecomm CallsManager.maybeRedirectToIntentForwarder also routes ACTION_CALL intents to the forwarder, per index search."
     ],
     "per_major": {
      "14": "Same fix: frameworks/base 042b52a8551f (API addition, IntentForwarderActivity, AbstractMultiProfilePagerAdapter, tests) and IntentResolver 000c3577880a at android-security-14.0.0_r24; test/injector follow-up frameworks/base 2fadc52d7f18 at r29. Per-major facts report 6/8 lines of the patched region present at r29.",
      "15": "Same fix: frameworks/base bf9e58d7edb5 at android-security-15.0.0_r12. Its content matches 14 apart from the surrounding private-profile code (buildAndExecuteForPrivateProfile, singleTabOnly). The material does not show an IntentResolver module counterpart on 15. 6/8 lines of the patched region present at r17.",
      "16": "Adapted fix in IntentResolver 4e6cf5285d0b at android-security-16.0.0_r3. It has the same IntentForwarderActivity changes, but the canForward call-site adaptation is in java/src/com/android/intentresolver/emptystate/CrossProfileIntentsChecker.java instead of AbstractMultiProfilePagerAdapter. The material does not show the frameworks/base API-addition commit on 16. 8/8 lines present at r8.",
      "17": "Resolved by change_id at android-security-17.0.0_r1. The only commit shown is the test/injector commit frameworks/base 323c8b968a88, which also re-enables the test in core/tests/coretests/Android.bp. The material does not show the main fix commit. 8/8 lines present at r1."
     },
     "backport_notes": [
      "Order matters: land the frameworks/base commit (new PackageManager/ApplicationPackageManager resolveActivityAsUser(Intent, String, ...) overloads) before the IntentResolver module commit, whose InjectorImpl calls the new overload. Update every caller of canForward (CrossProfileIntentsChecker / AbstractMultiProfilePagerAdapter) in the same change, because its signature changes.",
      "Per index search, the IntentResolver module canForward in public android-14.0.0_r75 has a different shape from the one the r24 security diff patches: it checks a single intentToCheck (the selector if present, otherwise the root intent) and resolves the type from intentToCheck, instead of the two-call canForwardInner form. A backport onto that shape needs manual adaptation so that one resolvedType feeds both canForwardTo and the later resolveActivityAsUser."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete trigger: how the intent's type can change between the canForwardTo check and the resolve (for example, whether a content provider's answer is involved).",
      "Which of the listed bugs (403565650, 407763772, 397216638) maps to this CVE.",
      "Whether the frameworks/base API addition and IntentForwarderActivity change exist on the 16 and 17 security branches (only the module fix for 16 and the test commit for 17 are shown).",
      "Whether the IntentResolver module fix was applied on Android 15.",
      "Why per-major facts report only 6/8 patched-region lines present on 14 and 15 at the latest tags.",
      "Whether the final startActivityAsCaller launch path re-resolves the type independently of the forwarder's resolvedType."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48565": {
     "cve": "CVE-2025-48565",
     "summary": "IntentForwarderActivity now resolves the intent MIME type once and passes that same string to both the IPackageManager.canForwardTo cross-profile check and the target-user activity resolution (via a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload), uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-user-consent check, and (after a follow-up on 14) sanitizes intent selectors (package, component, nested selector) in every sanitizeIntent call.",
     "component": "platform/frameworks/base, core/java/com/android/internal/app/IntentForwarderActivity (cross-profile intent forwarding), plus PackageManager / ApplicationPackageManager hidden API",
     "weakness": "Time-of-check/time-of-use on an untrusted, dynamically resolved intent type (type re-resolved from the intent after the canForwardTo check), combined with incomplete sanitization of the intent selector and use of getCallingPackage() for a privilege decision",
     "root_cause": "In IntentForwarderActivity.onCreate the cross-profile check canForward -> canForwardInner computed the type with intent.resolveTypeIfNeeded(contentResolver) and passed it to IPackageManager.canForwardTo; afterwards mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which independently called intent.resolveTypeIfNeeded(...) again. The commit message states the intent can change its type after the canForward check, so the type authorized for forwarding is not necessarily the type used for resolution in the other user. Additionally, launchResolverActivityWithCorrectTab sanitized only intentReceived and not its selector, and the managed-profile path computed callingPackage via getCallingPackage() for the EXTRA_SKIP_USER_CONFIRMATION privileged-caller decision.",
     "fix_analysis": [
      "Commit 042b52a8551f (14.0.0_r24) core/java/android/content/pm/PackageManager.java: adds two hidden overloads resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId); the javadoc states resolvedType is specified explicitly when the type contained in the intent cannot be trusted, null falls back to Intent#resolveTypeIfNeeded.",
      "Same commit core/java/android/app/ApplicationPackageManager.java: the resolveActivity(Intent, ResolveInfoFlags) and resolveActivityAsUser overloads now funnel into resolveActivityAsUser(Intent, String, ResolveInfoFlags, int), which calls mPM.resolveIntent with resolvedType when non-null and only falls back to intent.resolveTypeIfNeeded(mContext.getContentResolver()) when it is null.",
      "Same commit IntentForwarderActivity.onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...) and then to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId); canForward and canForwardInner take String resolvedType instead of ContentResolver and no longer call resolveTypeIfNeeded themselves. Injector interface and InjectorImpl gain the resolvedType overload (InjectorImpl calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId)).",
      "Same commit IntentForwarderActivity.java managed-profile branch: String callingPackage = getLaunchedFromPackage() replaces getCallingPackage() for the privilegedCallerAskedToSkipUserConsent computation.",
      "Same commit IntentForwarderActivity.launchResolverActivityWithCorrectTab: adds sanitizeIntent(intentReceived.getSelector()) when a selector is present, before startActivityAsCaller.",
      "Same commit AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker.hasCrossProfileIntents: adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver).",
      "Commit 2fadc52d7f18 (14.0.0_r29): test enablement; Injector gains getExecutorService() and isDeviceProvisioned(), onCreate uses mInjector.getExecutorService(), isDeviceProvisioned() delegates to the injector; test manifest adds HIDE_OVERLAY_WINDOWS; inverted assertNull assertions on the captured intents are corrected to assertNotNull.",
      "Commit 96057a4e7e40 (14.0.0_r29) IntentForwarderActivity.sanitizeIntent: now also clears the selector's package, component and nested selector; it removes the explicit selector-sanitize call added by 042b52a8551f in launchResolverActivityWithCorrectTab and the pre-existing sanitizeIntent(forwardIntent.getSelector()) call in canForward, since every sanitizeIntent call (including launchChooserActivityWithCorrectTab's calls on intentReceived and innerIntent) now covers the selector."
     ],
     "completeness": [
      "Covers the main forwarding path in onCreate: one resolvedType is used for the root-intent canForwardTo check, the selector canForwardTo check, and the target-user resolveIntent.",
      "launchResolverActivityWithCorrectTab still calls mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, callingUserId).join() (overload without resolvedType, 14.0.0_r29 lines 444-445), so that resolution still derives the type from the intent; its result selects which user hosts the resolver.",
      "The selector check in canForward now reuses the root intent's resolvedType rather than resolving the selector's own type.",
      "After 96057a4e7e40, selector sanitization applies wherever sanitizeIntent is called (canForward copy, resolver path, chooser path including the EXTRA_INTENT inner intent); a nested selector is nulled rather than recursed.",
      "AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker still resolves the type from the intent at the call site.",
      "The ApplicationPackageManager fallback (resolvedType == null -> resolveTypeIfNeeded) keeps legacy behavior for all other callers; only callers that pass a resolvedType benefit."
     ],
     "behavior_changes": [
      "New hidden PackageManager API: resolveActivityAsUser(Intent, String, int, int) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int); the base ResolveInfoFlags variant throws UnsupportedOperationException unless overridden, so PackageManager subclasses reached through the new path must override it.",
      "Cross-profile forwarding and target-user resolution use a single type string computed in onCreate; the selector is checked with the root intent's type.",
      "EXTRA_SKIP_USER_CONFIRMATION privileged-caller check uses getLaunchedFromPackage() instead of getCallingPackage().",
      "Selectors of forwarded, resolver-path and chooser-path intents lose any explicit package, component and nested selector (after 96057a4e7e40).",
      "IntentForwarderActivity.canForward signature changed (String resolvedType replaces ContentResolver); Injector interface gains resolveActivityAsUser(Intent, String, int, int), getExecutorService() and isDeviceProvisioned()."
     ],
     "preconditions": [
      "A device with a managed profile or profile parent so that an intent resolves to IntentForwarderActivity (ForwardIntentToParent / ForwardIntentToManagedProfile); IPackageManager.canForwardTo enforces INTERACT_ACROSS_USERS_FULL on the caller (ComputerEngine.canForwardTo, per index search).",
      "An intent whose type is obtained through Intent.resolveTypeIfNeeded, and/or an intent carrying a selector.",
      "For the getLaunchedFromPackage change: an intent carrying EXTRA_SKIP_USER_CONFIRMATION.",
      "Other system entry points route through the forwarder, e.g. Telecom CallsManager.maybeRedirectToIntentForwarder resolves ACTION_CALL to FORWARD_INTENT_TO_MANAGED_PROFILE (per index search)."
     ],
     "per_major": {
      "14": "same fix: 042b52a8551f (resolvedType plumbing, getLaunchedFromPackage, selector sanitize) in android-security-14.0.0_r24; follow-ups 2fadc52d7f18 (test/Injector refactor) and 96057a4e7e40 (selector sanitization folded into sanitizeIntent) in android-security-14.0.0_r29. 6/8 patched-region lines at r29, plausibly because 96057a4e7e40 removed the explicit selector-sanitize lines (not confirmed by the material).",
      "15": "same fix: bf9e58d7edb5 in android-security-15.0.0_r12, same production-code changes as 14 apart from context (private-profile branch, singleTabOnly); test changes differ slightly. 6/8 lines at 15.0.0_r17; whether a sanitizeIntent follow-up was applied is not shown.",
      "16": "same fix: 28579dff4305 in android-security-16.0.0_r3, same content as 15. 6/8 lines at 16.0.0_r8, same caveat as 15.",
      "17": "resolved by change_id at android-security-17.0.0_r1; the 17 commit shown, 323c8b968a88, is the test/Injector refactor (also removes the IntentForwarderActivityTest exclude filter). 8/8 lines present; whether the sanitizeIntent folding (96057a4e7e40 equivalent) is in 17.0.0_r1 is not shown."
     },
     "backport_notes": [
      "Apply in order: 042b52a8551f (new PackageManager/ApplicationPackageManager overloads and Injector overload; TestInjector and any PackageManager subclasses must implement them), then 2fadc52d7f18 (changes Injector), then 96057a4e7e40 whose diff base is the result of 2fadc52d7f18.",
      "packages/modules/IntentResolver has its own IntentForwarderActivity.canForward that still takes a ContentResolver and resolves the type from the intent (per index search); it is not covered by these frameworks/base commits."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the sanitizeIntent selector-folding commit (96057a4e7e40) was applied on Android 15, 16 and 17; the material shows it only for 14.",
      "Which 2 of 8 patched-region lines are absent at the latest 14/15/16 tags.",
      "Whether launchResolverActivityWithCorrectTab's remaining resolveActivityAsUser call without resolvedType was intentionally left as is.",
      "Whether the IntentResolver module copy of IntentForwarderActivity is active on Android 14 devices and whether it received a matching fix.",
      "Exact trigger details beyond the commit message statement that the intent can change its type after the canForward check."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48566": {
     "cve": "CVE-2025-48566",
     "summary": "IntentForwarderActivity now resolves the received intent's MIME type once and passes that same string both to the IPackageManager.canForwardTo check and to a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload. It also sanitizes the received intent's selector and uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-consent check.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity, android.content.pm.PackageManager / android.app.ApplicationPackageManager)",
     "weakness": "Time-of-check/time-of-use on intent type resolution: the type was derived from the intent again after the canForward check. Also incomplete sanitization of the intent selector, and the skip-consent check used the calling package instead of the launched-from package.",
     "root_cause": "In core/java/com/android/internal/app/IntentForwarderActivity.java, canForwardInner computed resolvedType itself with intent.resolveTypeIfNeeded(contentResolver). The later mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which called intent.resolveTypeIfNeeded(mContext.getContentResolver()) again. So the type was derived from the intent twice. Per the commit message, an intent that changes its type after the IntentForwarder#canForward check is checked against one type and resolved with another. The same class had two more issues. First, the path that forwards intentReceived to the resolver (the sanitizeIntent(intentReceived) block before startActivityAsCaller) sanitized only the top-level intent and not intentReceived.getSelector(). Second, the privileged skip-user-consent decision used getCallingPackage() rather than getLaunchedFromPackage().",
     "fix_analysis": [
      "Commit 042b52a8551f (android-security-14.0.0_r24), core/java/android/content/pm/PackageManager.java: adds two @hide overloads, resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The ResolveInfoFlags variant throws UnsupportedOperationException unless a subclass overrides it. The Javadoc says resolvedType is 'specified explicitly when the data type contained in the intent cannot be trusted'.",
      "core/java/android/app/ApplicationPackageManager.java: implements the new overloads. resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) passes resolvedType to mPM.resolveIntent when it is non-null and falls back to intent.resolveTypeIfNeeded(...) only when it is null. The existing overloads now delegate with resolvedType = null, so they behave as before.",
      "IntentForwarderActivity.onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once. It passes this value to canForward(...) and then to mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId), so the check and the resolution use the same type string.",
      "IntentForwarderActivity.canForward / canForwardInner: the ContentResolver parameter is replaced by String resolvedType, and the local resolveTypeIfNeeded call in canForwardInner is removed. The same resolvedType is now used for both the root forwardIntent check and the forwardIntent.getSelector() check. Before, the selector's type was resolved from the selector itself.",
      "IntentForwarderActivity (resolver-forwarding block before startActivityAsCaller): after sanitizeIntent(intentReceived), it also calls sanitizeIntent(intentReceived.getSelector()) when a selector is present. The updated test checks the intents passed to canForwardTo (root and selector), not this path.",
      "IntentForwarderActivity (managed-profile skip-consent logic): String callingPackage = getLaunchedFromPackage() replaces getCallingPackage().",
      "IntentForwarderActivity.Injector / InjectorImpl: adds resolveActivityAsUser(Intent, String resolvedType, int flags, int userId), which calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId) through CompletableFuture.supplyAsync.",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java, CrossProfileIntentsChecker.hasCrossProfileIntents: updated for the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver). The root intent's type is now also used for the selector check.",
      "Follow-up commit 2fadc52d7f18 (android-security-14.0.0_r29, 'Fix & speed up IntentForwarderActivityTest') is mainly about the test. In IntentForwarderActivity it adds Injector.getExecutorService() and Injector.isDeviceProvisioned(); InjectorImpl keeps Executors.newSingleThreadExecutor() and the Settings.Global.DEVICE_PROVISIONED read. In the test it fixes the backwards assertNull assertions from the first commit's test and removes a 30 s awaitTermination from the test wrapper activity. It also adds HIDE_OVERLAY_WINDOWS to core/tests/coretests/AndroidManifest.xml. It does not change the forwarding checks."
     ],
     "completeness": [
      "Covered: one type resolution shared between IPackageManager.canForwardTo (root intent and selector) and the ResolveInfo lookup in IntentForwarderActivity; selector sanitization on the intentReceived path; caller identity for the skip-consent path.",
      "Only IntentForwarderActivity uses the new resolvedType overloads. All other PackageManager.resolveActivityAsUser callers still derive the type from the intent (null fallback in ApplicationPackageManager).",
      "The selector check in canForward reuses the root intent's resolvedType. The material does not show whether a selector can carry its own data or type that would differ.",
      "The diff does not show the actual launch (startActivityAsCaller) after the check, so it is not visible whether the system-side start path re-resolves the type independently.",
      "The first commit's test contained inverted assertions (assertNull on a non-null captured Intent), which only the follow-up commit corrects. The follow-up's message says it re-enables the test; on 17 it removes the test exclusion from core/tests/coretests/Android.bp."
     ],
     "behavior_changes": [
      "New @hide methods PackageManager.resolveActivityAsUser(Intent, String, int, int) and (Intent, String, ResolveInfoFlags, int). The ResolveInfoFlags variant throws UnsupportedOperationException in PackageManager subclasses that do not override it.",
      "IntentForwarderActivity resolves the intent type once in onCreate. canForwardTo and resolveIntent now receive the same string instead of types derived separately; the selector is checked with the root intent's type.",
      "The selector of the intent forwarded to the resolver is now passed through sanitizeIntent (its exact effect is not shown in the material).",
      "The privileged skip-user-confirmation check now uses getLaunchedFromPackage() instead of getCallingPackage().",
      "Follow-up commit: the executor and device-provisioned lookups go through the Injector, with the same defaults in production."
     ],
     "preconditions": [
      "An intent must reach IntentForwarderActivity (FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE), which implies a profile relationship. IPackageManager.canForwardTo (ComputerEngine.canForwardTo, per index search) performs the cross-profile reachability check.",
      "Per the commit message, the issue needs an intent whose type can change after the canForward check. The material does not give exact trigger details.",
      "Example of a legitimate path into the forwarder: Telecomm CallsManager.maybeRedirectToIntentForwarder resolves ACTION_CALL to FORWARD_INTENT_TO_MANAGED_PROFILE and starts it (per index search)."
     ],
     "per_major": {
      "14": "Same fix: 042b52a8551f in android-security-14.0.0_r24 (main fix), plus the test/injector follow-up 2fadc52d7f18 in android-security-14.0.0_r29. Per-major facts report 6/8 lines of the patched region present at r29; the material does not explain the 2 missing lines.",
      "15": "Adapted fix: bf9e58d7edb5 in android-security-15.0.0_r12. Same logic with context differences (an isPrivateProfile branch after resolveActivityAsUser, a singleTabOnly branch after the selector sanitization). 6/8 lines present at android-security-15.0.0_r17.",
      "16": "Adapted fix: 28579dff4305 in android-security-16.0.0_r3, same content as 15 with shifted line numbers. 6/8 lines present at android-security-16.0.0_r8.",
      "17": "Resolved via change_id with 323c8b968a88 ('Fix & speed up IntentForwarderActivityTest') in android-security-17.0.0_r1. That commit also removes the test exclusion in core/tests/coretests/Android.bp. 8/8 lines of the patched region are present, and the context of the test diff already contains the capturedIntents assertions, which suggests the main fix is already in the 17 baseline."
     },
     "backport_notes": [
      "Apply the main commit (042b52a8551f, Change-Id I8453f25ed5ddd938718f4d2be4983c881778f281) first. The test follow-up (Change-Id Iade781e0758c477ca888bd1541e0c53e2dee3761) builds on its test changes and corrects its inverted assertions. Any downstream Injector implementation must add the new resolveActivityAsUser(Intent, String, int, int) method. A PackageManager subclass that does not override the ResolveInfoFlags variant will throw UnsupportedOperationException if that variant is called.",
      "canForward's parameter changed from ContentResolver to String resolvedType. The diff updates AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker. Any other callers, including vendor code, need the same change."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of IntentForwarderActivity.sanitizeIntent is not shown.",
      "It is not visible whether the system-side activity start after the check (startActivityAsCaller) re-resolves the type independently.",
      "The trigger details behind bugs 403565650, 407763772 and 397216638 are not shown, nor which of the three sub-fixes (type TOCTOU, selector sanitization, launched-from package) accounts for the CVE's High EoP rating.",
      "Why only 6/8 lines of the patched region are present at the latest 14, 15 and 16 security tags.",
      "Whether reusing the root intent's type for the selector check is intended, and whether it affects legitimate selector intents."
     ],
     "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-2026-0010": {
     "cve": "CVE-2026-0010",
     "summary": "In BnDrmManagerService::onTransact, the fix replaces a fixed 16-byte stack buffer that sprintf(\"%lu\") filled with the file descriptor value by a dynamically sized String8::format(\"%d\", fd). This removes a possible stack buffer overflow when the descriptor value is negative.",
     "component": "platform/frameworks/av, drm/common (drmserver Binder interface IDrmManagerService)",
     "weakness": "Stack-based buffer overflow from a fixed-size buffer and a sign-mismatched format specifier (int cast to unsigned long, printed with %lu into char[16])",
     "root_cause": "In drm/common/IDrmManagerService.cpp, BnDrmManagerService::onTransact (hunk at line ~1001, inferred to be in the handler after the ACQUIRE_DRM_INFO case label at line 985 of the context) handled a request entry with key \"FileDescriptorKey\" as follows: it read data.readFileDescriptor() into an int fd, cast it to unsigned long and wrote it with sprintf(buffer, \"%lu\", ...) into a local char buffer[16]. If fd is negative and unsigned long is 64-bit, the cast value prints as up to 20 decimal digits plus the terminating NUL. That is more than the 16-byte stack buffer. The key and the parcel content come from the Binder caller of drmserver.",
     "fix_analysis": [
      "drm/common/IDrmManagerService.cpp, BnDrmManagerService::onTransact, \"FileDescriptorKey\" branch: the local char buffer[16] and the sprintf call are deleted.",
      "The value stored in drmInfoRequest now comes from String8::format(\"%d\", fd). This sizes the string as needed and formats fd as a signed int, so there is no fixed-size buffer left to overflow.",
      "data.readFileDescriptor() and the drmInfoRequest->put(key, ...) call are otherwise unchanged. The fix is a single hunk in a single file."
     ],
     "completeness": [
      "The fix covers the only sprintf-into-fixed-buffer site shown in the material. The overflow is gone whatever value fd holds.",
      "The fix does not check that fd is non-negative. A negative fd still produces a \"FileDescriptorKey\" entry whose value is a negative number string, passed on through drmInfoRequest. This change does not address that.",
      "The neighbouring PROCESS_DRM_INFO case (lines 932-983 of the context) reads only key/value strings and has no file-descriptor formatting path. No sibling occurrence appears in the shown code."
     ],
     "behavior_changes": [
      "For non-negative descriptors the stored string is the same as before (decimal digits).",
      "For a negative fd, the stored value is now the signed decimal (e.g. \"-1\") instead of a wrapped unsigned number. No error is returned and the request still proceeds.",
      "The change adds no new error codes, permission checks or parcel validation."
     ],
     "preconditions": [
      "The caller can send Binder transactions to drmserver's IDrmManagerService (BnDrmManagerService::onTransact) for the transaction that contains the \"FileDescriptorKey\" handling, with a request key equal to \"FileDescriptorKey\".",
      "fd must be negative for the old formatted string to exceed 15 characters (with a 64-bit unsigned long). How a negative value comes about is not shown.",
      "Access control on drmserver (which clients can reach this interface) is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/av 8bf16f1ae904 in android-security-14.0.0_r25 (cherry-pick, Change-Id I6efecac362757c10b2308d020440eac5edc5b873), identical hunk. At android-security-14.0.0_r29, 5/8 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/av c8713a065232 in android-security-15.0.0_r13, identical hunk. At android-security-15.0.0_r17, 5/8 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/av ebdbf918dd87 in android-security-16.0.0_r4, identical hunk. At android-security-16.0.0_r8, 5/8 lines of the patched region are present.",
      "17": "not affected: Android 17 is not listed by the bulletin. At android-security-17.0.0_r1, 5/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-file, single-hunk change with no dependencies shown. The pre-image (char buffer[16]; sprintf \"%lu\"; String8(buffer)) is the same in the 14, 15 and 16 diffs. String8::format is a long-standing libutils API (general platform knowledge). The material includes no fork comparison."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact transaction case label containing the hunk is not shown. The context ends at the ACQUIRE_DRM_INFO case label (line 985) and the hunk is at line ~1001 using drmInfoRequest, so it is inferred to be in that handler.",
      "What data.readFileDescriptor() returns when no valid descriptor is present at that position is not shown.",
      "Why only 5/8 lines of the patched region match at the latest tags is not explained.",
      "Which processes or SELinux domains can reach this drmserver interface is not shown in the material.",
      "The two googleplex Cherrypick-From commits are referenced but their contents are not shown. The AOSP commit is a single hunk."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0054": {
     "cve": "CVE-2026-0054",
     "summary": "Adds an onTransact-level caller check to SystemUI's WalletContextualLocationsService binder so that only privileged apps holding the android.app.role.SYSTEM_UI_INTELLIGENCE role can invoke IWalletContextualLocationsService methods; all other callers get a SecurityException.",
     "component": "platform/frameworks/base, SystemUI wallet (packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt)",
     "weakness": "Missing caller authorization on a bound-service binder interface (missing permission check)",
     "root_cause": "The IWalletContextualLocationsService.Stub returned by WalletContextualLocationsService did not validate its caller. Its two AIDL methods were forwarded directly to the internal implementations for any process that could bind to the service: addWalletCardsUpdatedListener (to addWalletCardsUpdatedListenerInternal) and onWalletContextualLocationsStateUpdated (to onWalletContextualLocationsStateUpdatedInternal, which the context shows feeding store locations into controller.setSuggestionCardIds). The class comment says the service was designed as an intermediary between QuickAccessWalletService and ContextualCardManager in PCC, but nothing in the code limited it to that intended caller. The bulletin classifies the issue as information disclosure (ID), High.",
     "fix_analysis": [
      "WalletContextualLocationsService.kt: the anonymous IWalletContextualLocationsService.Stub now overrides onTransact(code, data, reply, flags). Before delegating to super.onTransact, it calls isCallerAllowed() and throws SecurityException(\"Caller is not allowed.\") if that returns false. Because the check is in onTransact, it gates every transaction on the stub, not individual methods.",
      "WalletContextualLocationsService.kt: the new private isCallerAllowed() reads Binder.getCallingUid() and clears the calling identity (Binder.clearCallingIdentity, restored in finally). It resolves packageManager.getPackagesForUid(uid) and returns false if the result is null or empty. It then fetches RoleManager.getRoleHolders(\"android.app.role.SYSTEM_UI_INTELLIGENCE\"), using an empty list if RoleManager is unavailable. It returns true only if at least one of the caller's packages is in the role holder list AND packageManager.getApplicationInfo(packageName, 0).isPrivilegedApp() is true. A NameNotFoundException counts as false.",
      "WalletContextualLocationsService.kt companion object: adds the ROLE_SYSTEM_UI_INTELLIGENCE constant. The commit message calls this a quick fix, chosen to match the originally intended caller and the existing primary callers, and says a dedicated permission should replace it later."
     ],
     "completeness": [
      "Covers both existing AIDL methods (addWalletCardsUpdatedListener, onWalletContextualLocationsStateUpdated) and any future methods on the stub, because the check sits in onTransact.",
      "Authorization is based on the role and on privileged status, not on a manifest permission; the commit itself says a new permission should replace it. The check accepts the call if ANY package sharing the calling UID holds the role and is privileged, not necessarily the package that made the call.",
      "The diff does not change binding to the service (onBind); only transactions are rejected. The material does not show whether the service is exported in the SystemUI manifest.",
      "The fix does not change the listener callback side (IWalletCardsUpdatedListener) or WalletContextualSuggestionsController; it relies only on gating entry to the service."
     ],
     "behavior_changes": [
      "Any binder transaction to WalletContextualLocationsService from a caller that is not a privileged app holding SYSTEM_UI_INTELLIGENCE now fails with SecurityException (\"Caller is not allowed.\").",
      "Each transaction now performs PackageManager and RoleManager lookups under a cleared calling identity before dispatch.",
      "On Android 14 the diff also reformats the binder stub declaration (no functional change)."
     ],
     "preconditions": [
      "The caller must be able to bind to com.android.systemui.wallet.controller.WalletContextualLocationsService in SystemUI and issue IWalletContextualLocationsService transactions. Before the fix, no caller identity check existed.",
      "The material does not show the service's manifest export or permission attributes, so the exact set of processes that can bind is unknown."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/base a83edee34b0d in android-security-14.0.0_r28 (Change-Id I7e8b2bf83727464369b94c63e208c5717601a51f). The diff also reformats the stub declaration because the 14 branch used the older layout. 8/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Same fix: 3717e022bc2a in android-security-15.0.0_r16, with identical logic, applied on the already reformatted stub at different line offsets. 8/8 lines are present at android-security-15.0.0_r17.",
      "16": "Same fix: a8f3c0ccb472 in android-security-16.0.0_r7, with identical logic; the context differs only by the existing android.os.DeadObjectException import. 8/8 lines are present at android-security-16.0.0_r8.",
      "17": "Not affected (the bulletin does not list Android 17). The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit. The only source difference across 14/15/16 is the formatting around the binder stub, so apply the onTransact override, isCallerAllowed(), the four imports and the ROLE_SYSTEM_UI_INTELLIGENCE constant to whatever stub layout the target branch has.",
      "The check calls RoleManager.getRoleHolders and ApplicationInfo.isPrivilegedApp from SystemUI; SystemUI is a privileged system app (general platform knowledge). If no package holds the SYSTEM_UI_INTELLIGENCE role on the target build, the role holder list is empty and the code rejects every caller."
     ],
     "confidence": "high",
     "unknowns": [
      "Manifest attributes of WalletContextualLocationsService (exported state, any permission attribute), and therefore which processes could bind before the fix.",
      "What data the registered IWalletCardsUpdatedListener receives, and what exact disclosure the bulletin rates as High.",
      "Contents of the companion STS test commit I4c59ae71f66035d2aeeb9b9e4dd13d7911e5fae5."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0065": {
     "cve": "CVE-2026-0065",
     "summary": "The BAL \"bound by foreground UID\" exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned (non-PiP) window for activity starts (FGS starts keep the old any-visible-window predicate), and on Android 14/15 additionally requires appSwitchState != APP_SWITCH_DISALLOW unconditionally.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager background activity launch (BAL) control",
     "weakness": "Insufficient authorization check (overly permissive foreground-visibility predicate in a BAL exemption)",
     "root_cause": "BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed() grants BAL_ALLOW_VISIBLE_WINDOW when isBoundByForegroundUid() finds any UID in mBalOptInBoundClientUids (clients bound with BIND_ALLOW_ACTIVITY_STARTS, per the tests) for which mUidHasActiveVisibleWindowPredicate is true. That predicate is ActivityTaskManagerService.hasActiveVisibleWindow(), which treats a UID as foreground if it has any visible non-toast window, including a pinned (picture-in-picture) activity. So a process bound by a client that only had a PiP window was allowed to start background activities. Per the commit message, this was inconsistent with regular BAL behavior, which requires a non-pinned visible window (the Android 15 tests include testForegroundTaskBlockedIfPinned). On Android 14 the bound-by-foreground branch also did not consult appSwitchState. On Android 15 that check was gated behind the flag Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid().",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java: adds hasActiveVisibleNotPinnedWindow(int uid), which returns true if mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity(uid) or mActiveUids.hasNonAppVisibleWindow(uid). It mirrors hasActiveVisibleWindow() but only considers non-pinned app activities.",
      "services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java: adds the field mUidHasActiveVisibleNotPinnedWindowPredicate and a new constructor parameter uidHasActiveVisibleNotPinnedWindowPredicate. isBoundByForegroundUid() now takes an IntPredicate uidVisibilityPredicate and tests each UID in mBalOptInBoundClientUids against it.",
      "services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java areBackgroundActivityStartsAllowed(): the bound-by-foreground branch becomes `appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid(isCheckingForFgsStart ? mUidHasActiveVisibleWindowPredicate : mUidHasActiveVisibleNotPinnedWindowPredicate)`. Activity starts now require a non-pinned visible window in the bound client, while FGS-start checks keep the old predicate. On Android 16 the same ternary is applied inside the existing `checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW` condition, using checkConfiguration.isCheckingForFgsStart.",
      "services/core/java/com/android/server/wm/WindowProcessController.java constructor: passes atm::hasActiveVisibleNotPinnedWindow as the new second argument when constructing BackgroundLaunchProcessController.",
      "Android 15 only: removes the import of com.android.window.flags.Flags and the flag-gated allowBoundByForegroundUid computation, so the appSwitchState != APP_SWITCH_DISALLOW check becomes unconditional.",
      "Android 15 and 16 only: services/tests/wmtests/.../BackgroundLaunchProcessControllerTests.java is updated for the new constructor and split into testBoundByForegroundFgs (any visible window) and testBoundByForeground (non-pinned visible window). The Android 14 commit contains no test changes."
     ],
     "completeness": [
      "Covers only the bound-by-foreground-UID exemption (mBalOptInBoundClientUids path) in BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed(). The process's own visible-task path is not changed by this commit. On Android 15/16 the tests show that path already takes a pinned-window-mode parameter (testForegroundTaskBlockedIfPinned).",
      "FGS-start checks (isCheckingForFgsStart true) intentionally keep using hasActiveVisibleWindow, so a bound client with only a PiP window still enables FGS starts. The commit message says this is by design.",
      "hasActiveVisibleNotPinnedWindow still falls back to mActiveUids.hasNonAppVisibleWindow(uid), so for BAL the bound client's non-app visible windows still count as foreground, same as before.",
      "The non-FGS BAL path now also requires appSwitchState != APP_SWITCH_DISALLOW: this is new on Android 14 and unconditional on Android 15 (it was previously flag-gated).",
      "Other callers of hasActiveVisibleWindow are not changed by this commit. Only the BAL predicate injected into BackgroundLaunchProcessController is switched."
     ],
     "behavior_changes": [
      "A process bound with BIND_ALLOW_ACTIVITY_STARTS by a UID whose only visible activity is in pinned (PiP) mode no longer receives BAL_ALLOW_VISIBLE_WINDOW for activity starts (BAL_ALLOW_BOUND_BY_FOREGROUND on 16 with balImprovedMetrics). The start falls through to the remaining checks and may be blocked.",
      "On Android 14, bound-by-foreground BAL is now also denied when appSwitchState == APP_SWITCH_DISALLOW. On Android 15 this behavior is no longer gated by Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid().",
      "FGS start eligibility via a bound foreground client is unchanged.",
      "The BackgroundLaunchProcessController constructor signature changes (one extra IntPredicate), which affects any test or caller that constructs it."
     ],
     "preconditions": [
      "The calling process must be bound by another UID using Context.BIND_ALLOW_ACTIVITY_STARTS, so that the client UID is recorded in mBalOptInBoundClientUids via addBoundClientUid().",
      "At the time of the activity start, that client UID's only visible activity is in pinned (picture-in-picture) windowing mode, and it has no other visible non-toast window.",
      "The start is an activity start (isCheckingForFgsStart false) that reaches the bound-by-foreground branch of areBackgroundActivityStartsAllowed() without having been allowed by an earlier check."
     ],
     "per_major": {
      "14": "Same fix: RESTRICT AUTOMERGE cherry-pick 407d7b083010 in android-security-14.0.0_r28. It adds the appSwitchState != APP_SWITCH_DISALLOW check as a new condition, since 14 had none. No test changes. 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Adapted fix: 3d4a174f1def in android-security-15.0.0_r16. Same predicate split, plus removal of the Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid() gating and the Flags import; this branch returns a BalVerdict. Includes BackgroundLaunchProcessControllerTests updates. 8/8 lines present at android-security-15.0.0_r17.",
      "16": "Adapted fix: 66c209d1f65f in android-security-16.0.0_r7. The ternary is applied inside the existing checkConfiguration.checkVisibility && appSwitchState condition, using checkConfiguration.isCheckingForFgsStart. Test updates add a BalCheckConfiguration for FGS. 8/8 lines present at android-security-16.0.0_r8.",
      "17": "Not affected: the bulletin does not list Android 17. The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix depends on VisibleActivityProcessTracker.hasVisibleNotPinnedActivity(int) existing in the target tree. The diff calls it but does not add it, so check that it is present before cherry-picking.",
      "Adjust the surrounding condition to the branch's shape: on 14 add the appSwitchState check, on 15 drop the Flags gating, on 16 keep checkConfiguration. Any other code that constructs BackgroundLaunchProcessController (e.g. wmtests) must pass the extra predicate."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of VisibleActivityProcessTracker.hasVisibleNotPinnedActivity() is not shown, so the material does not show how it determines pinned state.",
      "The material does not show which activity-start entry points reach areBackgroundActivityStartsAllowed() with isCheckingForFgsStart false.",
      "The Android 14 commit includes no test changes. Whether the test file was updated separately on that branch is unknown.",
      "The Android 14 code of the process's own visible-task / pinned-window check is not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28581": {
     "cve": "CVE-2026-28581",
     "summary": "Telecom's CallIntentProcessor.fixInitiatingUserIfNecessary now decides whether a managed (work) profile has a dialer by asking RoleManager for a ROLE_DIALER holder in that user and verifying with PackageManager that the holder package exists and is enabled, instead of checking whether any activity in the profile resolves ACTION_DIAL via DefaultDialerManager.getInstalledDialerApplications.",
     "component": "platform/packages/services/Telecomm, CallIntentProcessor (outgoing call intent handling for managed profiles)",
     "weakness": "Unreliable check for whether a work-profile dialer is present, leading to the wrong user being attributed to the call; the bulletin classifies the issue as information disclosure",
     "root_cause": "fixInitiatingUserIfNecessary(Context, Intent) in src/com/android/server/telecom/CallIntentProcessor.java is meant to re-attribute a call initiated from a managed profile to the parent user when the work profile has no dialer. It decided \"no dialer\" with DefaultDialerManager.getInstalledDialerApplications(context, userId).size() == 0, i.e. by whether anything in the work profile resolves the ACTION_DIAL intent. Per the commit message, a PackageManager#queryIntentActivitiesAsUser regression reported the \"android\" package (system server) as an ACTION_DIAL handler in the work profile even when nothing there handles it. The check therefore wrongly concluded that a work dialer existed, and the call was not trampolined to the personal profile. The first fix replaced this with a count of RoleManager.getRoleHolders(ROLE_DIALER). Per the second commit message, on Android 14 getQualifiedDefaultHolderPackageName from Role used the system user and could falsely report the default system dialer role as active for the work profile. So a role holder listed for the profile still did not guarantee an actual enabled dialer there.",
     "fix_analysis": [
      "Commit 13272bca51ee (android-security-14.0.0_r28), src/com/android/server/telecom/CallIntentProcessor.java: adds private static doesUserHaveDialerRoleHolder(Context, UserHandle). It creates a user context via context.createContextAsUser(user, 0), gets RoleManager from that context and returns getRoleHolders(RoleManager.ROLE_DIALER).size() > 0.",
      "Same commit, CallIntentProcessor.fixInitiatingUserIfNecessary: replaces 'noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context, initiatingUser.getIdentifier()).size() == 0' with 'noDialerInstalled = !doesUserHaveDialerRoleHolder(context, initiatingUser)'. The following logic, which swaps the initiating user to the profile parent when noDialerInstalled is true, is unchanged.",
      "Commit 48d04bd33441 (android-security-14.0.0_r29), CallIntentProcessor.doesUserHaveDialerRoleHolder: hardens the check. It now fetches the List<String> of ROLE_DIALER holders and returns false if the list is null or empty. It then takes roleHolders.get(0), looks it up with userContext.getPackageManager().getApplicationInfo(dialerPackage, 0) and returns ApplicationInfo.enabled. A PackageManager.NameNotFoundException returns false.",
      "Imports added: android.app.role.RoleManager (commit 1); android.content.pm.ApplicationInfo, android.content.pm.PackageManager and java.util.List (commit 2)."
     ],
     "completeness": [
      "Covers the single decision point in fixInitiatingUserIfNecessary for calls whose KEY_INITIATING_USER is a managed profile; no other call paths are changed.",
      "Only the first entry of the ROLE_DIALER holder list (roleHolders.get(0)) is validated; any other entries are not checked. ROLE_DIALER is an exclusive role, so it normally has at most one holder (general platform knowledge).",
      "The PackageManager cross-check verifies only ApplicationInfo.enabled at the package level. It does not verify that the holder package still has an enabled ACTION_DIAL activity or otherwise still qualifies as a dialer.",
      "The causes described in the commit messages (PackageManager queryIntentActivitiesAsUser reporting the 'android' package, and Role default-holder resolution against the system user) are worked around in Telecom rather than fixed in the framework. The material contains no framework-side change.",
      "The import of android.telecom.DefaultDialerManager is still in the file at android-security-14.0.0_r29. The material does not show whether getInstalledDialerApplications is still used elsewhere in CallIntentProcessor."
     ],
     "behavior_changes": [
      "Calls from a work profile that has no ROLE_DIALER holder, or whose role holder package is missing or disabled, are now re-attributed to the parent (personal) user and handled by the personal dialer. Previously such calls could stay attributed to the work profile.",
      "Calls from a work profile with an installed, enabled dialer role holder still launch the work profile dialer (per the commit tests).",
      "The decision now depends on RoleManager and PackageManager queries made under a user-specific context (createContextAsUser), not on ACTION_DIAL intent resolution."
     ],
     "preconditions": [
      "The device has a managed (work) profile, and a call intent reaches CallIntentProcessor with KEY_INITIATING_USER set to that profile (UserUtil.isManagedProfile returns true). Per the class javadoc, UserCallIntentProcessor is the per-user trampoline that forwards call intents to CallIntentProcessor.",
      "The work profile has no actual enabled dialer, so the old check and the first fix would wrongly conclude that a work dialer exists."
     ],
     "per_major": {
      "14": "Same fix, in two commits: 13272bca51ee (RoleManager check) in android-security-14.0.0_r28, then 48d04bd33441 (PackageManager enabled cross-check) in android-security-14.0.0_r29. The patched region is present at r29 (8/8 lines).",
      "15": "Adapted fix: commit 34079b659664 in android-security-15.0.0_r16 applies the RoleManager check. On 15, fixInitiatingUserIfNecessary takes a FeatureFlags parameter and uses UserUtil.isManagedProfile(context, initiatingUser, featureFlags) and context.getSystemService(UserManager.class). The material does not show the second (ApplicationInfo.enabled) commit for 15. Region 8/8 present at r17.",
      "16": "Adapted fix: commit 510bcce16c24 in android-security-16.0.0_r7, with the same FeatureFlags-based shape as 15. The material does not show the second commit for 16. Region 8/8 present at r8.",
      "17": "Commit c1104b6c1bbe in android-security-17.0.0_r1 adds the PackageManager enabled cross-check. Its diff context shows the RoleManager-based doesUserHaveDialerRoleHolder already present. Region 8/8 present at r1."
     },
     "backport_notes": [
      "Apply the two commits in order. The first introduces doesUserHaveDialerRoleHolder and switches fixInitiatingUserIfNecessary to it. The second replaces the size()>0 return with the null/empty check plus the getApplicationInfo(...).enabled check, and depends on the first commit's method and RoleManager import.",
      "On 15 and 16 the surrounding function signature differs (FeatureFlags parameter, 3-argument UserUtil.isManagedProfile, context.getSystemService(UserManager.class)). Only the noDialerInstalled line and the new helper need to change."
     ],
     "confidence": "high",
     "unknowns": [
      "What information is disclosed, and to whom, when a work profile call is not re-attributed to the parent user; the bulletin only classifies it as ID/High.",
      "Whether the second commit (PackageManager enabled cross-check) was applied to the Android 15 and 16 security branches; the material shows only the first commit there.",
      "Whether the underlying PackageManager and RoleManager behaviors described in the commit messages were fixed separately in frameworks/base.",
      "Whether DefaultDialerManager.getInstalledDialerApplications is still used elsewhere in Telecom for similar decisions.",
      "Which entry points call fixInitiatingUserIfNecessary; the index search found no callers."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28582": {
     "cve": "CVE-2026-28582",
     "summary": "ConfirmDeviceCredentialActivity now finishes immediately when it receives KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through any component other than the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity, so the remote-validation flow can no longer be entered by targeting the underlying activity class directly.",
     "component": "platform/packages/apps/Settings, password / device credential confirmation (ConfirmDeviceCredentialActivity)",
     "weakness": "Missing authorization check: permission enforcement attached to an activity-alias could be bypassed by addressing the target activity component directly",
     "root_cause": "ConfirmDeviceCredentialActivity handles several intent actions in onCreate, including KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL (the 'remoteValidation' path). Per the comment added by the fix, the remote-validation flow must go through the alias com.android.settings.ConfirmRemoteDeviceCredentialActivity 'to avoid bypassing permission checks'. Before the fix, the activity code never checked which component the intent was delivered to, so an intent with the remote-validation action delivered to a component other than the alias still entered the remote-validation path. The manifest that declares the alias and its permission is not part of the material.",
     "fix_analysis": [
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: adds the constant CONFIRM_REMOTE_DEVICE_CREDENTIAL_ACTIVITY_ALIAS = \"com.android.settings.ConfirmRemoteDeviceCredentialActivity\".",
      "ConfirmDeviceCredentialActivity.onCreate: right after 'remoteValidation' is computed from intent.getAction() and before mTaskOverlay is set, if remoteValidation is true the fix reads intent.getComponent(). If that is null or its getClassName() is not the alias string, it logs 'Caller bypassing alias for action: ...' with Log.w, calls finish() and returns, so the rest of onCreate after that point does not run.",
      "The Android 15/16/17 commits also add tests/robotests/src/com/android/settings/password/ConfirmDeviceCredentialActivityTest.java. It has two tests: remote validation with the alias component does not finish, and with the base class component the activity reaches DESTROYED. They also extend ShadowLockPatternUtils with a getCredentialTypeForUser shadow. The Android 14 commit contains only the source change."
     ],
     "completeness": [
      "The check applies only when the action equals ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL. This change does not touch the FRP action or the normal confirm-credential actions in the same onCreate.",
      "Only the class name of intent.getComponent() is compared, not the package.",
      "The check runs in onCreate after the 'remoteValidation' computation. Any code earlier in onCreate (not shown in the material) runs before the check.",
      "The alias name is hardcoded in Java and must match the activity-alias declared in the Settings manifest, which the material does not show.",
      "No other file is touched, and the material shows no similar unfixed pattern."
     ],
     "behavior_changes": [
      "Intents with action ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL that arrive at ConfirmDeviceCredentialActivity with a null component, or with a component whose class name is not com.android.settings.ConfirmRemoteDeviceCredentialActivity, now finish immediately and log a Log.w message.",
      "Intents delivered via the alias go through the same path as before.",
      "Test-only (15/16/17): the Robolectric test ConfirmDeviceCredentialActivityTest is added, and ShadowLockPatternUtils gains a getCredentialTypeForUser implementation."
     ],
     "preconditions": [
      "A caller starts ConfirmDeviceCredentialActivity (or a subclass) with the action KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through a component other than the ConfirmRemoteDeviceCredentialActivity alias.",
      "The material does not show whether those components are exported or protected by a permission in the Settings manifest."
     ],
     "per_major": {
      "14": "Same fix, source change only (16 insertions in ConfirmDeviceCredentialActivity.java, no tests). Commit 4b69be85e55d, RESTRICT AUTOMERGE, in android-security-14.0.0_r29. Patched region fully present (7/7 lines).",
      "15": "Same fix, plus the new ConfirmDeviceCredentialActivityTest and the ShadowLockPatternUtils.getCredentialTypeForUser shadow. Commit 839f92783965, RESTRICT AUTOMERGE, in android-security-15.0.0_r17. Patched region fully present (8/8).",
      "16": "Same fix, plus the same test and shadow change. Commit 3ee7e15a126f, RESTRICT AUTOMERGE, in android-security-16.0.0_r8. Patched region fully present (8/8).",
      "17": "Same fix, plus the test and shadow change, without RESTRICT AUTOMERGE in the commit title. Commit 642944e4c4e6 in android-security-17.0.0_r1. Patched region fully present (8/8)."
     },
     "backport_notes": [
      "Single self-contained hunk in onCreate, placed right after the 'remoteValidation' boolean. It uses ComponentName and Log; the Log import is visible in the file context, but check the ComponentName import on the target branch. Before applying, confirm that the target branch's Settings manifest declares the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity under that exact name. Otherwise legitimate remote-validation callers would be finished.",
      "The Robolectric test added on 15+ depends on the ShadowLockPatternUtils.getCredentialTypeForUser shadow from the same commit. The Android 14 commit omits both."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the Settings manifest entry for the ConfirmRemoteDeviceCredentialActivity alias or the permission it enforces.",
      "What information the remote-validation flow exposes when reached without the alias. The bulletin only classifies it as ID, High.",
      "The part of onCreate that runs before the new check is not shown.",
      "Whether ConfirmDeviceCredentialActivity or InternalActivity are exported in the affected branches.",
      "Whether comparing only the class name, without the package, is enough in every case."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28583": {
     "cve": "CVE-2026-28583",
     "summary": "Corrects validate_camera_metadata_structure in system/media camera_metadata.c so that entry_capacity is converted from an entry count to bytes (times sizeof(camera_metadata_buffer_entry_t)), with an overflow check, before verifying that the entry region ends at or before data_start.",
     "component": "platform/system/media, camera metadata (camera/src/camera_metadata.c)",
     "weakness": "Incorrect bounds check (unit mismatch: entry count treated as byte count) leading to out-of-bounds write",
     "root_cause": "In validate_camera_metadata_structure, the end of the entry region was computed as header->entries_start + header->entry_capacity and compared to header->data_start. But entry_capacity is the number of camera_metadata_buffer_entry_t entries the buffer can hold, as the struct layout comment at lines 72-80 describes, not a byte count. So the check accepted headers where entry_capacity * sizeof(camera_metadata_buffer_entry_t) was larger than the space between entries_start and data_start. Per the commit message, a crafted camera_metadata buffer could pass validation and cause an out-of-bounds write, which crashed cameraserver.",
     "fix_analysis": [
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: adds a pre-check that rejects header->entry_capacity > (metadata_size_t)~0 / sizeof(camera_metadata_buffer_entry_t), with a new ALOGE and CAMERA_METADATA_VALIDATION_ERROR. This stops the multiplication from wrapping in metadata_size_t, which is 32-bit per the _Static_assert.",
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: computes metadata_size_t entries_capacity_bytes = sizeof(camera_metadata_buffer_entry_t) * header->entry_capacity and uses it in entries_end = header->entries_start + entries_capacity_bytes.",
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: splits the old combined condition into two checks. The first is entries_end < header->entries_start (addition overflow, with a new log message). The second is entries_end > header->data_start (range check, with the existing log message now printing entries_end). Both return CAMERA_METADATA_VALIDATION_ERROR.",
      "camera/src/camera_metadata.c:struct camera_metadata: adds comments that document entry_capacity as a number of entries and data_capacity as a number of data bytes (no functional change)."
     ],
     "completeness": [
      "The fix covers the relationship between entry_capacity and data_start in validate_camera_metadata_structure, including multiplication and addition overflow.",
      "Only the entry-region check changes. The data_capacity/data_start checks and the rest of the function are not in the diff or the material, so their state cannot be judged here.",
      "The fix is only in the validator. The code paths that write entries are not changed, so protection depends on callers running validate_camera_metadata_structure on untrusted buffers (see the callers in preconditions)."
     ],
     "behavior_changes": [
      "Stricter validation: buffers whose entry_capacity, counted in entries, does not fit between entries_start and data_start now fail with CAMERA_METADATA_VALIDATION_ERROR instead of passing.",
      "Buffers with entry_capacity greater than (metadata_size_t)~0 / sizeof(camera_metadata_buffer_entry_t) are rejected with a new 'Entry capacity ... is too large' log.",
      "New 'Entry start ... + capacity bytes ... overflows' log line. The existing 'should be <= data start' log now prints the byte-based entries_end."
     ],
     "preconditions": [
      "An attacker-controlled camera_metadata_t blob must reach validate_camera_metadata_structure and then be used for entry writes.",
      "Per index search, validate_camera_metadata_structure is called from frameworks/av/camera/CameraMetadata.cpp CameraMetadata::acquire, from frameworks/av/services/camera/libcameraservice/hidl/Utils.cpp convertFromHidl and aidl/AidlUtils.cpp cloneFromAidl (metadata received from the HAL), and from VTS and unit tests.",
      "The commit message says the crash happens in cameraserver. It does not show the exact entry point used for the report."
     ],
     "per_major": {
      "14": "same fix: platform/system/media a9dc2a964e84 in android-security-14.0.0_r29, patched region fully present (8/8).",
      "15": "same fix: platform/system/media 9b4fe9dce95b in android-security-15.0.0_r17, identical diff and blob indices (e0c1cadb..0935461a), patched region fully present (8/8).",
      "16": "same fix: platform/system/media 312249da098c in android-security-16.0.0_r8, identical hunks on a different file revision (1f09d5b1..95116f6c), patched region fully present (8/8).",
      "17": "not affected per the bulletin (not listed). The patched region is fully present (8/8) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit touching one file (camera/src/camera_metadata.c). The hunks are identical across 14, 15 and 16, so they are expected to apply to trees that still have the original entries_start + entry_capacity check."
     ],
     "confidence": "high",
     "unknowns": [
      "Which caller or IPC path delivered the crafted metadata in the reported case, and the specific write path (the commit only mentions an out-of-bounds write and a cameraserver crash).",
      "Whether the surrounding data_start/data_capacity checks in validate_camera_metadata_structure are correct. They are outside the diff and not shown.",
      "How allocate/place_camera_metadata lay out entries_start and data_start, and so whether all legitimately created buffers still pass (the allocation code is not shown).",
      "Whether the change is also shipped in a vendor or HAL copy of the camera metadata code."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28593": {
     "cve": "CVE-2026-28593",
     "summary": "The Live TV app's \"Send Feedback\" settings action now builds an explicit intent targeting com.google.android.feedback/com.google.android.feedback.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent, so the intent can no longer resolve to an arbitrary app that registers for that action.",
     "component": "platform/packages/apps/TV (Live TV), settings side panel (com.android.tv.ui.sidepanel.SettingsFragment)",
     "weakness": "Implicit intent used for a sensitive, app-specific action (intent hijacking / unintended component launch, CWE-927 class)",
     "root_cause": "In src/com/android/tv/ui/sidepanel/SettingsFragment.java, the \"Send Feedback\" ActionItem.onSelected() created `new Intent(Intent.ACTION_APP_ERROR)` with no package or component set, then built an ApplicationErrorReport (packageName/processName set to the TV app's package, and time). Because the intent was implicit, any installed app declaring an intent filter for ACTION_APP_ERROR could be a resolution candidate, so a third-party app could be opened from this action instead of the intended feedback activity. The commit message says the change is \"To avoid opening malicious apps\".",
     "fix_analysis": [
      "src/com/android/tv/ui/sidepanel/SettingsFragment.java: adds `import android.content.ComponentName` and two constants, FEEDBACK_PACKAGE = \"com.google.android.feedback\" and FEEDBACK_CLASS = \"com.google.android.feedback.FeedbackActivity\".",
      "src/com/android/tv/ui/sidepanel/SettingsFragment.java: in the settings_send_feedback ActionItem.onSelected(), replaces `new Intent(Intent.ACTION_APP_ERROR)` with `new Intent().setComponent(new ComponentName(FEEDBACK_PACKAGE, FEEDBACK_CLASS))`. The intent no longer has an action set. It is explicit, so resolution is pinned to that single component and other apps' intent filters are not consulted. The ApplicationErrorReport construction that follows is unchanged."
     ],
     "completeness": [
      "Covers the single implicit-intent site in SettingsFragment's \"Send Feedback\" action. The diff touches only that Intent construction.",
      "The rest of onSelected() is not shown in the material: how the report is attached, how the intent is started, and whether an ActivityNotFoundException is handled. So the code does not show whether launch failures are handled.",
      "No other intent-construction sites in the TV app are changed or shown in the material. The material does not show whether the app has other implicit intents.",
      "The fix hard-codes a Google-specific package and class instead of, for example, using setPackage or verifying the target's signature. On builds without that package the action cannot resolve."
     ],
     "behavior_changes": [
      "\"Send Feedback\" now always targets com.google.android.feedback/com.google.android.feedback.FeedbackActivity. Apps that register for ACTION_APP_ERROR are no longer resolution candidates for this action.",
      "The intent no longer has ACTION_APP_ERROR set, only an explicit component.",
      "On devices where com.google.android.feedback (or that activity) is missing or not reachable by the TV app, the action will fail to resolve. The exact user-visible result depends on surrounding code that the diff does not show."
     ],
     "preconditions": [
      "A user opens the Live TV app's settings side panel and selects the \"Send Feedback\" item (ActionItem.onSelected() in SettingsFragment). Per index search, there are no other callers of this onSelected.",
      "Before the fix, another installed app needed an activity intent filter matching ACTION_APP_ERROR to be a resolution candidate."
     ],
     "per_major": {
      "14": "same fix: commit 2e9c27acedc2 in android-security-14.0.0_r29, identical diff (blob 762a190c -> 1ac5d81e). Patched region fully present (8/8 lines).",
      "15": "same fix: commit 7e516e62a48d in android-security-15.0.0_r17, identical diff. Patched region fully present (8/8 lines).",
      "16": "same fix: commit d688b27298d4 in android-security-16.0.0_r8, identical diff. Patched region fully present (8/8 lines).",
      "17": "same fix: commit 724154d5db03 in android-security-17.0.0_r1, identical diff. Patched region fully present (8/8 lines)."
     },
     "backport_notes": [
      "Single-file change with an identical pre-image (762a190c) on all four branches. It should apply cleanly wherever SettingsFragment.java still builds `new Intent(Intent.ACTION_APP_ERROR)` in the send-feedback ActionItem. If a fork ships a different feedback app, point FEEDBACK_PACKAGE/FEEDBACK_CLASS at that trusted target rather than going back to an implicit intent."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of onSelected() is not in the diff or context: which extras are attached (including whether the ApplicationErrorReport goes into the intent), how startActivity is called, and how errors are handled.",
      "Whether the TV app's manifest or other code contains more implicit intents for similar actions.",
      "Which ApplicationErrorReport fields beyond packageName/processName/time are populated."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28596": {
     "cve": "CVE-2026-28596",
     "summary": "Adds OutOfMemoryError to the catch clause around the game-mode-config XML meta-data parsing in GameManagerService, so an oversized package XML resource no longer crashes the process; the service falls back to the default game mode flags instead.",
     "component": "platform/frameworks/base, services/core GameManagerService (game mode config meta-data parsing)",
     "weakness": "Uncaught error (OutOfMemoryError) while parsing XML whose size is not limited, leading to a process crash (DoS)",
     "root_cause": "In services/core/java/com/android/server/app/GameManagerService.java, a method parses a package's game mode config meta-data XML. It gets the package Resources via packageManager.getResourcesForApplication, walks the parser with parser.next(), then calls resources.obtainAttributes(attributeSet, R.styleable.GameModeConfig). It caught only NameNotFoundException, XmlPullParserException and IOException. According to the commit message, a very large XML (>1 GB) makes parsing fail with an OutOfMemoryError. Nothing caught that error, so the process crashed.",
     "fix_analysis": [
      "services/core/java/com/android/server/app/GameManagerService.java, in the meta-data parsing method that returns xmlFound: the multi-catch is extended from `NameNotFoundException | XmlPullParserException | IOException` to also include `OutOfMemoryError`.",
      "The existing handler body is reused unchanged. On OutOfMemoryError it resets the flags to their defaults (mPerfModeOverridden=false, mBatteryModeOverridden=false, mAllowDownscale=true, mAllowAngle=true, mAllowFpsOverride=true), logs 'Error while parsing XML meta-data for ' + METADATA_GAME_MODE_CONFIG with Slog.e, and the method returns xmlFound.",
      "Single-file change (2 insertions, 1 deletion). It adds no size validation and no parser limits."
     ],
     "completeness": [
      "Covers only an OutOfMemoryError raised inside this one try block. The oversized XML is still processed; the fix catches the error and recovers rather than limiting input size.",
      "The handler adds no finally block. If the OutOfMemoryError happens after resources.obtainAttributes() and before array.recycle(), the TypedArray is not recycled (see lines 598-610 of the shown code).",
      "Code before the start of the try block is not shown in the material, so it is unclear whether any allocation there is covered.",
      "This commit touches no other XML-parsing sites."
     ],
     "behavior_changes": [
      "An OutOfMemoryError during game-mode-config meta-data parsing is now caught: the service logs an error, applies the default game mode flags and returns xmlFound (which may already be true), instead of crashing.",
      "No new error codes, API changes or stricter validation."
     ],
     "preconditions": [
      "An installed application that declares the game mode config meta-data and points it to an XML resource large enough to exhaust the parsing process's heap (the commit message cites >1 GB).",
      "GameManagerService must reach the parsing path for that package. The material does not show which callers trigger this method."
     ],
     "per_major": {
      "14": "same fix: commit 12567c8df8f3 in android-security-14.0.0_r29, identical two-line catch extension at line ~613 (resolved by bug_id).",
      "15": "same fix: commit 47898903e7c3 in android-security-15.0.0_r17, identical hunk at line ~622 (only the line offset differs).",
      "16": "same fix: commit 581fd9950f41 in android-security-16.0.0_r8, identical hunk at line ~636 (only the line offset differs).",
      "17": "not affected: the bulletin does not list Android 17. android-security-17.0.0_r1 has 4 of 5 lines of the patched region; the material contains no 17 commit."
     },
     "backport_notes": [
      "Trivial single-hunk backport: in GameManagerService.java's meta-data parsing method, find `catch (NameNotFoundException | XmlPullParserException | IOException ex)` and add `| OutOfMemoryError`. The hunk applied unchanged across 14, 15 and 16 with only line-number drift."
     ],
     "confidence": "high",
     "unknowns": [
      "The name of the enclosing method and class is not shown in the provided context.",
      "Where the try block begins, and whether any allocation before it could also throw OutOfMemoryError.",
      "Which callers invoke this parse, and therefore how directly a third-party app can trigger it.",
      "Whether the Android 17 branch contains an equivalent change: the region matches in 4 of 5 lines, but the material has no 17 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-2026-28602": {
     "cve": "CVE-2026-28602",
     "summary": "ClipboardService now reads and writes the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS secure setting for the explicit userId (getIntForUser/putIntForUser) instead of getInt/putInt without a user argument, so the clipboard-access notification decision uses the setting of the given user.",
     "component": "platform/frameworks/base, services/core ClipboardService (clipboard access notifications)",
     "weakness": "Per-user setting read without the target userId in a privacy notification check (wrong-user settings lookup)",
     "root_cause": "In services/core/java/com/android/server/clipboard/ClipboardService.java, the path that decides whether to show the clipboard access notification called Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, mShowAccessNotifications ? 1 : 0) without passing the userId available in that function, and returned early when the value was 0. So the notification gate was not evaluated against the setting of the user identified by userId. The setter setClipboardAccessNotificationsEnabledForUser used Settings.Secure.putInt on a resolver from createContextAsUser(UserHandle.of(userId), 0) without passing userId explicitly. The getter areClipboardAccessNotificationsEnabledForUser already used getIntForUser(..., userId).",
     "fix_analysis": [
      "ClipboardService.java, access-notification check (enclosing function not shown in the hunk header; the hunk follows 'if (clipboard.primaryClip == null) return;'): Settings.Secure.getInt(...) is replaced by Settings.Secure.getIntForUser(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, (mShowAccessNotifications ? 1 : 0), userId). The early return on value == 0 is unchanged.",
      "ClipboardService.java: setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId): Settings.Secure.putInt(resolver, CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, enable ? 1 : 0) is replaced by Settings.Secure.putIntForUser(resolver, CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, (enable ? 1 : 0), userId). The resolver is still obtained from createContextAsUser(UserHandle.of(userId), 0).",
      "No other logic changes in the shown code. The MANAGE_CLIPBOARD_ACCESS_NOTIFICATION permission checks and the default (getDefaultClipboardAccessNotificationsSetting / mShowAccessNotifications) are untouched."
     ],
     "completeness": [
      "Fixes the two accesses touched by the diff. The getter areClipboardAccessNotificationsEnabledForUser already used getIntForUser(..., userId) and is unchanged.",
      "The fallback default (mShowAccessNotifications) is unchanged and is not per user.",
      "Only the setting lookup is corrected; the following same-owner suppression logic and the notification itself are unchanged. No regression test is included (Test: Manual)."
     ],
     "behavior_changes": [
      "The clipboard access notification for a given user is now gated by that user's own CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS value, so on multi-user devices notifications may now appear or be suppressed differently than before.",
      "setClipboardAccessNotificationsEnabledForUser writes the setting explicitly for the given userId.",
      "No new exceptions, error codes or API changes in the diff."
     ],
     "preconditions": [
      "Multi-user or profile setup where the target user's CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS value differs from the value returned by the user-less lookup.",
      "An app reads the primary clip so the notification path is reached with a non-null clipboard.primaryClip; the accessing app is not the clip owner (same-owner suppression follows).",
      "For the setter hunk: a caller holding MANAGE_CLIPBOARD_ACCESS_NOTIFICATION invoking setClipboardAccessNotificationsEnabledForUser."
     ],
     "per_major": {
      "14": "same fix: DO NOT MERGE commit 6f90eac7f9e5 in android-security-14.0.0_r29; two-hunk diff (setter at ~line 516, notification check at ~line 1364 with 'return;').",
      "15": "same fix: DO NOT MERGE commit 993d9100be4c in android-security-15.0.0_r17; same diff at ~560 and ~1441.",
      "16": "same fix: DO NOT MERGE commit 80b1b1fe63b5 in android-security-16.0.0_r8; same diff at ~553 and ~1438.",
      "17": "adapted fix: 4b3bf7fb29ec in android-security-17.0.0_r1 (not DO NOT MERGE); setter hunk identical, notification hunk follows shouldSuppressAccessNotificationForUidLocked(uid) and uses 'return false;' instead of 'return;'. The getIntForUser(..., userId) change is the same."
     },
     "backport_notes": [
      "Single self-contained commit; both hunks rely on an existing in-scope userId variable. Where the notification function was refactored (as in Android 17), apply the getInt -> getIntForUser(..., userId) substitution in the function that performs the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS check."
     ],
     "confidence": "high",
     "unknowns": [
      "The name of the function containing the notification-check hunk is not shown.",
      "Which user getContext().getContentResolver() resolves to in the system server is not shown in the material.",
      "Whether putInt on the user-specific resolver already targeted userId (i.e. whether the setter hunk changes behavior) is not shown.",
      "Whether other CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS accesses exist elsewhere in ClipboardService.java is not shown.",
      "No test accompanies the fix (Test: Manual)."
     ],
     "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-2026-28603": {
     "cve": "CVE-2026-28603",
     "summary": "In Settings' AppRestrictionsFragment, the vetted copy of the app-supplied custom restrictions activity Intent now has all four FLAG_GRANT_*_URI_PERMISSION flags removed and its data and clipData cleared before Settings calls startActivityForResult on it.",
     "component": "platform/packages/apps/Settings, users / AppRestrictionsFragment (app restrictions custom activity launch)",
     "weakness": "Confused deputy: privileged app forwards an untrusted Intent carrying URI grant flags and URI payload (missing sanitization of Intent flags/data before startActivity)",
     "root_cause": "RestrictionsResultReceiver.assertSafeToStartCustomActivity in src/com/android/settings/users/AppRestrictionsFragment.java builds vettedIntent as new Intent(intent) from the untrusted Intent, resolves it, checks only that the resolved activity's package equals the expected packageName, then pins component and package. Everything else from the untrusted Intent was kept, including FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION, the data URI and the ClipData. The caller (lines 658-661) then invokes AppRestrictionsFragment.this.startActivityForResult(vettedIntent, requestCode), so Settings launched the activity with grant flags and URIs chosen by the app.",
     "fix_analysis": [
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.assertSafeToStartCustomActivity: after setComponent/setPackage, calls vettedIntent.removeFlags(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION | FLAG_GRANT_PREFIX_URI_PERMISSION).",
      "Same function: vettedIntent.setData(null) and vettedIntent.setClipData(null) clear the data URI and ClipData. The returned vettedIntent is what the existing caller passes to startActivityForResult.",
      "tests/robotests/src/com/android/settings/users/AppRestrictionsFragmentTest.java (new): assertSafeToStartCustomActivity_stripsSensitiveData calls the private method through ReflectionHelpers with a mocked PackageManager. It asserts that all four grant flags are clear and that data and clipData are null."
     ],
     "completeness": [
      "Covers the path that launches the app-supplied custom restrictions activity: all four URI grant flags, data and clipData are stripped from the vetted copy.",
      "Only Intent flags, data and clipData are touched. The Intent's extras, action, categories, type and other flags are still forwarded unchanged from the untrusted Intent (the diff does not address them).",
      "The existing package-equality check on the resolved activity is unchanged. The fix adds sanitization on top of it and does not change the vetting.",
      "The new test only exercises the stripping behaviour. No test covers the end-to-end receiver flow."
     ],
     "behavior_changes": [
      "Custom restrictions activities launched by AppRestrictionsFragment no longer receive the data URI or ClipData that the app placed on its Intent, and the four URI grant flags are removed.",
      "No new exception or error path: the Intent is sanitized silently."
     ],
     "preconditions": [
      "An installed app supplies a custom activity Intent that is processed by Settings' RestrictionsResultReceiver (constructed with the app's packageName, per the test's RestrictionsResultReceiver(\"com.example.app\", null, false)).",
      "AppRestrictionsFragment must process that result and call startActivityForResult on the vetted Intent.",
      "The Intent must resolve to an activity in the same package (existing check)."
     ],
     "per_major": {
      "14": "same fix: 0f9f6238cf25 in android-security-14.0.0_r29, identical hunk at line 687 plus new robolectric test; 4/4 patched lines present.",
      "15": "same fix: 856f8ccca12e in android-security-15.0.0_r17, identical hunk at line 683; 4/4 patched lines present.",
      "16": "same fix: 3bf7abe6e14b in android-security-16.0.0_r8, identical hunk at line 683 (same file blob as 15); 4/4 patched lines present.",
      "17": "same fix: a3cbe8fbf286 in android-security-17.0.0_r1, identical hunk at line 726; 4/4 patched lines present."
     },
     "backport_notes": [
      "Single self-contained hunk inside assertSafeToStartCustomActivity. It applies anywhere that method exists with the setComponent/setPackage lines. The EventLog tag 223578534 at the top of the method suggests the method came from an earlier fix. The robolectric test depends on ReflectionHelpers and on the RestrictionsResultReceiver(String, AppRestrictionsPreference, boolean) constructor; drop or adapt the test if the test infrastructure differs."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how RestrictionsResultReceiver obtains the custom activity Intent (the broadcast/result extras path).",
      "The material does not show which content providers or URI permissions the bulletin considers exposed.",
      "Whether URIs carried in the forwarded extras could still lead to grants is not shown by the material.",
      "Whether the two internal Cherrypick-From commits differ from each other is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28613": {
     "cve": "CVE-2026-28613",
     "summary": "ChannelImpl.initAppLinkTypeAndIntent in the Live TV app now refuses to build a channel app-link Intent whose resolved target activity is exported and declares an android:permission. This is in addition to the existing rejection of activities inside the TV app's own package.",
     "component": "platform/packages/apps/TV (Live TV), channel app links (src/com/android/tv/data/ChannelImpl.java)",
     "weakness": "Intent redirection (per the commit title): missing permission-protection check on the activity resolved from a parsed intent URI before it is accepted as the channel's app-link Intent.",
     "root_cause": "ChannelImpl.initAppLinkTypeAndIntent parses the channel's mAppLinkIntentUri with Intent.parseUri(..., Intent.URI_INTENT_SCHEME) and resolves it via intent.resolveActivityInfo(pm, 0). Before the fix, the only filter rejected targets whose packageName equals CommonConstants.BASE_PACKAGE or starts with it plus '.' (the TV app itself). Any other resolvable activity was accepted as mAppLinkIntent with mAppLinkType = APP_LINK_TYPE_CHANNEL. That included exported activities guarded by an android:permission. The commit title calls this 'Intent Redirection to permission protected Activities'.",
     "fix_analysis": [
      "src/com/android/tv/data/ChannelImpl.java:initAppLinkTypeAndIntent: after resolving activityInfo, the method computes boolean isPermissionProtected = activityInfo.exported && (activityInfo.permission != null).",
      "If isPermissionProtected is true, it logs 'Attempt to add app link to permission protected activity: <uri>' and returns. mAppLinkType stays APP_LINK_TYPE_NONE and mAppLinkIntent stays null (the values set at the top of the method), so no app link is created for that channel.",
      "The existing isProtectedActivity (BASE_PACKAGE) check is kept and evaluated first. The new check is a second, independent early return placed before mAppLinkIntent is assigned and the EXTRA_APP_LINK_CHANNEL_URI extra is added.",
      "A code comment records a decision not to also restrict chooser Intents or ACTION_SEND. Its reasons: with Intent.parseUri an attacker cannot wrap Intent.EXTRA_INTENT in the app link, and ACTION_SEND was tested not to yield FLAG_GRANT_* here.",
      "Single commit, single hunk. The Log.w formatting change is cosmetic."
     ],
     "completeness": [
      "Covers: app-link targets resolved from mAppLinkIntentUri whose ActivityInfo is exported and has a non-null permission string. The existing check still covers any target inside the TV app's own package.",
      "Does not cover: exported targets with no permission attribute, which remain allowed. The check looks only at the resolved activity's own permission attribute. This diff does not sanitize Intent flags or extras in the parsed URI.",
      "Chooser and ACTION_SEND Intents are deliberately left unrestricted. The comment's justification is based on testing, not enforced in code.",
      "Only the APP_LINK_TYPE_CHANNEL branch shown in the diff changes. The rest of initAppLinkTypeAndIntent (after line 710) is not in the material, so other app-link type branches cannot be judged."
     ],
     "behavior_changes": [
      "Channels whose app-link intent URI resolves to an exported, permission-protected activity now get no app link: getAppLinkType returns APP_LINK_TYPE_NONE and getAppLinkIntent returns null, instead of an Intent.",
      "A new Log.w warning, 'Attempt to add app link to permission protected activity: ...', is logged in that case.",
      "Any channel whose app link legitimately targeted an exported, permission-protected activity loses that app link."
     ],
     "preconditions": [
      "A channel with non-empty app link text (mAppLinkText) and a non-empty app link intent URI (mAppLinkIntentUri).",
      "The intent URI must resolve (intent.resolveActivityInfo) to an activity outside the TV app's own package, because BASE_PACKAGE targets were already rejected.",
      "The Live TV app must use getAppLinkType/getAppLinkIntent for that channel. How the Intent is then used or launched is not shown in the material."
     ],
     "per_major": {
      "14": "Same fix: platform/packages/apps/TV e64e4b90fc77 in android-security-14.0.0_r29 (diff index 5be1179d..62c06cc2), carrying Cherrypick-From references to two internal changes.",
      "15": "Same fix: platform/packages/apps/TV 481a7a588acd in android-security-15.0.0_r17, identical diff and blob hashes.",
      "16": "Same fix: platform/packages/apps/TV 86fb106012ec in android-security-16.0.0_r8, identical diff and blob hashes.",
      "17": "Same fix: platform/packages/apps/TV a10682aac138 in android-security-17.0.0_r1, identical diff and blob hashes."
     },
     "backport_notes": [
      "Single-file, single-hunk Java change with the same pre-image (5be1179d) on all four branches, so a straight cherry-pick should apply. The only context dependency is the existing isProtectedActivity / CommonConstants.BASE_PACKAGE check just above it. If a fork lacks that check, add the isPermissionProtected check right before the mAppLinkIntent = intent assignment."
     ],
     "confidence": "high",
     "unknowns": [
      "Where mAppLinkIntentUri and mAppLinkText come from, and who can write them.",
      "Where and with what flags the Live TV app launches mAppLinkIntent (the startActivity call site is not in the material).",
      "Which permissions the Live TV app holds that made the redirection valuable. The bulletin only gives High / EoP.",
      "The rest of initAppLinkTypeAndIntent after line 710 (other app-link type branches).",
      "Details of the original report in bug 329233015 and of the two internal cherry-pick sources."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28614": {
     "cve": "CVE-2026-28614",
     "summary": "Adds a caller check to SystemUI's SlicePermissionActivity.onCreate so the slice permission dialog is only shown when the activity was actually launched by the slice provider's package, SystemUI itself, or the \"android\" package; any other launcher is finished immediately.",
     "component": "platform/frameworks/base, SystemUI (packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java), slice permission request dialog",
     "weakness": "Missing caller validation (confused deputy): the activity trusted intent extras to identify the requesting package without verifying who launched it",
     "root_cause": "SlicePermissionActivity.onCreate reads mUri from SliceProvider.EXTRA_BIND_URI and mCallingPkg from SliceProvider.EXTRA_PKG, both taken directly from the incoming Intent. The only validation was that the URI type is SLICE_TYPE, the action is ACTION_REQUEST_SLICE_PERMISSION, and that verifyCallingPkg() compared an optional \"provider_pkg\" extra against the resolved provider package. Nothing checked which application actually started the activity, so the package named in EXTRA_PKG (used as the requesting app label in the dialog) was fully controlled by whoever launched the activity. Per the commit message, this let a malicious app trick the user into granting slice permissions by spoofing the package name in the intent (a confused deputy in SystemUI).",
     "fix_analysis": [
      "SlicePermissionActivity.java:onCreate: after mProviderPkg is resolved and verifyCallingPkg() runs, and before any dialog UI is built, a new check `if (!isCallerValid()) { finish(); return; }` stops the activity for untrusted launchers.",
      "SlicePermissionActivity.java:isCallerValid (new): finds the real launcher with getLaunchedFromPackage(), falling back to getCallingPackage() when that is null. It returns true only if the launcher is non-null and equals mProviderPkg (the package owning the slice authority), equals getPackageName() (SystemUI itself), or equals the literal \"android\". Otherwise it logs \"Direct launch blocked. Expected provider ... or system, but got ...\" at error level and returns false.",
      "SlicePermissionActivityTest.kt (new): unit tests covering valid launchers (provider package, com.android.systemui, android, and fallback to getCallingPackage) and an invalid launcher (SPOOF_PKG) asserting that finish() is invoked."
     ],
     "completeness": [
      "The check gates the only entry point shown (onCreate) and sits before the dialog is created, so the user is never prompted for launches from untrusted packages.",
      "Only the launcher identity is validated. EXTRA_PKG itself is still taken from the intent and not cross-checked against the launcher, so a trusted launcher can still name any package.",
      "The allowlist is by package name string (mProviderPkg, SystemUI's own package name, \"android\"), not by UID or signature.",
      "If both getLaunchedFromPackage() and getCallingPackage() return null, the activity is blocked (fail closed).",
      "The existing verifyCallingPkg() logic for the \"provider_pkg\" extra is unchanged; the new check is added on top of it.",
      "The onClick/grant path and the AndroidManifest exported state of the activity are not in the diff and are unchanged by it."
     ],
     "behavior_changes": [
      "Launches of SlicePermissionActivity from any package other than the slice provider's package, SystemUI, or \"android\" now finish immediately without showing the permission dialog.",
      "A new Log.e line \"Direct launch blocked. Expected provider <pkg> or system, but got <caller>\" is emitted for blocked launches.",
      "Requests launched by the slice provider's own package still pass the check.",
      "New test file SlicePermissionActivityTest.kt added to the SystemUI test suite."
     ],
     "preconditions": [
      "An app on the device can start SystemUI's SlicePermissionActivity with action SliceManager.ACTION_REQUEST_SLICE_PERMISSION, an EXTRA_BIND_URI whose ContentResolver.getType() returns SliceProvider.SLICE_TYPE, and an arbitrary EXTRA_PKG (the existing intent checks in onCreate must pass).",
      "User interaction is required: the user must accept the permission dialog that SystemUI presents (the bulletin rates this High / EoP)."
     ],
     "per_major": {
      "14": "same fix: commit c6b5038a9e23 in android-security-14.0.0_r29 (cherry-pick of 7f44449562bc, Change-Id I49822e9f7eb2d29278d1266ae2fd51fc154744db); identical code change at different line offsets (hunks @@ -66 / -110); 8/8 patched lines present.",
      "15": "same fix: commit 971f1d196295 in android-security-15.0.0_r17; hunks @@ -72 / -116 and the same new test file; 8/8 patched lines present.",
      "16": "same fix: commit dba6ec2c05ff in android-security-16.0.0_r8; identical hunks and test file as 15; 8/8 patched lines present.",
      "17": "same fix: commit 7e25b93a1faa in android-security-17.0.0_r1; identical hunks and test file as 15/16; 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit; the Java hunk applies to any tree where onCreate still calls verifyCallingPkg() right after resolving mProviderPkg. The fix relies on Activity.getLaunchedFromPackage(); SystemUI is platform code and can use this API even where it is not public SDK, but confirm it exists and returns the launcher package on the target branch (general platform knowledge).",
      "The new Kotlin test uses reflection on Activity internals (mActivityInfo, mFragments, mApplication, mWindow) and OnBackInvokedDispatcher; it may need adjustment on forks with customized Activity or SysuiTestCase, and can be dropped without affecting the fix."
     ],
     "confidence": "high",
     "unknowns": [
      "The onClick handler that actually grants slice access is not in the material, so the exact privilege granted on user acceptance is not shown.",
      "The AndroidManifest declaration (exported state, intent filter, permissions) of SlicePermissionActivity is not shown.",
      "The catch block following the try in onCreate is not shown, so behaviour when resolveContentProvider returns null is unknown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28626": {
     "cve": "CVE-2026-28626",
     "summary": "Adds a launching-package identity check (getLaunchedFromPackage() on SDK 34+) to SetupPassthroughActivity so that the embedded setup Intent is only forwarded when the activity was launched by CommonConstants.BASE_PACKAGE; otherwise the activity finishes without forwarding.",
     "component": "platform/packages/apps/TV (LiveTv), SetupPassthroughActivity",
     "weakness": "Intent redirection due to insufficient caller verification",
     "root_cause": "SetupPassthroughActivity takes a setup Intent from the incoming Intent's extras, copies the remaining extras into it (InputSetupActionUtils.removeSetupIntent(extras); setupIntent.putExtras(extras)) and then forwards it. The fix's log message (\"Not forwarding intent\") implies this forwarding. Before the fix, the only caller check visible in the shown context is a getCallingActivity() comparison against CommonConstants.BASE_PACKAGE. Google added a getLaunchedFromPackage() check ahead of it, which shows that the existing check was not considered enough to verify the launcher. The rest of that branch is not shown, so the exact pre-fix behaviour for an untrusted or null calling activity is not visible.",
     "fix_analysis": [
      "src/com/android/tv/SetupPassthroughActivity.java: imports android.os.Build.",
      "src/com/android/tv/SetupPassthroughActivity.java (setup-intent forwarding block inside the try, immediately after setupIntent.putExtras(extras) and before the existing getCallingActivity() check): when Build.VERSION.SDK_INT >= 34, the fix calls getLaunchedFromPackage(). If the result is null or not equal to CommonConstants.BASE_PACKAGE, it logs a warning (\"Calling package ... is not trusted. Not forwarding intent.\"), calls finish() and returns.",
      "The existing getCallingActivity()/BASE_PACKAGE check is kept unchanged. On SDK 34+ it now runs only after the launching-package check passes."
     ],
     "completeness": [
      "Covers the setup-intent forwarding path in SetupPassthroughActivity on SDK 34 or newer, which includes Android 14 through 17.",
      "The new check is wrapped in Build.VERSION.SDK_INT >= 34. On older SDK levels only the original getCallingActivity() check applies, so the new check adds no protection there.",
      "The check compares against the single package CommonConstants.BASE_PACKAGE. The value of that constant and the rest of the enclosing branch are not shown in the material.",
      "Only this one activity and code location are changed. The material does not show other TV app components that forward Intents taken from extras, so coverage beyond this path cannot be judged."
     ],
     "behavior_changes": [
      "On SDK 34+, if SetupPassthroughActivity is launched by any package other than CommonConstants.BASE_PACKAGE, it now finishes without forwarding the embedded setup Intent and logs a Log.w warning that includes the calling package name.",
      "Launches where getLaunchedFromPackage() returns null are treated as untrusted and also finish.",
      "No behaviour change on SDK < 34."
     ],
     "preconditions": [
      "The caller must be able to reach SetupPassthroughActivity. Its manifest export status is not shown in the material.",
      "The incoming Intent must carry a setup Intent in its extras, so that execution reaches the forwarding block.",
      "Before the fix, a caller only had to satisfy or get past the getCallingActivity()-based check. The exact pre-fix handling of a null or untrusted calling activity is not visible in the material."
     ],
     "per_major": {
      "14": "same fix: commit 738d2cc1f03e in android-security-14.0.0_r29, identical diff (file 2a4a556f..87a66a0e), 8/8 lines of the patched region present.",
      "15": "same fix: commit f23429d7cad6 in android-security-15.0.0_r17, identical diff, 8/8 lines present.",
      "16": "same fix: commit 856a260c2504 in android-security-16.0.0_r8, identical diff, 8/8 lines present.",
      "17": "same fix: commit 25c98c910675 in android-security-17.0.0_r1, identical diff, 8/8 lines present."
     },
     "backport_notes": [
      "Single-commit fix with an identical diff on 14 to 17 (same pre/post blob hashes), so it should apply cleanly to any tree where SetupPassthroughActivity.java matches blob 2a4a556f. Activity.getLaunchedFromPackage() is public API only from API level 34 (general platform knowledge), which matches the SDK_INT >= 34 guard. On a branch compiled against an older SDK the call would not compile."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of the existing getCallingActivity() branch and the code that actually starts setupIntent are not shown, so the precise pre-fix behaviour for a null or foreign calling activity is unknown.",
      "Why the getCallingActivity() check alone was insufficient is not explained in the material.",
      "The value of CommonConstants.BASE_PACKAGE and the manifest declaration (exported status, intent filters) of SetupPassthroughActivity are not in the material.",
      "The two internal Cherrypick-From commits referenced in the commit message are not available, so it is unknown whether they contain anything beyond this diff."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28631": {
     "cve": "CVE-2026-28631",
     "summary": "Adds getWindow().setHideOverlayWindows(true) to the mini resolver consent dialog that IntentForwarderActivity shows before redirecting an intent into the managed profile, so overlay windows are hidden over the consent UI. A companion commit moves the executor and device-provisioned lookups behind the Injector so the activity test can run.",
     "component": "platform/frameworks/base, core/java/com/android/internal/app/IntentForwarderActivity (cross-profile intent forwarding, mini resolver consent UI)",
     "weakness": "Missing UI overlay protection on a security-relevant consent dialog (tapjacking-prone window; improper restriction of rendered UI layers)",
     "root_cause": "On Android 14, IntentForwarderActivity.onCreate shows a consent dialog when the caller is not privileged enough to skip consent and the target is not the profile owner. It inflates R.layout.miniresolver with setContentView and asks the user to confirm opening the intent in the work profile (button_open calls startActivityAsCaller). This consent window was never marked with setHideOverlayWindows(true), so overlay windows from other apps could stay visible above it while the user decided.",
     "fix_analysis": [
      "Commit cb471de52128 (Set hide overlays on mini resolver), core/java/com/android/internal/app/IntentForwarderActivity.java:onCreate: adds getWindow().setHideOverlayWindows(true) right after setContentView(R.layout.miniresolver). This one line is the entire security change.",
      "Commit 2fadc52d7f18 (Fix & speed up IntentForwarderActivityTest), applied on top of cb471de52128: core/java/com/android/internal/app/IntentForwarderActivity.java adds Injector.getExecutorService() and Injector.isDeviceProvisioned(). onCreate now uses mInjector.getExecutorService(), whose default still returns Executors.newSingleThreadExecutor(). isDeviceProvisioned() now delegates to mInjector.isDeviceProvisioned(), whose default still reads Settings.Global.DEVICE_PROVISIONED. Production behavior does not change.",
      "Same commit, core/tests/coretests/AndroidManifest.xml: adds uses-permission android.permission.HIDE_OVERLAY_WINDOWS. Per the commit message, without it IntentForwarderActivity crashes when it calls setHideOverlayWindows() in the test.",
      "Same commit, core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java: TestInjector now provides a synchronous AbstractExecutorService and returns true from isDeviceProvisioned(). The commit also removes the 30 s awaitTermination in the wrapper onCreate and the direct Settings.Global writes, and replaces two backwards assertNull checks with assertNotNull."
     ],
     "completeness": [
      "setHideOverlayWindows(true) is applied only in the mini resolver path, the block that calls setContentView(R.layout.miniresolver). In the material this is the only interactive UI that IntentForwarderActivity inflates. The skip-consent path (privilegedCallerAskedToSkipUserConsent or intentToLaunchProfileOwner) calls startActivityAsCaller and finish() without setting content, and showToast only displays a Toast.",
      "The second commit is a test enabler and Injector refactor. It adds no protection and changes none. It fixes existing assertions, and the material shows no test that checks the hide-overlay flag.",
      "The material does not show whether the full-size ResolverActivity or other resolver UIs set the same flag."
     ],
     "behavior_changes": [
      "While the mini resolver consent dialog of IntentForwarderActivity is shown, its window has hide-overlay-windows set, so non-system application overlay windows are hidden for that time (semantics of Window.setHideOverlayWindows, general platform knowledge).",
      "setHideOverlayWindows requires android.permission.HIDE_OVERLAY_WINDOWS. Any host without it fails (general platform knowledge). For the test host, the commit message confirms this.",
      "Executor creation and the DEVICE_PROVISIONED read now go through the Injector. The default implementations are unchanged."
     ],
     "preconditions": [
      "An intent is routed through IntentForwarderActivity with component FORWARD_INTENT_TO_MANAGED_PROFILE and a managed profile exists, so forwarding is possible.",
      "The consent dialog path is taken: the launch intent does not carry EXTRA_SKIP_USER_CONFIRMATION from a caller holding INTERACT_ACROSS_USERS, and the target is not the profile owner package (code context lines 212-235).",
      "Another app can draw overlay windows on top of the consent dialog. The material gives no details."
     ],
     "per_major": {
      "14": "same fix: cb471de52128 adds the one line in onCreate right after setContentView(R.layout.miniresolver), and the test refactor 2fadc52d7f18 is applied on top. Both are in android-security-14.0.0_r29 (linked by bug_id). 4/4 patched lines present.",
      "15": "adapted fix: 8e66b48894f7 in android-security-15.0.0_r17 adds the same line after setContentView(R.layout.miniresolver). On 15 the mini resolver is built in a separate helper method (parameters resolverTitle and pmForTargetUser, around line 330), not inline in onCreate. 4/4 patched lines present.",
      "16": "The material shows only the test refactor 71d6dc109528 in android-security-16.0.0_r8: the same Injector changes plus removal of the IntentForwarderActivityTest exclude filter in core/tests/coretests/Android.bp. No setHideOverlayWindows commit is shown for 16. The resolver reports 8/8 patched lines present.",
      "17": "Same as 16: 323c8b968a88 in android-security-17.0.0_r1 (linked by change_id) is the test refactor, including the Android.bp exclude-filter removal. No separate setHideOverlayWindows commit is shown. 8/8 patched lines present."
     },
     "backport_notes": [
      "Apply cb471de52128 first. It is the security change and stands alone: one line after setContentView(R.layout.miniresolver). Find the spot by that call, because the enclosing method differs between 14 and 15. Then apply 2fadc52d7f18 if you want the test. Its HIDE_OVERLAY_WINDOWS manifest permission is needed, or the test crashes once the overlay call exists.",
      "On 16 and 17 the test commit also edits core/tests/coretests/Android.bp to remove the IntentForwarderActivityTest exclude filter. The 14 commit has no such hunk, so expect that file to differ when porting across majors."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether, and in which commit, the setHideOverlayWindows(true) line landed on Android 16 and 17. The material shows only the test refactor for those branches.",
      "Whether the parent-forwarding path (FORWARD_INTENT_TO_PARENT) can reach the mini resolver consent dialog.",
      "Whether other resolver UIs (for example ResolverActivity in frameworks/base or the IntentResolver module) already set the same flag.",
      "The material shows no regression test for the hide-overlay flag."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28633": {
     "cve": "CVE-2026-28633",
     "summary": "In safe mode, VoiceInteractionManagerService no longer parses the recognition service metadata of third-party (non-FLAG_SYSTEM) recognizers in RecognitionServiceInfo.getAvailableServices, nor the metadata of the already-configured current recognizer, to prevent a malicious large metadata file from causing boot issues (OOM) in safe mode.",
     "component": "platform/frameworks/base, services/voiceinteraction (VoiceInteractionManagerService / RecognitionServiceInfo)",
     "weakness": "Uncontrolled resource consumption: system_server parses app-supplied recognition service XML metadata during initialization, and a large metadata file can cause an OOM (DoS), including in safe mode",
     "root_cause": "When VoiceInteractionManagerService initializes recognizer settings for a user, RecognitionServiceInfo.getAvailableServices calls RecognitionServiceInfo.parseInfo on every resolved recognition service. A separate path also calls parseInfo on the currently configured recognizer (curRecognizer != null branch). Per the commit message and inline comments, a malicious app can supply a large XML recognition service metadata file, and loading it can cause an OOM that crashes the system and causes boot issues. This parsing also ran in safe mode, even though third-party apps are not launchable in safe mode.",
     "fix_analysis": [
      "RecognitionServiceInfo.java:getAvailableServices gains a boolean isSafeMode parameter. For each ResolveInfo, if isSafeMode is true and serviceInfo.applicationInfo.flags lacks ApplicationInfo.FLAG_SYSTEM, it skips parseInfo, adds new RecognitionServiceInfo(serviceInfo, selectableAsDefault=true, parseError=\"\") and continues. FLAG_SYSTEM recognizers are still parsed in safe mode. In normal mode the behavior is unchanged.",
      "VoiceInteractionManagerService.java (curRecognizer != null branch of initialization): the guard around RecognitionServiceInfo.parseInfo(mContext.getPackageManager(), recognizerInfo) changes from `recognizerInfo != null` to `recognizerInfo != null && !mSafeMode`, so the metadata of the already-set recognizer (system or third-party) is not parsed in safe mode.",
      "VoiceInteractionManagerService.java: the call at the recognizer selection site (around line 834 on Android 14) becomes RecognitionServiceInfo.getAvailableServices(mContext, userHandle, mSafeMode).",
      "Single commit. parseInfo itself is not modified."
     ],
     "completeness": [
      "The fix only suppresses metadata parsing when mSafeMode is true. In a normal boot, parseInfo still runs on every resolved recognizer's metadata, so the fix limits the safe-mode boot path rather than the parsing itself.",
      "In getAvailableServices the skip applies only to apps without ApplicationInfo.FLAG_SYSTEM; system recognizers are still parsed in safe mode. Updated preinstalled apps keep FLAG_SYSTEM, so their metadata is still parsed (general platform knowledge).",
      "The curRecognizer branch skips parsing for any recognizer in safe mode, system or not, which is broader than the getAvailableServices filter.",
      "parseInfo is unchanged. The material shows no other callers beyond these two sites. The TODO about using this class in VoiceInputHelper refers to possible future use."
     ],
     "behavior_changes": [
      "In safe mode, third-party recognition services are returned by getAvailableServices with selectableAsDefault=true and an empty parse error without their metadata being parsed; the comment says they still will not be auto-selected because only preinstalled recognizers are auto-selected.",
      "In safe mode, a currently configured recognizer whose metadata marks it as not selectableAsDefault is no longer detected and unset during initialization, and its parse-error warning is not logged (that block now runs only when !mSafeMode).",
      "getAvailableServices signature changed (new boolean parameter); any additional caller must be updated."
     ],
     "preconditions": [
      "An installed app declares a recognition service whose metadata is parsed by RecognitionServiceInfo.parseInfo (no special permission is indicated in the material).",
      "VoiceInteractionManagerService initialization runs, which resolves recognizers via getAvailableServices and/or parses the current recognizer's metadata.",
      "The fixed behavior applies only when the device boots in safe mode (mSafeMode true); in normal mode the parsing still occurs."
     ],
     "per_major": {
      "14": "same fix; commit 20ecd72dd4c8 in android-security-14.0.0_r29, cherry-pick of a81b51b6a6e0 (Change-Id I52435ca8da633a73fc63e07c6fea3ec9cd8cd986); 8/8 patched lines present",
      "15": "same fix; commit df4d44965279 in android-security-15.0.0_r17, identical hunks, VoiceInteractionManagerService.java hunks at 648 and 912 (old) / 915 (new); 8/8 present",
      "16": "same fix; commit a660d0c46482 in android-security-16.0.0_r8, identical hunks at 648 and 912 (old) / 915 (new); 8/8 present",
      "17": "same fix; commit 4c5500a0d604 in android-security-17.0.0_r1, identical hunks at 818 and 1101 (old) / 1104 (new); 8/8 present"
     },
     "backport_notes": [
      "Single self-contained commit; the RecognitionServiceInfo.java hunks are identical across 14 through 17 (blob 05d4b9305d30 -> 376ba571f790), and only the VoiceInteractionManagerService.java line offsets differ. The fix relies on the existing mSafeMode field in VoiceInteractionManagerService; any extra downstream callers of getAvailableServices must pass the new parameter."
     ],
     "confidence": "high",
     "unknowns": [
      "The parseInfo implementation, and whether it bounds resource or XML size, are not shown.",
      "Exact size or structure of metadata that triggers the OOM, and whether it happens in resource loading or XML parsing.",
      "Whether Google shipped separate hardening of parseInfo; none is in the material.",
      "Names of the enclosing VoiceInteractionManagerService methods and whether other in-tree callers of parseInfo exist."
     ],
     "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-2026-28634": {
     "cve": "CVE-2026-28634",
     "summary": "Adds a check to PhoneInterfaceManager.handleUssdRequest that the calling user is associated with the subscription. A caller that passes enforceCallPermission can no longer send USSD requests on a subscription that is not associated with its user.",
     "component": "platform/packages/services/Telephony, PhoneInterfaceManager (ITelephony binder service, USSD handling)",
     "weakness": "Missing authorization check before a privileged operation: the caller's user is not validated against the target subscription",
     "root_cause": "PhoneInterfaceManager.handleUssdRequest(int subId, String ussdRequest, ResultReceiver wrappedCallback) called only enforceCallPermission() (and, on 15+, enforceTelephonyFeatureWithException). It then cleared the calling identity and dispatched CMD_HANDLE_USSD_REQUEST for the subId the caller supplied. Nothing verified that the Binder calling user was associated with that subscription.",
     "fix_analysis": [
      "src/com/android/phone/PhoneInterfaceManager.java:handleUssdRequest: after the existing permission checks and before Binder.clearCallingIdentity(), the method now calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mApp, subId, Binder.getCallingUserHandle()). If that returns false, it throws SecurityException(\"The subscription <subId> is not associated with the calling user\").",
      "The check runs while the caller's identity is still in effect, so Binder.getCallingUserHandle() reflects the real caller rather than the phone process.",
      "tests/src/com/android/phone/PhoneInterfaceManagerTest.java adds two tests. testHandleUssdRequest_SubscriptionNotAssociatedWithUser_ThrowsSecurityException mocks SubscriptionManagerService.isSubscriptionAssociatedWithUser to return false and asserts a SecurityException. testHandleUssdRequest_ValidSubscription_Success mocks it to return true and accepts the main-thread deadlock RuntimeException as proof that the check passed. setUp now installs the mocked SubscriptionManagerService as sInstance and stubs getActiveSubIdList."
     ],
     "completeness": [
      "The fix covers only the handleUssdRequest entry point. The single new check gates the whole method before sendRequest(CMD_HANDLE_USSD_REQUEST).",
      "The sibling MMI path handlePinMmiForSubscriber(int subId, String dialString) appears directly below in the same file. It still calls enforceModifyPermission(), then clearCallingIdentity() and sendRequest(CMD_HANDLE_PIN_MMI, dialString, subId), with no user-association check. The fix does not touch it.",
      "The new check runs before SubscriptionManager.isValidSubscriptionId(subId). The outcome for invalid subIds therefore depends on TelephonyPermissions.checkSubscriptionAssociatedWithUser, and its implementation is not in the material."
     ],
     "behavior_changes": [
      "handleUssdRequest now throws SecurityException to the binder caller when the calling user is not associated with the given subscription. Before the fix, the request was dispatched.",
      "Exception message: \"The subscription <subId> is not associated with the calling user\".",
      "Callers that relied on an invalid subId returning silently may now get a SecurityException first, depending on how checkSubscriptionAssociatedWithUser handles invalid ids."
     ],
     "preconditions": [
      "The caller passes enforceCallPermission (TelephonyManager.sendUssdRequest is annotated with CALL_PHONE, per index search). On 15+, it must also pass enforceTelephonyFeatureWithException for FEATURE_TELEPHONY_RADIO_ACCESS.",
      "The caller can reach ITelephony.handleUssdRequest, for example through TelephonyManager.sendUssdRequest (per index search).",
      "The target subscription is not associated with the caller's user, because the new check compares Binder.getCallingUserHandle() against subId."
     ],
     "per_major": {
      "14": "Same fix (2a177c06ec48, android-security-14.0.0_r29). The check is inserted directly after enforceCallPermission(); handleUssdRequest on this security tag has no enforceTelephonyFeatureWithException call. 8/8 lines of the patched region present.",
      "15": "Same fix (83d27ac3f32f, android-security-15.0.0_r17). The same 5-line check is inserted after enforceTelephonyFeatureWithException(...). Test setUp also stubs HalVersion, Phone.getContext, DataNetworkController and getDefaultPhone. 8/8 lines present.",
      "16": "Same fix (03cc6ee76027, android-security-16.0.0_r8). The same check is inserted after enforceTelephonyFeatureWithException. The test file drops its local Phone mock and adds imports. 8/8 lines present.",
      "17": "Same fix (5ef227b0a2f7, android-security-17.0.0_r1). The same check is inserted after enforceTelephonyFeatureWithException. The test file reorganises its mock fields. 8/8 lines present."
     },
     "backport_notes": [
      "The production change is 5 self-contained lines. The target tree must have TelephonyPermissions.checkSubscriptionAssociatedWithUser(Context, int, UserHandle) and mApp. If the target's handleUssdRequest contains enforceTelephonyFeatureWithException, place the check after it, as the 15/16/17 commits do. Per index search, android-14.0.0_r75 has that call; android-security-14.0.0_r29 does not.",
      "The test changes depend on TelephonyTestBase.replaceInstance and the SubscriptionManagerService singleton. They vary by branch and can be adapted separately from the production fix."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how TelephonyPermissions.checkSubscriptionAssociatedWithUser is implemented (for example, how it treats invalid subIds, system or root callers, or single-user devices), or which project defines it.",
      "The two Cherrypick-From changes (b76670f3c5ba, 0dfb804981a7) are internal. Whether they differ from the public commit is unknown.",
      "The bulletin rates this High/EoP, but the material does not describe the concrete impact of sending USSD on another user's subscription."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28638": {
     "cve": "CVE-2026-28638",
     "summary": "MediaProvider's XMP redaction for ISO (MP4-style) media is extended to redact EXIF GPS fields stored as XML attributes (not only as element names) and, on Android 14, to process every XMP box in the container rather than only the first one.",
     "component": "platform/packages/providers/MediaProvider, media metadata redaction (util/IsoInterface, util/XmpInterface, util/XmpDataParser)",
     "weakness": "Incomplete redaction / missing check for an alternate encoding of sensitive metadata (information disclosure)",
     "root_cause": "XmpInterface (Android 14) and XmpDataParser (15+) decided whether to redact an XMP element only by testing the element's own namespace and name against NS_EXIF and the redacted EXIF tag set. XMP allows the same properties (e.g. GPSLatitude) to be expressed as attributes on an element; those were never matched, so they were not redacted for callers without ACCESS_MEDIA_LOCATION (per commit message). On Android 14, XmpInterface.fromContainer also extracted only the first XMP UUID box (be7acfcb-97a9-42e8-9c71-999491e3afac) or, if none, the first BOX_XMP box via IsoInterface.getBoxBytes, so further XMP boxes were never scanned. On Android 16/17, IsoInterface's filtered flatten switch did not keep BOX_XMP boxes, so plain XMP boxes were not found by the lookup.",
     "fix_analysis": [
      "IsoInterface.java (14): adds getBoxRangesList(int)/getBoxRangesList(UUID) and getBoxBytesList(int)/getBoxBytesList(UUID). Each iterates mFlattened and returns one entry per matching box ([range[0]+headerSize, range[0]+range[1]] and box.data respectively), unlike the first-match-only getBoxBytes variants.",
      "XmpInterface.java (14): the constructor body moves into a new process(rawXmp, redactedExifTags, xmpOffsets) that can be called repeatedly on one instance; mRedactedXmp is assigned only on the first call. In the START_TAG loop, before the rdf:Description/EXIF name checks, it walks parser.getAttributeCount() and sets hasSensitiveAttribute when an attribute has namespace NS_EXIF and a name in redactedExifTags. If set, it reads tokens until the matching END_TAG (depth counting), records [xmpOffset+start, xmpOffset+offset] in mRedactedRanges, overwrites that span in rawXmp with spaces, and continues. The existing element-name redaction path now also writes into rawXmp (the current box) instead of mRedactedXmp.",
      "XmpInterface.fromContainer (14): collects all UUID XMP boxes, then all BOX_XMP boxes, into bufs/xmpOffsetsList. It builds XmpInterface from the first one (or empty arrays if there are none) and calls process() for each remaining box, so every XMP box adds redaction ranges.",
      "XmpDataParser.java (15/16/17): adds isSensitiveTagOrAttribute(), which returns true if the element itself is NS_EXIF plus a redacted tag, or if any attribute is NS_EXIF with a redacted tag name. The metadata-parsing loop and the redacted-ranges loop both call it instead of the inline element-only check. In the parsing loop the check is no longer an else-if, so it runs after format/DocumentID handling for the same element.",
      "IsoInterface.java (15): restructured so parseNextBox adds only selected box types (BOX_UUID with XMP_UUID, BOX_META, BOX_HDLR, BOX_XYZ, BOX_XMP, BOX_LOCI, BOX_GPS, BOX_GPS0) to mFlattened. It introduces MAX_XMP_SIZE_BYTES (1 MiB) through allocateBuffer for XMP data, and replaces getBoxBytes(UUID)/getBoxRanges(UUID) with getBoxBytesForXmpUuid()/getBoxRangesForXmpUuid(), which use a fixed XMP_UUID constant. XmpData.extractXmpData is updated to the new names.",
      "IsoInterface.java (16/17): adds BOX_XMP to the switch that keeps boxes in the flattened list, so getBoxBytes(BOX_XMP) finds plain XMP boxes.",
      "Tests: a new tests/res/raw/gps_test_xmp.mp4 and testRedactXmp_RemovesExifGpsAttributes, which asserts that the redacted XMP no longer contains exif:GPSLatitude or exif:GPSLongitude."
     ],
     "completeness": [
      "Attribute matching covers only attributes in namespace NS_EXIF whose local name is in the redacted EXIF tag set. Sensitive values under other namespaces are not checked (same scope as the existing element-name check).",
      "Android 14: mRedactedRanges covers all XMP boxes, but mRedactedXmp (returned by getRedactedXmp) is only the first box's buffer. Later boxes are redacted in place in the IsoInterface box.data arrays but are not part of the returned XMP copy.",
      "Android 14: getBoxRangesList uses only range[0]/range[1] of each box (one range per box), unlike the older getBoxRanges, which iterated all range pairs.",
      "Android 15/16/17: XmpData.extractXmpData still takes only the first XMP UUID box and falls back to the first BOX_XMP box. The 15 diff shows this, and 16/17 end with the same XmpDataParser blob (3e48d1d91). The multi-box handling added on Android 14 is not in these diffs.",
      "Android 14: when a sensitive attribute is found, the whole enclosing element (children included) is redacted and the loop continues before the rdf:Description parsing, so non-sensitive metadata in that element is also dropped. This is over-redaction, not a gap."
     ],
     "behavior_changes": [
      "Android 14: an element carrying an NS_EXIF attribute in the redacted set is now blanked in full (span up to its matching end tag) in the current XMP buffer and in the file-level redaction ranges. On 15/16/17 such elements now enter the existing XmpDataParser redaction path.",
      "Android 14: on an element with a sensitive attribute, dc:format, DocumentID, OriginalDocumentID and InstanceID in that same element are no longer extracted (the loop skips the element).",
      "Android 14: redaction ranges may now span several XMP boxes within one file.",
      "Android 15: IsoInterface keeps only a fixed set of box types in mFlattened, so getBoxBytes/getBoxRanges return nothing for other types. XMP boxes larger than 1 MiB make allocateBuffer return null and parseNextBox return false, which stops parsing of the remaining sibling boxes at that level.",
      "Android 16/17: BOX_XMP boxes are now kept and parsed, so their content goes through redaction."
     ],
     "preconditions": [
      "A caller reads an ISO base media file (e.g. MP4) through MediaProvider where location redaction applies, i.e. without ACCESS_MEDIA_LOCATION (per commit message).",
      "The file carries XMP in a be7acfcb-97a9-42e8-9c71-999491e3afac UUID box or a BOX_XMP box whose EXIF GPS fields are encoded as XML attributes. On Android 14 the XMP can instead be in a non-first XMP box; on Android 16/17, in a plain BOX_XMP box."
     ],
     "per_major": {
      "14": "Fixed in android-security-14.0.0_r29 by 15168c2e6f84 (the commit message lists two Cherrypick-From sources). It adds list-returning box accessors to IsoInterface, plus attribute scanning and multi-box process() in XmpInterface. The patched region is fully present (8/8 lines).",
      "15": "Adapted fix cb04f3531ff2 in android-security-15.0.0_r17. The attribute check is XmpDataParser.isSensitiveTagOrAttribute(), and IsoInterface is restructured (type-filtered flatten list, 1 MiB XMP cap, XMP_UUID helpers). No multi-box handling in this diff.",
      "16": "Adapted fix 4bdbc2fa9f53 in android-security-16.0.0_r8: XmpDataParser.isSensitiveTagOrAttribute(), plus BOX_XMP added to IsoInterface's retained-box switch. No multi-box handling in this diff.",
      "17": "Same adapted fix as 16 (11009f3800f9 in android-security-17.0.0_r1); the diff is identical, with the same blob hashes."
     },
     "backport_notes": [
      "The 14 patch targets the older XmpInterface (parsing in the constructor, fromContainer calling iso.getBoxBytes). On 15+ parsing lives in XmpDataParser and IsoInterface.mFlattened is type-filtered, so the attribute check belongs in isSensitiveTagOrAttribute()-style logic there. BOX_XMP must also be in the retained-box switch, or plain XMP boxes are never seen."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which MediaProvider entry point (redaction info computation) calls XmpInterface.fromContainer / XmpDataParser.createXmpInterface.",
      "Whether Android 15/16/17 handle multiple XMP boxes elsewhere, or whether the single-box behaviour there is intentional.",
      "The full span logic of the existing XmpDataParser redaction block that attribute matches now enter on 15/16/17.",
      "Contents of gps_test_xmp.mp4 beyond what the test asserts."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28642": {
     "cve": "CVE-2026-28642",
     "summary": "In ActivityStarter, when the Background Activity Launch (BAL) check returns a blocking verdict, the fix clears the mLaunchTaskBehind flag on the newly built ActivityRecord so a blocked background start does not perform the launch-behind animation that would make the activity visible.",
     "component": "platform/frameworks/base, WindowManager activity start (services/core/java/com/android/server/wm/ActivityStarter.java)",
     "weakness": "Incomplete enforcement of a security decision: the BAL block verdict did not suppress the launch-behind flag on the activity record, so a blocked background launch could still get launch-behind visibility (missing check).",
     "root_cause": "In ActivityStarter, around the ActivityRecord.Builder(...).build() call (built with setActivityOptions(checkedOptions)), the BAL result (balCode on Android 14, balVerdict on 15/16) was already available but was not consulted for r.mLaunchTaskBehind: even when the launch was BAL-blocked, the flag remained set, so the launch-behind animation/visibility could still be performed for an activity whose background launch should have been blocked. The commit message states there is no need to perform the launch-behind animation if BAL blocks, and the code comment says the reset avoids making the activity visible when the launch should be blocked.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java (class ActivityStarter, immediately after `new ActivityRecord.Builder(mService)...build()` and before `mLastStartActivityRecord = r`): adds `if (r.mLaunchTaskBehind && balCode == BAL_BLOCK) { Slog.w(TAG, \"Disallowed launching behind for a background launch\"); r.mLaunchTaskBehind = false; }` on Android 14.",
      "On Android 15 and 16 the same insertion uses `r.mLaunchTaskBehind && balVerdict.blocks()`; otherwise identical (log + reset of r.mLaunchTaskBehind to false).",
      "Effect: the launch-behind flag on the record is cleared at creation time for BAL-blocked starts; per the commit message and comment this prevents the launch-behind animation/visibility. The consumers of the flag are not shown in the material. The existing check a few lines later (`if (balCode != BAL_BLOCK && !isHomeProcess) mService.resumeAppSwitches()`) is unchanged."
     ],
     "completeness": [
      "Covers only the single point where the ActivityRecord is constructed in ActivityStarter; the flag is cleared exactly when the BAL verdict is blocking.",
      "Does not change the BAL decision itself or how a BAL-blocked start is otherwise handled; that downstream handling is not shown in the material.",
      "Any other place where mLaunchTaskBehind could be set on a record after this point (not shown in the material) would not be covered by this check.",
      "Starts that are not BAL-blocked keep the launch-behind flag unchanged."
     ],
     "behavior_changes": [
      "For activity starts whose BAL verdict is blocking and whose record has mLaunchTaskBehind set, the flag is now forced to false; the launch-behind animation/visibility is suppressed.",
      "A new warning log line is emitted: \"Disallowed launching behind for a background launch\" (ActivityStarter TAG).",
      "No new error codes or API changes are visible in the diff; non-blocked starts are unaffected."
     ],
     "preconditions": [
      "An activity start reaches ActivityStarter's record-creation path with r.mLaunchTaskBehind set on the built record.",
      "The BAL check for that start evaluates to a blocking verdict (balCode == BAL_BLOCK on 14; balVerdict.blocks() on 15/16).",
      "The exact caller/API surface is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: frameworks/base d94f8b27bc84 in android-security-14.0.0_r29, condition `r.mLaunchTaskBehind && balCode == BAL_BLOCK`; 3/3 patched lines present.",
      "15": "adapted fix: frameworks/base d98cb1490fc3 in android-security-15.0.0_r17, same insertion but condition uses `balVerdict.blocks()`; 3/3 patched lines present.",
      "16": "adapted fix: frameworks/base 2b8a07bf7437 in android-security-16.0.0_r8, same insertion with `balVerdict.blocks()`; 3/3 patched lines present.",
      "17": "not affected (not listed by the bulletin); the patched region (3/3 lines) is already present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-hunk change inserted right after the ActivityRecord.Builder(...).build() call and before `mLastStartActivityRecord = r`. On 14 the BAL result is `balCode` compared to `BAL_BLOCK`; on 15/16 it is `balVerdict` with `.blocks()`. Pick the form matching the BAL API in the target tree; ActivityStarter already uses Slog and TAG.",
      "The commit carries two Cherrypick-From references (34bbada3964e, 95f41dbd68ab) and Change-Id Iab9bd9fc6ebe7291d2eeec7f697cb877240d1c9e; no fork comparison was provided."
     ],
     "confidence": "high",
     "unknowns": [
      "Name of the enclosing ActivityStarter method; where balCode/balVerdict is computed and what happens to a BAL-blocked start after this point.",
      "How mLaunchTaskBehind is set on the record (e.g. from which ActivityOptions setting) and which caller/API path triggers the issue; the bulletin only gives EoP / High.",
      "Whether other code paths can set mLaunchTaskBehind on an ActivityRecord after this check.",
      "Contents of the internal commits referenced by the Cherrypick-From trailers; the test was only 'locally verified'."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28644": {
     "cve": "CVE-2026-28644",
     "summary": "ActivityTaskManagerService.startNextMatchingActivity() now forwards the original caller's identity (launchedFromUid/launchedFromPackage/launchedFromFeatureId) to the next resolved activity only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID; otherwise the calling UID and package are reset to the intermediary's own.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManagerService (activity start, startNextMatchingActivity)",
     "weakness": "Improper caller identity propagation (caller UID/package spoofing via trusted-identity forwarding, missing trust check)",
     "root_cause": "In services/core/java/com/android/server/wm/ActivityTaskManagerService.java, startNextMatchingActivity() always built the ActivityStarter with .setCallingUid(r.launchedFromUid), .setCallingPackage(r.launchedFromPackage) and .setCallingFeatureId(r.launchedFromFeatureId), where r is the ActivityRecord of the activity calling the API. So the activity that received an intent could hand it on to the next matching activity while the system reported the original launcher's UID and package as the caller. Per the commit message, this let an untrusted intermediary trampoline an implicit intent from a privileged app to another app. Permission checks based on the calling identity (the message cites REQUEST_INSTALL_PACKAGES) then saw the privileged original caller instead of the intermediary.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java:startNextMatchingActivity(): the builder chain is split so the ActivityStarter is kept in a local 'starter' before execute(). setCallingPid(-1), setRealCallingPid(origCallingPid), setRealCallingUid(origCallingUid) and setActivityOptions(options) are unchanged.",
      "A new check sets intermediaryUid = r.getUid(). If !UserHandle.isSameApp(intermediaryUid, r.launchedFromUid) && UserHandle.getAppId(intermediaryUid) != Process.SYSTEM_UID, the starter gets setCallingUid(intermediaryUid) and setCallingPackage(r.packageName). setCallingFeatureId is not called in this branch, so no feature ID is forwarded.",
      "Otherwise (same app ID as the original caller, or an intermediary whose app ID is SYSTEM_UID, e.g. ChooserActivity per the commit message) the old behavior is kept: setCallingUid(r.launchedFromUid), setCallingPackage(r.launchedFromPackage), setCallingFeatureId(r.launchedFromFeatureId). Then res = starter.execute().",
      "core/java/android/app/Activity.java: the Javadoc of startNextMatchingActivity(Intent, Bundle) now says the propagated caller identity reflects the intermediary unless it shares the original caller's UID, and that receivers should validate the immediate caller. This is a documentation change only.",
      "services/tests/wmtests/.../ActivityTaskManagerServiceTests.java: three new tests (differentAppId_propagatesIntermediary, sameAppId_propagatesOriginalCaller, systemUid_propagatesOriginalCaller). A helper mocks IPackageManager.queryIntentActivities and spies on ActivityStarter to verify setCallingUid/setCallingPackage. services/tests/wmtests/.../WindowTestsBase.java: ActivityBuilder gains setIntent(Intent), so a test can pass a full intent instead of a component-only one."
     ],
     "completeness": [
      "The fix is limited to the single identity-forwarding site in startNextMatchingActivity(). The real calling UID/PID (origCallingUid/origCallingPid from Binder) and the BAL-related handling referenced for b/230492947 are not touched.",
      "Trust is decided only by app ID: an intermediary with the same app ID as the original caller is trusted, and any intermediary whose app ID is SYSTEM_UID is trusted regardless of package.",
      "SYSTEM_UID is the only privileged intermediary on the allowlist. Any other system app that calls startNextMatchingActivity now has the caller identity reset to its own UID and package.",
      "In the reset branch, launchedFromFeatureId is dropped entirely (setCallingFeatureId is not called) rather than replaced with an intermediary feature ID.",
      "The check runs in the system server, so it applies to every caller of the public Activity.startNextMatchingActivity API without app changes."
     ],
     "behavior_changes": [
      "When an activity launched by a caller with a different app ID that is not SYSTEM_UID calls startNextMatchingActivity, the calling UID and package passed to ActivityStarter for the next activity are now the intermediary's.",
      "No calling feature ID is forwarded in that case.",
      "No new error codes: the API still returns true/false based on START_SUCCESS. Intermediaries with the same app ID or SYSTEM_UID keep the old behavior.",
      "The public Javadoc for Activity.startNextMatchingActivity(Intent, Bundle) now documents the identity behavior."
     ],
     "preconditions": [
      "An activity (ActivityRecord r) started by another app, so r.launchedFromUid/launchedFromPackage carry a different identity.",
      "That activity calls the public Activity.startNextMatchingActivity(Intent[, Bundle]) with an intent for which PackageManager resolves at least one more matching activity (the code needs r.app with a thread and a next ResolveInfo, per the code context and tests).",
      "The next activity, or the start path, makes permission or trust decisions based on the calling UID/package passed into ActivityStarter.",
      "Before the fix, the intermediary did not need to share the caller's app ID or be SYSTEM_UID for the original caller's identity to be forwarded."
     ],
     "per_major": {
      "14": "Same fix: commit bb7413247455 in android-security-14.0.0_r29 (cherry-pick, Change-Id I3df4b731bfae6ce6f8896c69ecdd118819057b36). On 14 the builder chain has no setUserId(userId). Patched region fully present (8/8).",
      "15": "Same fix: commit ba9c05f37043 in android-security-15.0.0_r17. The only context difference is that the chain also has .setUserId(userId) before the identity check. Patched region fully present (8/8).",
      "16": "Same fix: commit e95170c6378e in android-security-16.0.0_r8, same shape as 15 (with setUserId(userId)). Patched region fully present (8/8).",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1 the patched region is fully present (8/8). The material shows no separate 17 commit."
     },
     "backport_notes": [
      "Single commit. The only version-specific difference is whether the ActivityStarter chain includes .setUserId(userId) (present on 15/16, absent on 14). When backporting, keep setUserId in the chain before the check and move only the three setCalling* calls under the new condition. The test changes depend on WindowTestsBase.ActivityBuilder.setIntent(), which the same commit adds."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which target activities or permission checks were affected, beyond the commit message's REQUEST_INSTALL_PACKAGES example.",
      "The material does not show whether any system intermediaries that are not SYSTEM_UID relied on the old forwarding (possible functional regressions).",
      "The bulletin's affected-version list includes '17-next', but the per-major facts mark Android 17 as not listed. The material does not explain the difference."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28650": {
     "cve": "CVE-2026-28650",
     "summary": "WindowState.setHiddenWhileSuspended now checks the window's base type (getBaseType()) instead of its own mAttrs.type. As a result, sub-windows whose top parent is a system alert window or toast of a suspended app are hidden, and later shown again, like their parent.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/WindowState.java)",
     "weakness": "Incorrect type check on sub-windows: the code checked the window's own type instead of its base type. This causes a missed hide of overlay content while the owning app is suspended.",
     "root_cause": "setHiddenWhileSuspended used mAttrs.type in its early-return test `!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST`. For a sub-window (for example TYPE_APPLICATION_PANEL), mAttrs.type is a sub-window type. That type is neither a system alert window type nor TYPE_TOAST, so the method returned early and never called hide()/show(). As a result, when the owning package was suspended, a sub-window whose top parent is a system alert window (such as TYPE_APPLICATION_OVERLAY) was not hidden, while its parent overlay was. The sibling method setForceHideNonSystemOverlayWindowIfNeeded in the same file already used getBaseType() for the equivalent check.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowState.java:setHiddenWhileSuspended: adds `final int baseType = getBaseType();` and replaces both uses of mAttrs.type in the early-return condition with baseType, giving `!isSystemAlertWindowType(baseType) && baseType != TYPE_TOAST`. According to the commit message, this means a sub-window whose top parent is a system alert window now passes the check and is hidden/shown. The mOwnerCanAddInternalSystemWindow bypass and the mHiddenWhileSuspended state tracking are unchanged.",
      "services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java: adds testSubWindowHiddenWhenSuspended. The test creates a TYPE_APPLICATION_OVERLAY parent with a TYPE_APPLICATION_PANEL sub-window. It then verifies that setHiddenWhileSuspended(true) calls hide(true, true) on the sub-window and setHiddenWhileSuspended(false) calls show(true, true)."
     ],
     "completeness": [
      "The added test covers a direct sub-window of a TYPE_APPLICATION_OVERLAY parent.",
      "The changed condition applies to both system alert window types and TYPE_TOAST as base type. Only the overlay case is tested.",
      "The fix is limited to setHiddenWhileSuspended. In the shown code, the neighbouring setForceHideNonSystemOverlayWindowIfNeeded already uses getBaseType(), so the material shows no similar unfixed pattern.",
      "Windows whose owner has mOwnerCanAddInternalSystemWindow are still exempt, as before. The fix does not change that policy."
     ],
     "behavior_changes": [
      "Sub-windows (e.g. TYPE_APPLICATION_PANEL) owned by a package without internal-system-window rights, whose base type is a system alert window or toast, are now hidden when RootWindowContainer.updateHiddenWhileSuspendedState marks the package as suspended. They are shown again when the package is unsuspended.",
      "No new error codes or exceptions. Only the hide()/show() visibility outcome for these sub-windows changes."
     ],
     "preconditions": [
      "The app's owner cannot add internal system windows (mOwnerCanAddInternalSystemWindow is false). The app has a window whose base type is a system alert window type (e.g. TYPE_APPLICATION_OVERLAY) or TYPE_TOAST, with one or more sub-windows attached.",
      "The owning package becomes suspended. Per index search, this reaches setHiddenWhileSuspended through RootWindowContainer.updateHiddenWhileSuspendedState, which iterates over all windows of the suspended packages."
     ],
     "per_major": {
      "14": "same fix: commit ce479a9a57bd in android-security-14.0.0_r29. The test uses the createWindow(parent, type, name) helper.",
      "15": "same fix: commit 8130b6057817 in android-security-15.0.0_r17. The WindowState.java change and the test (createWindow helper) are identical to 14; only line offsets differ.",
      "16": "same fix: commit b61562e1abe6 in android-security-16.0.0_r8. The WindowState.java change is identical; only the test is adapted to the newWindowBuilder(...).setParent(parent).build() helper.",
      "17": "same fix: commit 236258a3e304 in android-security-17.0.0_r1. The WindowState.java change is identical; the test uses newWindowBuilder and is placed before testOverlayWindowHiddenWhenLockedByAppLock."
     },
     "backport_notes": [
      "Single commit, two-line functional change, no dependencies. getBaseType() and isSystemAlertWindowType are already used in WindowState on the shown branches. Only the test helper differs: createWindow(parent, type, name) on 14/15 versus newWindowBuilder(name, type).setParent(parent).build() on 16/17."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not describe the concrete security impact beyond the commit message's 'security concerns' and the bulletin's High/EoP rating.",
      "The material does not show how getBaseType() is implemented, so behaviour for sub-windows nested more than one level deep is not shown.",
      "The per-major facts report 6/7 patched-region lines present on 14/15/16 versus 7/7 on 17, even though the fix commits are in those tags. The material does not show which line differs.",
      "Fixes for the 16-qpr2 and 17-next branches listed in the bulletin are not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28655": {
     "cve": "CVE-2026-28655",
     "summary": "Reworks RemoteViews parceling so that a single ApplicationInfoCache (typed list) is written once by the root RemoteViews and nested RemoteViews reference it by int id, and removes the Parcel.allowSquashing()/restoreAllowSquashing() wrapping of RemoteViews.writeToParcel together with the ALTERNATIVE_DEFAULT ReadWriteHelper trick that forced immediate (non-lazy) Bundle unparceling in ReflectionAction.",
     "component": "platform/frameworks/base, core/java/android/widget/RemoteViews.java (RemoteViews parceling, ApplicationInfoCache, ReflectionAction)",
     "weakness": "Parcel serialization/deserialization inconsistency in RemoteViews: reliance on Parcel.allowSquashing() (ApplicationInfo squashing) plus a ReadWriteHelper hack to force non-lazy Bundle reads; the precise flaw class (memory vs. logic) is not evident from the diff",
     "root_cause": "Before the fix, RemoteViews.writeToParcel enabled Parcel.allowSquashing() for the whole write so that repeated ApplicationInfo objects in nested RemoteViews would be squashed, and each RemoteViews (MODE_NORMAL) wrote its full mApplication via ApplicationInfo.writeToParcel. Because squashing changes how Bundle contents are written, ReflectionAction's BUNDLE case installed a private Parcel.ReadWriteHelper (ALTERNATIVE_DEFAULT) around in.readBundle() purely to defeat lazy Bundle unparceling. The second commit's message states squashing 'doesn't work' for ApplicationInfo and that mApplicationInfoCache had been added as well, making squashing redundant. The combination of squashing on the writer side, per-RemoteViews ApplicationInfo serialization, and the ReadWriteHelper side effect on Bundle reads is the mechanism the fix removes; the material does not spell out the exact faulty read/write sequence.",
     "fix_analysis": [
      "Commit aa6e28b4fd2d (Revert^2, applied first): RemoteViews.java ApplicationInfoCache is changed from a Map<Pair<String,Integer>,ApplicationInfo> to a List<ApplicationInfo> (mCachedApplicationInfos) with a Parcel constructor (parcel.createTypedArrayList(ApplicationInfo.CREATOR)) and writeToParcel (dest.writeTypedList). getOrPut/updateAndGetId now return an int index; findId matches on packageName (Objects.equals) and uid; get(int) returns null for id < 0 or id >= size. getPackageUserKey is deleted.",
      "Commit aa6e28b4fd2d: a new field mApplicationId is added and set alongside mApplication in RemoteViews(ApplicationInfo,int), the landscape/portrait constructor, the sized constructor, the copy constructor, the Parcel constructor for sized and landscape/portrait modes (updateAndGetId), and via getOrPut in configureDescendantsAsChildren and in the root-only method that recreates BitmapCache and ApplicationInfoCache. updateAppInfo now looks up via findId and stores via updateAndGetId (without assigning mApplicationId).",
      "Commit aa6e28b4fd2d: wire format change in RemoteViews.writeToParcel: in all three modes (MODE_NORMAL, MODE_HAS_SIZED_REMOTEVIEWS, landscape/portrait) the root writes mApplicationInfoCache.writeToParcel right after mBitmapCache.writeBitmapsToParcel; in MODE_NORMAL dest.writeInt(mApplicationId) replaces mApplication.writeToParcel. In the Parcel constructor the root reads mApplicationInfoCache = new ApplicationInfoCache(parcel) (children receive it via configureAsChild(rootData)), and MODE_NORMAL reads mApplicationId = parcel.readInt() and mApplication = mApplicationInfoCache.get(mApplicationId) instead of ApplicationInfo.CREATOR.createFromParcel.",
      "Commit ae484e3d7a20 (applied second; its pre-image blob f0e6c3e09518 is the post-image of aa6e28b4fd2d): RemoteViews.writeToParcel no longer calls dest.allowSquashing() / dest.restoreAllowSquashing(prevSquashingAllowed).",
      "Commit ae484e3d7a20: ReflectionAction(Parcel) BUNDLE case is reduced to this.value = in.readBundle(); the static ALTERNATIVE_DEFAULT Parcel.ReadWriteHelper and the hasReadWriteHelper/setReadWriteHelper handling are removed."
     ],
     "completeness": [
      "All three RemoteViews parcel modes are updated symmetrically on write and read; the cache is written only when mIsRoot and children reuse it via HierarchyRootData.",
      "ApplicationInfoCache.get(int) bounds-checks the id read from the parcel and returns null rather than throwing; callers that then use mApplication must tolerate null (the old getOrPut could also return null when packageName was null).",
      "ApplicationInfoCache(Parcel) uses createTypedArrayList, which can yield null list entries; findId dereferences cachedApplicationInfo.packageName without a null check, so a null entry would cause an exception in findId.",
      "Squashing is only removed from RemoteViews.writeToParcel; the material does not show whether other Parcelables in frameworks/base still use Parcel.allowSquashing.",
      "Lazy Bundle unparceling is now permitted again for ReflectionAction BUNDLE values; per the removed comment, the forced non-lazy read existed because of allowSquashing, so its removal is tied to squashing no longer being enabled by RemoteViews."
     ],
     "behavior_changes": [
      "RemoteViews parcel wire format changes: the root writes a typed list of ApplicationInfo after the bitmap cache, and each MODE_NORMAL RemoteViews writes an int index instead of a full ApplicationInfo. Writer and reader must both use the new format.",
      "ApplicationInfo instances with the same packageName and uid are deduplicated into one cache slot; updateAndGetId replaces the existing slot, and on read all nested RemoteViews sharing an id get the identical ApplicationInfo instance.",
      "RemoteViews.writeToParcel no longer enables Parcel squashing itself.",
      "Bundles in ReflectionAction are read with plain readBundle() and may be unparceled lazily rather than immediately.",
      "mApplication becomes null if the parceled id is -1 or out of range for the cache."
     ],
     "preconditions": [
      "A RemoteViews object is serialized and deserialized across a process boundary (RemoteViews is a Parcelable; per index search it is received in RemoteViewsAdapter.updateRemoteViews from IRemoteViewsFactory.getViewAt and handled in LauncherAppWidgetHostView.updateAppWidget).",
      "The affected code paths are RemoteViews.writeToParcel (squashing enabled), the RemoteViews(Parcel, ...) constructor reading per-RemoteViews ApplicationInfo, and ReflectionAction(Parcel) with a BUNDLE-typed value, typically with nested RemoteViews (sized, landscape/portrait, or addView children).",
      "The exact trigger sequence is not described in the material."
     ],
     "per_major": {
      "14": "same fix, two commits in android-security-14.0.0_r29: aa6e28b4fd2d (Revert^2 ApplicationInfoCache parceling, cherry-pick of 2137c24fb2d1) followed by ae484e3d7a20 (remove allowSquashing and the Bundle ReadWriteHelper hack).",
      "15": "adapted fix: 04faeb30ca94 in android-security-15.0.0_r17 applies the Revert^2 ApplicationInfoCache change with 15-specific context (mCollectionCache written/read alongside the caches, writeTypedObject/readTypedObject for mApplication replaced, HierarchyRootData with four arguments). The material does not show the squashing-removal commit for 15.",
      "16": "adapted fix: 6fe346abe6fb in android-security-16.0.0_r8 applies the squashing/Bundle-helper removal with 16-specific context (field named mValue, private writeToParcel(Parcel,int,SparseArray<Intent>) with mHasDrawInstructions). The material does not show the ApplicationInfoCache commit for 16.",
      "17": "adapted fix: b78e05338801 in android-security-17.0.0_r1 applies the squashing/Bundle-helper removal with 17-specific context (mValue, writeToParcel also writes mOriginalDensity). The material does not show the ApplicationInfoCache commit for 17."
     },
     "backport_notes": [
      "Apply the Revert^2 ApplicationInfoCache commit first, then the allowSquashing removal: the second diff's pre-image blob (f0e6c3e09518) is the first diff's post-image, and the second commit's message assumes mApplicationInfoCache already exists. Check whether the target branch still contains the earlier revert 532a0a90b30a or the original change before cherry-picking.",
      "Adjust for branch-specific context in the RemoteViews(Parcel) constructor and writeToParcel (mCollectionCache / intentsToIgnore on 15+, mValue and mHasDrawInstructions on 16+, mOriginalDensity on 17); the wire-format change requires reader and writer to agree."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact faulty read/write sequence (how squashed ApplicationInfo plus lazy/non-lazy Bundle reads led to the EoP) is not described in the material.",
      "Whether the squashing-removal commit landed on Android 15, and whether the ApplicationInfoCache parceling commit was already present or separately landed on Android 16 and 17, is not shown; per-major facts report only partial presence (4/8 or 6/8 lines) of the patched region.",
      "Whether any other frameworks/base Parcelable still enables Parcel.allowSquashing is not shown.",
      "The name of the root-only method that recreates the caches and calls getOrPut is not shown in the diff."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28656": {
     "cve": "CVE-2026-28656",
     "summary": "Replaces the AppOps user-restriction based overlay suppression (OP_SYSTEM_ALERT_WINDOW / OP_TOAST_WINDOW toggled on show/hide or resume/pause) with the window-level Window#setHideOverlayWindows(true) / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS flag. The change covers the autofill UI (FillUi, SaveUi, DialogFillUi) and the DeviceAdminAdd screens in Settings, Car Settings and TvSettings.",
     "component": "platform/frameworks/base (services/autofill UI: AutoFillUI, FillUi, SaveUi, DialogFillUi, OverlayControl), platform/packages/apps/Settings (DeviceAdminAdd), platform/packages/apps/Car/Settings (DeviceAdminAddActivity, DeviceAdminAddHeaderPreferenceController), platform/packages/apps/TvSettings (DeviceAdminAdd)",
     "weakness": "Improper restriction of rendered UI layers (overlay / tapjacking protection). Sensitive system UI suppressed overlays with an AppOps user restriction which, per the commit message, did not stop overlay windows from showing again once the restriction was released.",
     "root_cause": "Sensitive UIs protected themselves with AppOpsManager.setUserRestriction / setUserRestrictionForUser(OP_SYSTEM_ALERT_WINDOW and OP_TOAST_WINDOW, true, token). These UIs are the autofill fill, save and dialog UI in system_server, plus the device-admin activation screen in Settings, Car Settings and TvSettings. The restriction was set when the UI was shown (autofill OverlayControl.hideOverlays after show) or resumed (DeviceAdminAdd onResume), and cleared when the UI was hidden, destroyed or paused. Per the commit messages, this approach \"didn't stop overlay window showing again without SYSTEM_ALERT_WINDOW permission when the restrictions are released\". The protection was a system-wide AppOps toggle tied to lifecycle and show/hide calls, not a property of the protected window. Autofill applied it with UserHandle.USER_ALL.",
     "fix_analysis": [
      "frameworks/base c441211a26c9: services/autofill/java/com/android/server/autofill/ui/OverlayControl.java is deleted. It was the AppOps setUserRestrictionForUser wrapper for OP_SYSTEM_ALERT_WINDOW / OP_TOAST_WINDOW with USER_ALL.",
      "AutoFillUI.java: the mOverlayControl field and its construction in the AutoFillUI constructor are removed. The OverlayControl argument is dropped from the FillUi, SaveUi and DialogFillUi constructor calls. Per index search, DialogFillUi is created in showFillDialog.",
      "FillUi.java: the FillUi constructor and the AnchoredWindow constructor lose the OverlayControl parameter. In AnchoredWindow's show path, `params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS` is set before the if (!mShowing) branch, so it applies to both the addView branch and the else branch. The mOverlayControl.hideOverlays() call after addView is removed, and so is the finally { showOverlays() } in the block that handles destroyCallbackOnError.",
      "DialogFillUi.java: the constructor loses the OverlayControl parameter and calls `mDialog.getWindow().setHideOverlayWindows(true)` right after mDialog.setContentView(decor). show(), hide() and destroy() no longer toggle overlays through AppOps, and the try/finally wrappers are removed.",
      "SaveUi.java: the constructor loses the OverlayControl parameter. In the dialog window setup, `window.setHideOverlayWindows(true)` is added after window.setCloseOnTouchOutside(true). show(), hide() and destroy() no longer call hideOverlays/showOverlays.",
      "packages/apps/Settings e115f7703748 (Android 14) and f6b6ecb4b6e1 (Android 16): DeviceAdminAdd.onCreate replaces `mAppOps = getSystemService(AppOpsManager.class)` with `getWindow().setHideOverlayWindows(true)`. The setUserRestriction(true) calls in onResume and the setUserRestriction(false) calls in onPause are removed, along with the mAppOps field and the AppOpsManager import.",
      "packages/apps/Car/Settings ccb13e0864d4: DeviceAdminAddActivity gains an onCreate override that calls `getWindow().setHideOverlayWindows(true)` after super.onCreate. DeviceAdminAddHeaderPreferenceController drops mAppOps and mToken, and the setUserRestriction calls in onResumeInternal and onPauseInternal.",
      "packages/apps/TvSettings 7a21141ea7ef (Android 15 tag): DeviceAdminAdd.onCreate replaces the AppOpsManager lookup with `getWindow().setHideOverlayWindows(true)`. mToken, mAppOps and the onResume/onPause setUserRestriction calls are removed."
     ],
     "completeness": [
      "All three autofill UI surfaces in frameworks/base are converted. FillUi (AnchoredWindow) gets the flag through privateFlags on its LayoutParams. DialogFillUi and SaveUi get it through Window#setHideOverlayWindows on the Dialog window. OverlayControl is deleted, so no autofill caller is left on the AppOps path.",
      "The device-admin activation screens are converted in Settings (DeviceAdminAdd), Car Settings (DeviceAdminAddActivity) and TvSettings (DeviceAdminAdd). The TvSettings change appears only in the Android 15 tag material.",
      "The flag is now set when the window is created (constructor, dialog setup or onCreate) instead of being toggled on show/hide or resume/pause.",
      "The old code also restricted OP_TOAST_WINDOW, while the new code only sets the hide-non-system-overlay flag. The material does not show whether that flag also suppresses toast windows.",
      "In Settings DeviceAdminAdd, the android.os.Binder / IBinder imports are still present after the fix, and the diff does not remove the mToken field. This is leftover code, not a functional gap.",
      "In Car Settings, DeviceAdminAddHeaderPreferenceController.onPauseInternal keeps preference.setEnabled(false), which its comment describes as preventing tapjacking.",
      "The material does not show whether other system UIs still use the AppOps setUserRestriction(OP_SYSTEM_ALERT_WINDOW) pattern."
     ],
     "behavior_changes": [
      "While the autofill fill UI, save UI or fill dialog, or a DeviceAdminAdd screen is visible, its window carries the hide-non-system-overlay flag, so non-system overlay windows are hidden (per the semantics of Window#setHideOverlayWindows).",
      "These UIs no longer set an AppOps user restriction on OP_SYSTEM_ALERT_WINDOW / OP_TOAST_WINDOW when they appear. In the autofill case that restriction applied to USER_ALL.",
      "These components no longer restrict toast windows explicitly through OP_TOAST_WINDOW.",
      "With the try/finally blocks removed, hide() and destroy() in SaveUi and DialogFillUi no longer make AppOps calls on teardown."
     ],
     "preconditions": [
      "A sensitive UI covered by the fix must be on screen. That means the autofill FillUi, SaveUi or DialogFillUi (DialogFillUi is created from AutoFillUI.showFillDialog, per index search), or the device-admin activation screen (DeviceAdminAdd / DeviceAdminAddActivity) in Settings, Car Settings or TvSettings.",
      "Another app able to display an overlay window must be present. The replaced code targeted OP_SYSTEM_ALERT_WINDOW and OP_TOAST_WINDOW. The bulletin rates this issue High / EoP."
     ],
     "per_major": {
      "14": "same fix: resolved at android-security-14.0.0_r29 by three commits linked through bug 463364901: frameworks/base c441211a26c9 (autofill UI, OverlayControl deleted), packages/apps/Settings e115f7703748 (DeviceAdminAdd) and packages/apps/Car/Settings ccb13e0864d4 (DeviceAdminAddActivity and header controller). Only 6/8 lines of the patched region are reported present at that tag. The material shows no TvSettings commit for 14.",
      "15": "same fix: resolved at android-security-15.0.0_r17. The material shows packages/apps/TvSettings 7a21141ea7ef, where DeviceAdminAdd.onCreate calls getWindow().setHideOverlayWindows(true) and the AppOps calls are removed. 8/8 lines of the patched region are present.",
      "16": "same fix: resolved at android-security-16.0.0_r8. The material shows packages/apps/Settings f6b6ecb4b6e1, the same content as the Android 14 Settings change. 8/8 lines are present.",
      "17": "not affected: per the per-major facts, the bulletin does not list Android 17. 6/8 lines of the patched region are present at android-security-17.0.0_r1. The material shows no fix commit for 17."
     },
     "backport_notes": [
      "This fix spans several projects (frameworks/base, Settings, Car/Settings, TvSettings), all under bug 463364901. The material shows no ordering dependency between the commits. Window#setHideOverlayWindows requires the HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission (general platform knowledge), so check that each app you patch holds it."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact sequence by which an overlay could appear over the protected UI under the old AppOps scheme. The commit message only says the restriction did not stop overlays from showing again once it was released.",
      "Whether the new flag also covers toast windows previously blocked through OP_TOAST_WINDOW.",
      "Which 2 of the 8 patched-region lines are missing at android-security-14.0.0_r29 and android-security-17.0.0_r1, and whether a TvSettings fix exists for Android 14.",
      "The bulletin/OSV affected list includes '17-next', while the per-major facts treat Android 17 as not listed. That discrepancy is not resolved by the material.",
      "Whether Car Settings and TvSettings hold the permission needed for setHideOverlayWindows on a given fork."
     ],
     "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-2026-28658": {
     "cve": "CVE-2026-28658",
     "summary": "AccountsDb.findMetaAuthUid now strips the fixed \"auth_uid_for_type:\" prefix from the meta_key by length instead of splitting on \":\" and taking the second token, so authenticator types containing a colon are no longer truncated when the type-to-UID map is built.",
     "component": "platform/frameworks/base, services/core AccountManager (com.android.server.accounts.AccountsDb)",
     "weakness": "Incorrect string parsing of a stored key (improper input handling), causing a stored UID to be associated with a truncated authenticator type name",
     "root_cause": "In services/core/java/com/android/server/accounts/AccountsDb.java, findMetaAuthUid builds a Map<String, Integer> of authenticator type to UID from meta table rows whose key is \"auth_uid_for_type:<type>\". The old code derived the type with TextUtils.split(metaCursor.getString(0), \":\")[1], i.e. it tokenized the whole key on \":\" and took only the second token. Any authenticator type that itself contains \":\" was therefore truncated to the portion before its first colon, so the stored UID was recorded in the map under a different (shorter) type name than the one it was persisted for. The commit message states the entries are stored with the \"auth_uid_for_type:\" prefix and that the prefix must be removed properly.",
     "fix_analysis": [
      "AccountsDb.java (class constants): the META_KEY_DELIMITER = \":\" constant is removed and replaced with META_KEY_PREFIX_LEN = META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX.length().",
      "AccountsDb.java:findMetaAuthUid: the loop now reads typeWithPrefix = metaCursor.getString(0) and computes type = typeWithPrefix.substring(META_KEY_PREFIX_LEN), so everything after the fixed prefix (including any embedded colons) is kept as the type. The existing TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr) check that follows is unchanged."
     ],
     "completeness": [
      "The fix covers the single parsing site in findMetaAuthUid; it is the only place in the diff that interpreted the meta_key.",
      "The new substring call assumes every row returned by the query starts with the full prefix; the material shows the selection is META_KEY LIKE ? but not the bound argument, so whether a shorter key could reach substring and throw StringIndexOutOfBoundsException is not visible.",
      "The writer side (how the key is composed when a UID is stored for a type) is not in the diff, so the fix relies on the stored format being exactly prefix + type as the commit message states."
     ],
     "behavior_changes": [
      "Authenticator types containing ':' are now returned intact by findMetaAuthUid instead of being truncated at the first colon, so the type-to-UID map keys match the stored types.",
      "A key equal to just the prefix yields an empty type, which is then subject to the existing isEmpty check; no new error codes or API changes."
     ],
     "preconditions": [
      "A row in the accounts meta table whose authenticator type portion contains ':' (i.e. an authenticator type string with an embedded colon).",
      "A code path that calls AccountsDb.findMetaAuthUid to build the authenticator type to UID map; the callers are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 051431afaec5 in android-security-14.0.0_r29, identical diff (hunks at lines 177 and 1055).",
      "15": "same fix: platform/frameworks/base 38dbed092b9d in android-security-15.0.0_r17, identical diff and line offsets.",
      "16": "same fix: platform/frameworks/base 8c2ce8c567a2 in android-security-16.0.0_r8, identical diff and line offsets.",
      "17": "same fix: platform/frameworks/base 31e7981c9d23 in android-security-17.0.0_r1, identical content, hunks shifted to lines 173 and 1094."
     },
     "backport_notes": [
      "Single self-contained commit touching one file; it applies cleanly to any tree that still has META_KEY_DELIMITER and the TextUtils.split(...)[1] pattern in findMetaAuthUid. If a fork renamed the prefix constant, keep META_KEY_PREFIX_LEN derived from the actual stored prefix string."
     ],
     "confidence": "high",
     "unknowns": [
      "The callers of findMetaAuthUid and how the resulting type-to-UID map is consumed are not shown.",
      "The LIKE argument bound to SELECTION_META_BY_AUTHENTICATOR_TYPE and the code that writes the meta_key are not in the material.",
      "The concrete EoP consequence (which check used the truncated type) is not shown beyond the bulletin's High/EoP rating.",
      "How the old split behaved for a key consisting of only the prefix is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28664": {
     "cve": "CVE-2026-28664",
     "summary": "In platform/art, RuntimeImage::WriteImageToDisk now calls fchmod() to set the newly created runtime-generated app image (.art) file to 0444 right after creation, and fails image generation if that call fails.",
     "component": "platform/art, runtime (runtime-generated app image, runtime/runtime_image.cc)",
     "weakness": "Insecure file permissions (runtime-generated app image file left writable)",
     "root_cause": "RuntimeImage::WriteImageToDisk did not restrict the permissions of the app image file it created, so the file stayed writable both while it was being generated and afterwards. The commit message says there was no need to keep it writable and that making it read-only helps prevent tampering. The bulletin rates the issue High, EoP.",
     "fix_analysis": [
      "runtime/runtime_image.cc: adds #include <sys/stat.h> for fchmod and the S_IR* mode constants.",
      "runtime/runtime_image.cc:RuntimeImage::WriteImageToDisk: after an earlier failure check ('return false;') and before image->FillData(), calls fchmod(image_file->Fd(), S_IRUSR | S_IRGRP | S_IROTH) to set the file to 0444. The in-code comment says this happens immediately after creation to prevent tampering during the generation window, and that writes through the already-open descriptor still succeed.",
      "If fchmod fails, *error_msg is set to 'Failed to make runtime image read-only: <strerror>' and the function returns false. The commit message says the image is deleted in that case, but no explicit deletion appears in this hunk.",
      "The commit message says the image is made read-only once it is written and moved to its final path. The code instead applies fchmod at creation time, before the data is written."
     ],
     "completeness": [
      "Covers only the file created inside RuntimeImage::WriteImageToDisk. The mode is set on the open file's inode, so it should still apply after the file is moved to its final path.",
      "Does not change permissions of the containing directory. The diff does not address replacing or renaming the file by someone with write access to the directory.",
      "Does not change images generated before the patch. Nothing in the diff handles existing files.",
      "There is still a short window between file creation and the fchmod call.",
      "Whether the partially created file is actually deleted on the fchmod failure path is not shown in the hunk; only the commit message states it."
     ],
     "behavior_changes": [
      "Runtime-generated app image files end up with mode 0444, set by fchmod after creation.",
      "New failure mode: if fchmod fails, WriteImageToDisk returns false with the error 'Failed to make runtime image read-only: ...' and no image is produced for that run."
     ],
     "preconditions": [
      "Runtime app image generation must be active so that RuntimeImage::WriteImageToDisk runs and creates the .art file. Per index search, no other callers of WriteImageToDisk and no users outside art appeared.",
      "Before the fix, someone would need to be able to write to the generated image file at its on-disk location. The exact threat model is not given in the material."
     ],
     "per_major": {
      "14": "same fix: commit f8f793610644 in android-security-14.0.0_r29, same hunk (the include context differs: <sstream> is present). 6/6 patched lines present.",
      "15": "same fix: commit 19665de3fb4d in android-security-15.0.0_r17, same hunk at about line 1903. 6/6 patched lines present.",
      "16": "same fix: commit a57f914df7ee in android-security-16.0.0_r8, same hunk at about line 1904. 6/6 patched lines present.",
      "17": "same fix: commit 70be2043e6db in android-security-17.0.0_r1, same hunk at about line 1915. 6/6 patched lines present."
     },
     "backport_notes": [
      "One self-contained hunk plus one include, applied the same way on 14 through 17. Place the fchmod after the file is created and before image->FillData(), so the generation window is covered. ART is a mainline module (com.android.art), so devices can also receive ART fixes through module updates (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The file's mode before the fix, and how another party could modify the writable image (the commit only says 'tampering').",
      "Whether the existing failure handling in WriteImageToDisk deletes the file when fchmod fails, as the commit message claims.",
      "Why the commit message (read-only after the move) and the code (read-only at creation) describe different timing.",
      "Whether any later path-based open of the image for writing is affected by the new 0444 mode."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28668": {
     "cve": "CVE-2026-28668",
     "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that when a growing realloc is rejected by the Android allocation limit the original pointer is left untouched instead of being freed while the caller still holds it.",
     "component": "platform/bionic, libc malloc allocation limit (libc/bionic/malloc_limit.cpp)",
     "weakness": "Incorrect realloc failure semantics leading to a bad free (double free / use-after-free) of the caller's original allocation",
     "root_cause": "In libc/bionic/malloc_limit.cpp, LimitRealloc(void* old_mem, size_t bytes) is the realloc path of the Android allocation limit code (enabled in the tests via android_mallopt(M_SET_ALLOCATION_LIMIT_BYTES, ...)). When the requested size grows the allocation and CheckLimit(bytes - old_usable_size) fails, the function logged a warning, called LimitFree(old_mem) and returned nullptr. Per the commit message this is invalid because the original pointer should not be changed: callers normally rely on realloc leaving the original block valid when it returns nullptr, so the caller was left holding a freed pointer that it could later use or free again.",
     "fix_analysis": [
      "libc/bionic/malloc_limit.cpp:LimitRealloc: in the branch where bytes > old_usable_size and CheckLimit() fails, the LimitFree(old_mem) call is deleted and replaced by the comment 'Do not free the old pointer, it's still valid.'; the function still logs the warning and returns nullptr, so the caller's original allocation is no longer freed.",
      "tests/malloc_test.cpp:TEST(android_mallopt, set_allocation_limit_realloc_increase): the failing realloc result is stored in 'new_memory' and asserted nullptr, then the original 'memory' is freed to show it is still valid (the commit message states this test fails with the old behavior).",
      "Single commit, cherry-picked from d00918bfbbe06d4a0841d52aabe6dea3a2432787 (Change-Id I52ccd62f4f513aa55989fb7dda816d13e3171120)."
     ],
     "completeness": [
      "Covers the only place in the shown code that freed the caller's block on a rejected request: the limit-exceeded branch of LimitRealloc.",
      "The second failure path in LimitRealloc (dispatch_table->realloc / Malloc(realloc) returning nullptr, lines 213-216) is unchanged; it does not free old_mem but subtracts old_usable_size from gAllocated with the comment 'This acts as if the pointer was freed'. This commit does not change that accounting.",
      "Other limit hooks visible in context (LimitAlignedAlloc, the posix_memalign hook) return failure without touching caller memory and were not changed."
     ],
     "behavior_changes": [
      "With the allocation limit enabled, a realloc that would cross the limit now returns nullptr and leaves the original block allocated (caller must free it), matching standard realloc behavior (general platform knowledge).",
      "The warning_log message on the rejected path is unchanged."
     ],
     "preconditions": [
      "The Android allocation limit must be enabled (in the tests via android_mallopt(M_SET_ALLOCATION_LIMIT_BYTES, ...)) so that LimitRealloc handles realloc.",
      "A realloc call whose requested size exceeds the old block's usable size and whose increase fails CheckLimit.",
      "The caller then uses or frees its original pointer after realloc returns nullptr."
     ],
     "per_major": {
      "14": "same fix: platform/bionic cd1be6e8bfb8 in android-security-14.0.0_r29, hunk in LimitRealloc (malloc_limit.cpp line ~199) plus the test change.",
      "15": "same fix: platform/bionic 10a1e2ce6e59 in android-security-15.0.0_r17, identical hunk (line ~200) plus the test change.",
      "16": "same fix: platform/bionic 50cbdffd6f7e in android-security-16.0.0_r8, identical hunk (line ~200) plus the test change.",
      "17": "same fix: platform/bionic 48a7e3362ff9 in android-security-17.0.0_r1, identical hunk (line ~201); the test hunk is at a different offset (line ~818)."
     },
     "backport_notes": [
      "Small change in LimitRealloc (two lines removed, one comment added); the hunk context is identical on 14 through 17 with only line offsets differing. The accompanying test change verifies the original pointer stays valid."
     ],
     "confidence": "high",
     "unknowns": [
      "Which production processes or callers enable the allocation limit and thereby reach LimitRealloc is not shown in the material.",
      "How the installation of the Limit* hooks happens is not shown.",
      "Whether LimitFree adjusted gAllocated (and thus how accounting changes with the fix) is not shown, since LimitFree's body is not in the material.",
      "The exact EoP scenario behind the High rating is not described in the provided 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-2026-28671": {
     "cve": "CVE-2026-28671",
     "summary": "MediaProvider.updateInternal's file-movement path now checks, before Os.rename, whether a database row already claims the destination _data path. If no file backs that row it deletes the row; if a file exists it enforces write permission on the row. The SQLiteConstraintException handler in updateAllowingReplace now deletes a conflicting row at the active rename destination and retries the update in the same transaction.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.java update path (pending-file publish / file movement)",
     "weakness": "Race condition / missing ownership check on a shared namespace: the destination path of a MediaStore rename was not checked against existing database rows, so a row inserted beforehand (a \"squatter\" row) could hold the destination path.",
     "root_cause": "In MediaProvider.java updateInternal, an update can touch placement columns (e.g. clearing IS_PENDING, changing DISPLAY_NAME or RELATIVE_PATH) with movement allowed. In that case the code computes afterPath and calls Os.rename(beforePath, afterPath) on disk. Only after that does it run the database update through updateAllowingReplace. Nothing checked whether a different Files row with _data == afterPath already existed. Per the commit message, a malicious app could insert such a row ahead of time. In the tests the squatter row has the same DISPLAY_NAME and RELATIVE_PATH, IS_PENDING=0, and no backing file. The qb.update then hit the UNIQUE constraint on _data. For callers with targetSdk >= R the exception was simply rethrown. For legacy callers the replace logic ran only if a file existed at the path. The bulletin rates this High / ID.",
     "fix_analysis": [
      "MediaProvider.java:updateInternal (movement branch): once afterPath is final and before Os.rename, it calls the new checkIfPathAlreadyExists(helper, beforeVolume, afterPath). It also records renameAfterPath = afterPath and passes it to updateAllowingReplace.",
      "MediaProvider.java:checkIfPathAlreadyExists: with the local calling identity cleared, it uses queryForSingleItem to look up _ID in Files.getContentUri(volume) where DATA == afterPath. If there is no row (FileNotFoundException or null cursor), it returns. If a row exists but new File(afterPath).exists() is false, it deletes that row with deleteEntryForPath and returns. If both the row and the file exist, it restores the calling identity and calls enforceCallingPermission(Files.getContentUri(volume, id), Bundle.EMPTY, /*forWrite*/ true).",
      "MediaProvider.java:updateAllowingReplace: takes a new @Nullable String renameAfterPath parameter. In the SQLiteConstraintException handler, before the targetSdk >= R check, it reads path = values.getAsString(FileColumns.DATA) and calls handleNonExistingEntryFile. If that returns anything other than -1, the value is returned directly. The old 'path == null || !new File(path).exists() -> throw e' guard is removed: a null path is now handled in handleNonExistingEntryFile, and the file-exists check for the legacy replace path is gone.",
      "MediaProvider.java:handleNonExistingEntryFile: throws e if path is null. If Objects.equals(path, renameAfterPath), it calls deleteEntryForPath(helper, path). If a row was deleted, it retries qb.update(helper, values, userWhere, userWhereArgs) inside the enclosing helper.runWithTransaction. Otherwise it returns -1. It does not check whether a file exists at path, and it does not check who owns the row.",
      "MediaProvider.java:deleteEntryForPath: clears the local calling identity and runs db.delete(Files.TABLE, \"_data=?\", {path}) inside helper.runWithTransaction. On any exception it logs and returns 0.",
      "MediaProviderTest.java: adds testPendingUpdate_SquatterRowDeleted, where a squatter row with no file is removed when the pending row is published. It also adds testPendingUpdate_SquatterRowFileExists, where a squatter row backed by a real file is kept and the published row gets a unique name."
     ],
     "completeness": [
      "The fix covers only the movement branch of updateInternal. handleNonExistingEntryFile acts only when renameAfterPath is non-null and equals the conflicting DATA value. Updates that do not go through movement include those that supply DATA directly, thumbnails, and calls where allowMovement is false. For a UNIQUE conflict in those updates, callers with targetSdk >= R still get the exception. Legacy callers go to the replace branch, which no longer has its file-exists guard.",
      "checkIfPathAlreadyExists runs before Os.rename. A row inserted between that check and the database update is handled only by the constraint-exception path. That path deletes the conflicting row with no owner check and no file-existence check.",
      "The stale-row deletion in checkIfPathAlreadyExists depends on new File(afterPath).exists(). If a file exists at the path, the row is not deleted. Instead, the caller doing the move must hold write permission on that row (enforceCallingPermission forWrite).",
      "deleteEntryForPath catches all exceptions and returns 0. In that case handleNonExistingEntryFile returns -1 and the rest of the handler runs: a rethrow for targetSdk >= R, or the legacy replace branch.",
      "The legacy (targetSdk < R) replace branch lost its 'file must exist on disk' guard. It now goes on to query and replace the conflicting row whenever path is non-null.",
      "The insert side is not changed: rows with no backing file can still be inserted. The fix only reclaims the name when a row is published or moved."
     ],
     "behavior_changes": [
      "Moving or publishing a row to a path that has a Files row but no file: that other row, whoever owns it, is now deleted with a direct db.delete on Files.TABLE, and a verbose 'Deleted stale entry' line is logged.",
      "Moving or publishing to a path where both a row and a file exist, when the caller lacks write access to that row: enforceCallingPermission(..., forWrite=true) now fails the update before the rename.",
      "A UNIQUE(_data) conflict on the rename destination is now resolved by deleting the conflicting row and retrying the update, for all targetSdk levels. Before the fix, callers with targetSdk >= R got the SQLiteConstraintException.",
      "updateAllowingReplace signature changed (extra @Nullable String renameAfterPath parameter)."
     ],
     "preconditions": [
      "A caller performs ContentResolver.update on a single-item MediaStore URI (AUDIO_MEDIA_ID, AUDIO_PLAYLISTS_ID, VIDEO_MEDIA_ID, IMAGES_MEDIA_ID, DOWNLOADS_ID or FILES_ID). The update changes placement columns (e.g. IS_PENDING, DISPLAY_NAME, RELATIVE_PATH) without supplying DATA. Movement must be allowed, which is the default for non-self callers unless QUERY_ARG_ALLOW_MOVEMENT overrides it. The caller must pass enforceCallingPermission(forWrite) on its own row.",
      "Another row already exists in the Files table whose _data equals the computed destination path. In the tests this is a row with the same DISPLAY_NAME and RELATIVE_PATH, inserted with IS_PENDING=0 before the pending row is published.",
      "MediaProvider.update has external callers, e.g. DownloadProvider.updateMediaProvider calls mediaProvider.update(mediaStoreUri, mediaValues, null, null) (per index search). Whether such calls reach the movement branch depends on the values passed. DownloadProvider's values appear to include DATA, which would skip movement."
     ],
     "per_major": {
      "14": "same fix: RESTRICT AUTOMERGE cherry-pick e27ea39d7184 in android-security-14.0.0_r29 (Change-Id Ica7cec90bb2d1870ce8902fa3d3e9899d59a743b); 8/8 patched lines present.",
      "15": "same fix: 9e42f05c7c32 in android-security-15.0.0_r17, identical hunks at shifted line numbers; 8/8 patched lines present.",
      "16": "same fix: 94374dc41151 in android-security-16.0.0_r8, identical hunks; the test methods are inserted before testGetType; 8/8 patched lines present.",
      "17": "same logic with context adaptation: 87beb3978b6a in android-security-17.0.0_r1. The surrounding code uses Logging.logIfLoggable and markPathAsDeletedAndInvalidateFuseDentry instead of Log.d and invalidateFuseDentry. The checkIfPathAlreadyExists call is placed before that logging line. 8/8 patched lines present."
     },
     "backport_notes": [
      "This is a single self-contained commit and adds no imports. The target tree must therefore already have queryForSingleItem, enforceCallingPermission(Uri, Bundle, boolean), LocalCallingIdentity clear/restore and DatabaseHelper.runWithTransaction, plus java.util.Objects and java.io.FileNotFoundException imported in MediaProvider.java. Any other caller of updateAllowingReplace must be updated for the new renameAfterPath parameter. Index search found none, but verify this.",
      "MediaProvider ships as a Mainline module (com.android.mediaprovider APEX), so devices may receive this via module update rather than the platform security tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The bulletin classifies this as ID (information disclosure). The material does not say what information is exposed beyond the namespace squatting described in the commit message.",
      "What happened before the fix to the file already renamed on disk, and to the rows, after the UNIQUE conflict was rethrown (for example, whether the rename was reverted).",
      "Where the unique-name fallback used in testPendingUpdate_SquatterRowFileExists is implemented (it is not in the shown hunks).",
      "Whether queryForSingleItem with the identity cleared sees pending or trashed rows at the destination path.",
      "Whether the direct db.delete in deleteEntryForPath triggers the same change notifications and cleanup as the normal delete path."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45518": {
     "cve": "CVE-2026-45518",
     "summary": "Adds an OPTION_CHECK_BRACKETS mode to android.database.sqlite.SQLiteTokenizer that rejects SQL fragments with unbalanced parentheses, and makes ContactsProvider's SelectionBuilder.addClause() validate every clause with it so a caller-supplied selection cannot close the parentheses that SelectionBuilder wraps around each clause.",
     "component": "platform/frameworks/base (core/java/android/database/sqlite/SQLiteTokenizer) and platform/packages/providers/ContactsProvider (com.android.providers.contacts.util.SelectionBuilder)",
     "weakness": "SQL injection via improper neutralization of caller-supplied selection string (missing input validation)",
     "root_cause": "SelectionBuilder concatenates clauses as \"(clause1) AND (clause2) ...\" (see SelectionBuilderTest expectations such as \"(A) AND (B)\"). The base selection passed to the constructor is, per its javadoc, \"typically the user supplied selection arg\". addClause() only checked TextUtils.isEmpty() and stored the clause verbatim, so a selection containing unbalanced parentheses (for example the test input \"1=1)) OR ((1=1\") could break out of the wrapping parentheses and change how it combines with the other clauses. SQLiteTokenizer had no facility to detect this, and it also contained a bug where `options &= OPTION_TOKEN_ONLY` (compound assignment) cleared all other option bits when that check was reached.",
     "fix_analysis": [
      "1. platform/frameworks/base 9183bfe6574b, core/java/android/database/sqlite/SQLiteTokenizer.java: adds `public static final int OPTION_CHECK_BRACKETS = 1 << 1`.",
      "2. SQLiteTokenizer.tokenize(String, int, Consumer<String>): the `checker` parameter becomes @Nullable and the three `checker.accept(token)` call sites (the identifier path, the quote-handling path using tokenUnquoted, and the quoted-token path between quoteStart/quoteEnd) are guarded with `if (checker != null)`, so the method can be used purely as a validator.",
      "3. SQLiteTokenizer.tokenize: both `if ((options &= OPTION_TOKEN_ONLY) != 0)` tests are corrected to `(options & OPTION_TOKEN_ONLY)`. Without this, reaching those checks would strip OPTION_CHECK_BRACKETS from `options` and the new check would not fire.",
      "4. SQLiteTokenizer.tokenize: a new `int level` counter is maintained in the generic-character path (after the semicolon check; parentheses inside comments are not counted, as testCheckBracketsInComments verifies). '(' increments level; ')' decrements it and, if level drops below 0 and OPTION_CHECK_BRACKETS is set, throws genException(\"Unbalanced brackets\", sql). After the loop, if level != 0 and the option is set, the same exception is thrown. This rejects both premature closes and unclosed opens.",
      "5. platform/packages/providers/ContactsProvider 3f606a02d7e6, src/com/android/providers/contacts/util/SelectionBuilder.java: SelectionBuilder.addClause(String) now calls `SQLiteTokenizer.tokenize(clause, SQLiteTokenizer.OPTION_CHECK_BRACKETS, null)` before adding a non-empty clause to mWhereClauses. Because the constructor SelectionBuilder(String baseSelection) delegates to addClause(), the base selection is validated too. An IllegalArgumentException is thrown for unbalanced input.",
      "6. Tests: SQLiteTokenizerTest.testCheckBrackets / testCheckBracketsInComments and SelectionBuilderTest.testInjection cover balanced input, missing close, extra close, the \"1=1)) OR ((1=1\" pattern, and parentheses inside comments."
     ],
     "completeness": [
      "Covers every clause that goes through SelectionBuilder.addClause(), including the constructor's base selection; the actual ContactsProvider call sites are not shown in the material.",
      "The tokenizer check is opt-in (OPTION_CHECK_BRACKETS); existing SQLiteTokenizer callers that do not pass the flag get no new validation. Only ContactsProvider's SelectionBuilder is changed to pass it in the material shown.",
      "The check only enforces parenthesis balance. Any balanced SQL fragment is still accepted; the fix relies on the existing wrapping parentheses plus balance to keep the caller's clause self-contained. It does not validate tokens or grammar (OPTION_TOKEN_ONLY is not combined with it).",
      "Parentheses inside comments are not counted (tested), avoiding false rejections.",
      "The `&=` to `&` correction also fixes a latent option-handling bug for any future multi-flag use of SQLiteTokenizer."
     ],
     "behavior_changes": [
      "SelectionBuilder(String) and SelectionBuilder.addClause(String) now throw IllegalArgumentException (\"Unbalanced brackets in '...'\") for clauses with unmatched parentheses; callers passing such selections into SelectionBuilder will now get this exception.",
      "SQLiteTokenizer.tokenize(String, int, Consumer<String>) accepts a null checker.",
      "New public constant SQLiteTokenizer.OPTION_CHECK_BRACKETS; passing it causes IllegalArgumentException on unbalanced parentheses outside comments and quotes.",
      "Option bits are no longer destroyed mid-tokenization (the `&=` bug); no observable change for callers that only use OPTION_NONE or OPTION_TOKEN_ONLY."
     ],
     "preconditions": [
      "A caller must be able to supply a selection string that ContactsProvider feeds into SelectionBuilder (the constructor javadoc states the base selection is typically the user supplied selection arg). The specific ContactsProvider entry points that construct SelectionBuilder are not shown in the material.",
      "The selection must contain unbalanced parentheses that close the wrapper added by SelectionBuilder; after the fix these are rejected."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 9183bfe6574b (SQLiteTokenizer OPTION_CHECK_BRACKETS) and platform/packages/providers/ContactsProvider 3f606a02d7e6 (SelectionBuilder.addClause check) in android-security-14.0.0_r29, both MendIt cherry-picks; resolver reports 7/8 lines of the patched region present.",
      "15": "same fix: platform/packages/providers/ContactsProvider b8e00892122c in android-security-15.0.0_r17, identical SelectionBuilder diff (test file has different blob ids); 8/8 lines present. The matching frameworks/base SQLiteTokenizer commit is not shown in the material but is required for the ContactsProvider change to compile.",
      "16": "same fix: platform/packages/providers/ContactsProvider 3c11261b2a6b in android-security-16.0.0_r8, identical SelectionBuilder diff; 8/8 lines present. frameworks/base counterpart not shown in the material.",
      "17": "same fix: platform/frameworks/base 5a73367d5b3a in android-security-17.0.0_r1, identical SQLiteTokenizer diff (test file has different blob ids); 7/8 lines present. The ContactsProvider counterpart is not shown in the material."
     },
     "backport_notes": [
      "Two-repo dependency: the frameworks/base SQLiteTokenizer change (new OPTION_CHECK_BRACKETS constant, @Nullable checker, and the `&=` to `&` correction) must land before or together with the ContactsProvider SelectionBuilder change, which references both the constant and the null-checker form; do not drop the `&=` fix, since without it the bracket option is cleared and the check is ineffective. SQLiteTokenizer lives in the core framework jar rather than a mainline module, so both pieces ship as platform updates (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which ContactsProvider query/update/delete paths pass caller-controlled selections into SelectionBuilder, and which appended clauses they protect, are not shown.",
      "The frameworks/base commits for Android 15 and 16 and the ContactsProvider commit for Android 17 are not included in the material, though the per-major facts mark all four majors as resolved.",
      "Why the resolver reports only 7/8 lines of the patched region on Android 14 and 17 is not shown.",
      "Whether any other SQLiteTokenizer users elsewhere in the platform were also switched to OPTION_CHECK_BRACKETS is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45520": {
     "cve": "CVE-2026-45520",
     "summary": "BiometricsSettingsBase now honors the EXTRA_USER_ID and gatekeeper password handle (EXTRA_KEY_GK_PW_HANDLE) intent extras only when the hosting SettingsActivity was launched by the Settings package itself. Otherwise it uses the current user and ignores the handle, so the device credential confirmation flow is enforced.",
     "component": "platform/packages/apps/Settings, biometrics/combination (CombinedBiometricSettingsActivity / BiometricsSettingsBase)",
     "weakness": "Missing caller validation on security-sensitive intent extras (untrusted intent input is trusted for the user ID and the credential-confirmation token)",
     "root_cause": "In src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java, onAttach() always read Intent.EXTRA_USER_ID from the host activity's intent (mUserId = getActivity().getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId())). onCreate() read the gatekeeper password handle from the same intent through BiometricUtils.containsGatekeeperPasswordHandle()/getGatekeeperPasswordHandle() whenever it was present. Per the commit message, external callers launching CombinedBiometricSettingsActivity could therefore inject a target user ID and a gatekeeper password handle. This let them bypass device credential confirmation and change biometric settings (such as Watch Unlock) for arbitrary users.",
     "fix_analysis": [
      "BiometricsSettingsBase.java:onAttach(Context): mUserId is now set to UserHandle.myUserId() and a new field, mAllowInternalExtras, is set to false. Only when the host activity is a SettingsActivity and SettingsActivity.getInitialCallingPackage() equals activity.getPackageName() is mAllowInternalExtras set to true and EXTRA_USER_ID read from the intent.",
      "BiometricsSettingsBase.java:onCreate(Bundle): the gatekeeper password handle is read only when mAllowInternalExtras && BiometricUtils.containsGatekeeperPasswordHandle(getIntent()). For external callers mGkPwHandle stays 0. The new test testExternalCaller_ignoresSensitiveExtras checks that launchChooseOrConfirmLock() is then called.",
      "Tests (CombinedBiometricProfileSettingsTest.java): the host activity changes from FragmentActivity to SettingsActivity. The ShadowPasswordUtils shadow is added, with setCallingAppPackageName(mActivity.getPackageName()) in setUp and reset() in tearDown. Two new tests cover an internal caller (extras honored) and an external caller (EXTRA_INITIAL_CALLING_PACKAGE set to a foreign package; extras ignored; confirm lock launched)."
     ],
     "completeness": [
      "Both sensitive extras read by BiometricsSettingsBase (EXTRA_USER_ID in onAttach and the gatekeeper password handle in onCreate) depend on the same mAllowInternalExtras decision, so subclasses of BiometricsSettingsBase inherit the check.",
      "If the host activity is not a SettingsActivity, the extras are never honored: the code falls back to the current user and no handle.",
      "The gate depends entirely on SettingsActivity.getInitialCallingPackage() matching the Settings package name. Its implementation is not in the material, so this diff does not show how it determines the caller, or whether EXTRA_INITIAL_CALLING_PACKAGE (used in the test) is trusted or sanitized at the activity level.",
      "This commit does not touch biometric fragments or activities outside BiometricsSettingsBase that read EXTRA_USER_ID or EXTRA_KEY_GK_PW_HANDLE. The material does not show whether any exist."
     ],
     "behavior_changes": [
      "When a SettingsActivity hosting a BiometricsSettingsBase fragment (such as CombinedBiometricSettingsActivity) is launched by a package other than Settings, EXTRA_USER_ID is ignored and the fragment uses UserHandle.myUserId().",
      "For such external launches, a supplied gatekeeper password handle is ignored (mGkPwHandle stays 0) and launchChooseOrConfirmLock() is called, as the new test checks.",
      "If the fragment is hosted by an activity that is not a SettingsActivity, the extras are now ignored even for internal launches.",
      "For launches by Settings itself, the user ID and gatekeeper handle are still honored."
     ],
     "preconditions": [
      "A local app that can start CombinedBiometricSettingsActivity with crafted intent extras (per commit message).",
      "The code path is BiometricsSettingsBase.onAttach()/onCreate(), called by the fragment lifecycle of the hosting activity. Per index search, no users outside Settings were found."
     ],
     "per_major": {
      "14": "Same fix: commit a88ca4587208 in android-security-14.0.0_r29. Same logic, plus an added android.app.Activity import. The test class is @Ignore on this branch. Patched region fully present (8/8 lines).",
      "15": "Same fix: commit 783b7806995f in android-security-15.0.0_r17. Same source logic, with no Activity import added. Test adapted to the branch context. Patched region fully present (8/8 lines).",
      "16": "Same fix: commit 23e5e4077bc9 in android-security-16.0.0_r8. Same source hunks, with surrounding context that includes the mBiometricsAuthenticationRequested field. Patched region fully present (8/8 lines).",
      "17": "Same fix: commit 759e75cec1c1 in android-security-17.0.0_r1. Same source hunks as 16. Patched region fully present (8/8 lines)."
     },
     "backport_notes": [
      "Single commit with no ordering dependency. The diff uses SettingsActivity.getInitialCallingPackage() and the ShadowPasswordUtils test shadow but does not add them, so the tree must already have both.",
      "Make sure android.app.Activity and android.text.TextUtils are imported in BiometricsSettingsBase.java. The 14 commit adds the Activity import; the 15 to 17 commits do not."
     ],
     "confidence": "high",
     "unknowns": [
      "How SettingsActivity.getInitialCallingPackage() is implemented and how it determines the caller. The test sets EXTRA_INITIAL_CALLING_PACKAGE, but the trust model at the activity level is not shown.",
      "The pre-fix logic in onCreate that decides whether to call launchChooseOrConfirmLock() based on mGkPwHandle, and the downstream code that uses mGkPwHandle/mUserId (the commit message mentions Watch Unlock).",
      "Whether CombinedBiometricSettingsActivity is exported in the manifest, and whether other Settings fragments read the same extras from externally launchable activities."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45521": {
     "cve": "CVE-2026-45521",
     "summary": "StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid() and AppFuseBridge.openFile rejects the request unless the calling UID equals the uid recorded in the AppFuse MountScope for the requested mountId, preventing cross-app access to another app's AppFuse proxy file descriptors.",
     "component": "platform/frameworks/base, services/core StorageManagerService / AppFuseBridge (AppFuse proxy file descriptors)",
     "weakness": "Missing authorization (caller/owner UID check) on a Binder-exposed handle lookup",
     "root_cause": "IStorageManager.openProxyFileDescriptor(mountId, fileId, mode) in StorageManagerService looked up the AppFuse mount only by the caller-supplied mountId via AppFuseBridge.openFile(mountId, fileId, mode), which only checked that mScopes.get(mountId) was non-null before calling scope.waitForMount() and scope.openFile(mountId, fileId, flags). Mounts are created in StorageManagerService with new AppFuseMountScope(uid, name), where name comes from mNextAppFuseName++, so each scope carries an owner uid, but that uid was never compared against the caller at open time. A caller reaching the Binder method could therefore obtain a ParcelFileDescriptor for a file in an AppFuse mount owned by another UID.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java:openProxyFileDescriptor: adds `final int uid = Binder.getCallingUid();` at method entry (before the mode mask) and passes it as the new first argument to mAppFuseBridge.openFile(uid, mountId, fileId, mode). The mAppFuseLock synchronization, null-bridge check and catch of FuseUnavailableMountException | InterruptedException (returning null) are unchanged.",
      "services/core/java/com/android/server/storage/AppFuseBridge.java:openFile: signature changes to openFile(int uid, int mountId, int fileId, int mode). Inside the synchronized(this) block the check `scope == null` becomes `scope == null || scope.uid != uid`, throwing FuseUnavailableMountException(mountId) when the caller is not the mount owner. The rest of the method (waitForMount, translateModePfdToPosix, scope.openFile) is unchanged, so the ownership check happens before any wait or open."
     ],
     "completeness": [
      "The check is applied at the single lookup point in AppFuseBridge.openFile, the only consumer of mScopes for opening files shown in the material; the other mScopes user shown, AppFuseBridge.onMount, is a native-driven callback and is not changed.",
      "The comparison is an exact UID match against MountScope.uid (the uid passed to new AppFuseMountScope(uid, name) when the mount is created); there is no allowance for system/root or other principals, and no additional permission check was added to openProxyFileDescriptor itself.",
      "The mismatch case is folded into the pre-existing FuseUnavailableMountException path, so to the caller it is indistinguishable from a stale or unknown mountId (null return)."
     ],
     "behavior_changes": [
      "openProxyFileDescriptor returns null (with a verbose log 'The mount point has already been invalid') when the caller's UID differs from the mount owner's UID, instead of returning a ParcelFileDescriptor.",
      "AppFuseBridge.openFile gains a leading int uid parameter; any out-of-tree caller of the old 4-argument signature will fail to compile."
     ],
     "preconditions": [
      "Caller can invoke IStorageManager.openProxyFileDescriptor over Binder; the material shows no permission check inside the method itself.",
      "An AppFuseBridge exists (mAppFuseBridge != null) and a MountScope is registered for the supplied mountId by another UID, i.e. another app has an active AppFuse proxy mount.",
      "mountIds are assigned from mNextAppFuseName++ per the context."
     ],
     "per_major": {
      "14": "same fix: 0d2ee8e16afc in android-security-14.0.0_r29, two-file change (StorageManagerService.java + AppFuseBridge.java), StorageManagerService hunk at line 3571; 7/8 lines of the patched region present at that tag.",
      "15": "same fix: bca099fcb293 in android-security-15.0.0_r17, identical diff content, StorageManagerService hunk offset 3729; 7/8 lines of patched region present.",
      "16": "same fix: 63f908ebd9ee in android-security-16.0.0_r8, identical diff content, hunk offset 3829; 7/8 lines of patched region present.",
      "17": "same fix: 03988cf95e56 in android-security-17.0.0_r1, identical diff content, hunk offset 3793; 7/8 lines of patched region present."
     },
     "backport_notes": [
      "Single self-contained commit; the AppFuseBridge.java hunk is identical across 14 through 17 (same blob ids 292314840efb -> d839b30ddf44) and the StorageManagerService hunk only shifts by line number, so it should apply where openProxyFileDescriptor still calls mAppFuseBridge.openFile(mountId, fileId, mode). Update any additional callers of AppFuseBridge.openFile for the new uid parameter."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether openProxyFileDescriptor is gated by any permission or SELinux policy at the Binder/AIDL layer; the method body shows none.",
      "The name of the StorageManagerService method that creates AppFuseMountScope(uid, name) and where its uid comes from are not shown in the excerpt.",
      "The commit lists two Cherrypick-From sources; the material does not show what, if anything, the second upstream change contained beyond this diff.",
      "No test was added (Test: EXEMPT tested manually); the material does not show a regression 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."
     }
    },
    "CVE-2026-45525": {
     "cve": "CVE-2026-45525",
     "summary": "Makes MediaProvider's ISO/MP4 parser keep parsing when an XMP box is too large to load. It also makes the XMP redaction logic redact the whole XMP byte range, instead of nothing, when the XMP payload could not be loaded, so location metadata is no longer left unredacted for oversized XMP boxes.",
     "component": "platform/packages/providers/MediaProvider, media metadata redaction (util/IsoInterface and util/XmpInterface on 14; util/XmpDataParser on 15+)",
     "weakness": "Fail-open handling of oversized input: the parser aborted and the redaction step produced no ranges, so sensitive (location) metadata was not redacted (incomplete redaction / improper handling of an exceptional condition).",
     "root_cause": "In IsoInterface's box parser, an XMP-carrying box (type BOX_XMP, or a BOX_UUID box with the XMP UUID) whose payload could not be loaded made the parser stop. On Android 14 the BOX_UUID / BOX_XMP branches returned null when len > Integer.MAX_VALUE or when the allocation threw OutOfMemoryError. On 15+ they returned false when allocateBuffer returned null (per the createXmpData code comment, for payloads above 1 MB). Because parsing stopped, no later box in the file was flattened, and the oversized box's own range was not available to the redaction logic either. In addition, the XMP redaction code treated a missing or empty XMP buffer as an empty document and returned zero redaction ranges. On 15+, createXmpData also reset xmpOffsets to an empty array when the buffer was null, and it fell back to BOX_XMP. Net effect: a file with an oversized XMP box went through redaction with no XMP ranges redacted, which could leave location data inside the XMP (and potentially in later boxes) readable. On 14 the pre-fix code also allocated and read the full payload of every uuid box regardless of its UUID, with no size cap below Integer.MAX_VALUE.",
     "fix_analysis": [
      "IsoInterface.java (14, BOX_UUID / BOX_XMP branches of the box parser): adds MAX_XMP_SIZE_BYTES = 1 MiB and a public XMP_UUID constant. For uuid boxes, data is now allocated and read only when box.uuid equals XMP_UUID. If the payload exceeds 1 MiB, the parser logs 'Skipping abnormally large uuid box' and leaves box.data null instead of returning null. BOX_XMP gets the same treatment ('Skipping abnormally large xmp box'). OutOfMemoryError is still caught but no longer aborts parsing. The box, with its range, is still returned, and later boxes are parsed.",
      "XmpInterface.java (14) constructor and process(): the rawXmp parameter becomes @Nullable. If rawXmp is null and xmpOffsets has at least two entries, process() adds xmpOffsets[0] and xmpOffsets[1] to mRedactedRanges (redacting the whole XMP box range) and returns without XML parsing. The constructor sets mRedactedXmp to an empty byte[] if it is still null. The guard that assigns mRedactedXmp now also accepts a zero-length array.",
      "XmpInterface.java (14) EXIF path: the range from exif.getAttributeRange(TAG_XMP), which is [start, length] per the code comment, is converted to [start, start+length], or to an empty array if it is null or too short.",
      "15/16/17 IsoInterface.java: in the XMP_UUID and BOX_XMP branches, `if (box.data == null) return false;` is replaced by `if (box.data != null) { Os.read(...) }`, so when allocateBuffer returns null, parsing no longer aborts.",
      "15/16/17 XmpDataParser.java getRedactionRanges(XmpData): if mRawXmp.length == 0 and mXmpOffsets.length > 0, it returns the full mXmpOffsets array as the redaction ranges without parsing. createXmpData(IsoInterface) now falls back to BOX_XMP only when buf is null AND xmpOffsets is empty, and it no longer clears xmpOffsets when buf is null.",
      "Tests: a new resource tests/res/raw/gps_test_xmp_big.mp4 and a new test testContainer_IsoRedactionRanges_OversizedXmp. The test asserts that the oversized box is still in the flattened list, that the redaction ranges array has exactly two entries (one [start, end] pair), and that the redacted XMP buffer is empty."
     ],
     "completeness": [
      "Covers both XMP carriers in ISO BMFF (BOX_XMP, and uuid boxes with XMP_UUID) and the failure modes visible in the diffs (size cap and OutOfMemoryError on 14; allocateBuffer returning null on 15+).",
      "On 14 the whole-range branch in XmpInterface.process() adds only xmpOffsets[0] and xmpOffsets[1]. If xmpOffsets contained more than one range pair, only the first would be redacted. The 15+ fix returns the full mXmpOffsets array instead.",
      "On 14 the branch only takes effect if the caller (fromContainer) actually passes a null rawXmp together with the box ranges. fromContainer is not in the diff, so this depends on existing 14 code that the material does not show (the new test exercises it).",
      "The 14 fix does not include the 15+ createXmpData change that stops the fallback from an oversized uuid-XMP box to BOX_XMP. Whether 14's fromContainer has such a fallback is not shown.",
      "Whole-range redaction applies only when rawXmp is null or empty and the offsets are non-empty. XMP that loads but fails XML parsing is handled as before (an existing test expects an IOException in that case)."
     ],
     "behavior_changes": [
      "Android 14 gains a 1 MiB cap on XMP box payloads in IsoInterface (previously only Integer.MAX_VALUE or OutOfMemoryError stopped it). XMP boxes above 1 MiB are no longer read, and the test shows their entire byte range is flagged for redaction.",
      "Android 14: the payload of non-XMP uuid boxes is no longer allocated or read, and an oversized non-XMP uuid box no longer aborts parsing.",
      "An oversized XMP box no longer stops parsing of later boxes, so the ranges of later boxes are now available.",
      "On 14, XmpInterface.getRedactedXmp() returns an empty array when the XMP payload was skipped.",
      "15+: createXmpData no longer falls back to BOX_XMP when a uuid-XMP box exists but was too large, and it keeps that box's offsets.",
      "14: EXIF XMP offsets passed to process() are now [start, end] instead of [start, length]."
     ],
     "preconditions": [
      "A media file whose XMP container (BOX_XMP, or a uuid box with the XMP UUID, in ISO BMFF/MP4) has a payload larger than the load limit: 1 MiB on 15+ and on 14 after this fix; on 14 before the fix, larger than Integer.MAX_VALUE or large enough to cause an OutOfMemoryError.",
      "The file is processed by MediaProvider's metadata redaction path (IsoInterface plus XmpInterface or XmpDataParser). MediaProvider redacts location metadata for apps that are not allowed to see it (general platform knowledge).",
      "Bulletin: Information Disclosure, High severity; affected versions 14, 15, 16, 16-qpr2, 17, 17-next."
     ],
     "per_major": {
      "14": "Adapted fix (commit 93cf0194cff2 in android-security-14.0.0_r29). On 14, IsoInterface allocates the box buffers directly with new byte[] instead of using allocateBuffer. The fix adds MAX_XMP_SIZE_BYTES and XMP_UUID to IsoInterface, restricts uuid reads to the XMP UUID, and adds the null-rawXmp whole-range branch and the EXIF [start, end] mapping in XmpInterface. 7 of 8 lines of the patched region are present at that tag.",
      "15": "Adapted fix (commit ec0834680bfe in android-security-15.0.0_r17). IsoInterface replaces `return false` with a null check around Os.read. XmpDataParser.getRedactionRanges returns the full offsets when mRawXmp is empty. createXmpData keeps the offsets and skips the BOX_XMP fallback for an oversized uuid-XMP box.",
      "16": "Same changes as 15 (commit cedfc5086b8d in android-security-16.0.0_r8), applied to a different IsoInterface base (hunk at line 189 instead of 188).",
      "17": "Same fix as 16 (commit 21270fd56118 in android-security-17.0.0_r1); the diff is identical to the 16 one."
     },
     "backport_notes": [
      "Pick the variant that matches the tree. Trees with XmpDataParser and IsoInterface.allocateBuffer should take the 15+ patch. Trees where XmpInterface does its own parsing (14 style) need the 14 patch, and must check that fromContainer passes a null buffer together with the box ranges when the box data was skipped; otherwise the whole-range branch never runs.",
      "MediaProvider is a Mainline module (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The Android 14 XmpInterface.fromContainer, getBoxBytes and getBoxRanges code is not in the material, so how a skipped box's null data and ranges reach process() on 14 is inferred from the test, not shown.",
      "Whether getBoxRanges can return multiple range pairs for several XMP boxes (which matters because the 14 branch only redacts xmpOffsets[0..1]) is not shown.",
      "The allocateBuffer implementation and its exact limit on 15+ are not shown.",
      "Whether xmpOffsets[1] was used elsewhere in the 14 process() before the [start, end] change is not shown.",
      "Which MediaProvider entry points invoke these parsers, and how the fix is distributed beyond the security tags, is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45528": {
     "cve": "CVE-2026-45528",
     "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp with the target package name and appInfo.uid instead of calling PendingIntent.getActivity on a package context created inside system_server with the calling identity cleared.",
     "component": "platform/frameworks/base, services/core StorageManagerService (getManageSpaceActivityIntent binder API)",
     "weakness": "PendingIntent created under the wrong identity: system_server registered a PendingIntent for another package while running as itself (privilege/identity confusion, confused deputy)",
     "root_cause": "In services/core/java/com/android/server/StorageManagerService.java, getManageSpaceActivityIntent cleared the Binder calling identity, obtained a Context for the target package via mContext.createPackageContext(packageName, 0) (Android 14/15) or mContext.createPackageContextAsUser(packageName, /* flags= */ 0, UserHandle.of(UserHandle.getUserId(originalUid))) (Android 16/17), and called PendingIntent.getActivity(targetAppContext, ...). Per the commit message, this let callers obtain a PendingIntent \"registered under the target package identity with system privileges\". Any caller passing the hasExternalStorageAccess (MANAGE_EXTERNAL_STORAGE) check could reach this path.",
     "fix_analysis": [
      "StorageManagerService.java:getManageSpaceActivityIntent: removes the Binder.clearCallingIdentity()/restoreCallingIdentity(token) block and the createPackageContext/createPackageContextAsUser call, so no system-created Context for the target package is used.",
      "StorageManagerService.java:getManageSpaceActivityIntent: keeps the same Intent (ACTION_DEFAULT, setClassName(packageName, appInfo.manageSpaceActivityName), FLAG_ACTIVITY_NEW_TASK), the same ActivityOptions with MODE_BACKGROUND_ACTIVITY_START_DENIED, and the same flags FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE.",
      "StorageManagerService.java:getManageSpaceActivityIntent: obtains ActivityManagerInternal via LocalServices.getService(ActivityManagerInternal.class) and calls amInternal.getPendingIntentActivityAsApp(requestCode, intent, flags, options.toBundle(), packageName, appInfo.uid), passing the target package and the uid from the ApplicationInfo resolved for UserHandle.getUserId(originalUid).",
      "The catch of PackageManager.NameNotFoundException and its IllegalArgumentException(\"packageName not found\") are removed; the pre-existing appInfo == null check (IllegalArgumentException(\"Invalid packageName\")) remains the guard for unknown packages."
     ],
     "completeness": [
      "The fix covers the single PendingIntent construction site in getManageSpaceActivityIntent; the preceding caller checks (getPackagesForUid, hasExternalStorageAccess on packagesFromUid[0], getApplicationInfo for the caller's user, manageSpaceActivityName null check) are unchanged.",
      "The intent contents and flags are unchanged; only the way the PendingIntent is created changes. The material shows no other PendingIntent creation sites in StorageManagerService, so nothing else can be judged from it.",
      "The caller check still only inspects packagesFromUid[0] (acknowledged in the existing comment); this is untouched by the fix."
     ],
     "behavior_changes": [
      "The PendingIntent is created via ActivityManagerInternal.getPendingIntentActivityAsApp with packageName and appInfo.uid instead of from a system_server-created package context with identity cleared.",
      "A package that resolves via getApplicationInfo but fails createPackageContext no longer yields IllegalArgumentException(\"packageName not found\"); unknown packages still get IllegalArgumentException(\"Invalid packageName\") from the earlier check.",
      "Binder calling identity is no longer cleared during PendingIntent creation."
     ],
     "preconditions": [
      "Caller reaches the IStorageManager binder method getManageSpaceActivityIntent and its uid passes mStorageManagerInternal.hasExternalStorageAccess (MANAGE_EXTERNAL_STORAGE holder, per the code comment and SecurityException message \"Only File Manager Apps permitted\").",
      "Target packageName resolves via mIPackageManager.getApplicationInfo for the caller's user and declares a manageSpaceActivityName (otherwise null is returned)."
     ],
     "per_major": {
      "14": "same fix: commit 51b647175642 in android-security-14.0.0_r29 (RESTRICT AUTOMERGE), replaces createPackageContext-based PendingIntent.getActivity with getPendingIntentActivityAsApp.",
      "15": "same fix: commit 8c8709a5ad8c in android-security-15.0.0_r17, diff identical to Android 14 (old code used createPackageContext).",
      "16": "same fix: commit 84d85dbe121f in android-security-16.0.0_r8; added code identical to 14/15, the removed code used createPackageContextAsUser(packageName, /* flags= */ 0, UserHandle.of(UserHandle.getUserId(originalUid))).",
      "17": "same fix: commit 43ef4a09e448 in android-security-17.0.0_r1; identical to Android 16 (createPackageContextAsUser variant removed)."
     },
     "backport_notes": [
      "The new code requires ActivityManagerInternal.getPendingIntentActivityAsApp(int, Intent, int, Bundle, String, int) to be available in the target branch and imports for ActivityManagerInternal and LocalServices in StorageManagerService.java (no import changes appear in the diff); the material does not show that method's definition, so verify it exists before backporting. The 16/17 variant of the removed block differs only in the createPackageContextAsUser call."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of ActivityManagerInternal.getPendingIntentActivityAsApp and the exact PendingIntent ownership/send semantics it enforces are not shown in the material.",
      "Whether LocalServices.getService(ActivityManagerInternal.class) can return null at this call site is not shown; the diff adds no null handling.",
      "The commit has an empty Test: field; no test coverage is shown.",
      "The material does not show whether the import changes (ActivityManagerInternal, LocalServices, possibly unused PackageManager) were part of the same commit."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45529": {
     "cve": "CVE-2026-45529",
     "summary": "MmsSmsProvider.query() now restricts the simple-conversations, conversation recipients/subject, search and searchSuggest URI paths to messages whose sub_id belongs to the calling user's subscriptions, returning an empty cursor when the user has no associated subscriptions.",
     "component": "platform/packages/providers/TelephonyProvider, MmsSmsProvider (content://mms-sms)",
     "weakness": "Missing per-user authorization filter (cross-user data exposure): query paths did not apply the subscription-ID restriction already available in query()",
     "root_cause": "In src/com/android/providers/telephony/MmsSmsProvider.java, query() already computes a per-user subscription selection string (selectionBySubIds, derived from SubscriptionManager.getSubscriptionInfoListAssociatedWithUser per the test mock), but five URI cases ignored it: URI_CONVERSATIONS with simple=true (getSimpleConversations), URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT (getConversationById), URI_SEARCH_SUGGEST (raw SEARCH_QUERY on the words table) and URI_SEARCH (getTextSearchQuery over sms/pdu joined with words). These paths therefore returned conversation, recipient, subject and full-text search data for messages belonging to subscriptions not associated with the calling user, and returned data even when the user had no associated subscriptions at all.",
     "fix_analysis": [
      "MmsSmsProvider.java:query(), URI_CONVERSATIONS simple branch: if selectionBySubIds == null, log and return emptyCursor; otherwise selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds) before calling getSimpleConversations().",
      "MmsSmsProvider.java:query(), URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT: same null check returning emptyCursor, then concatenateWhere(selection, selectionBySubIds) before getConversationById().",
      "MmsSmsProvider.java:SEARCH_QUERY constant: the trailing \" ORDER BY snippet LIMIT 50;\" is removed so a clause can be appended. In URI_SEARCH_SUGGEST: null check returning emptyCursor, then the raw query becomes SEARCH_QUERY + \" AND \" + selectionBySubIds.replace(\"'\", \"\") + \" ORDER BY snippet LIMIT 50;\" passed to db.rawQuery with SEARCH_STRING.",
      "MmsSmsProvider.java:getTextSearchQuery(): new third parameter selectionBySubIds. When non-empty, an smsSubIdClause (\" AND \" + smsTable + \".\" + selectionBySubIds with quotes stripped) is inserted into the sms sub-select and an mmsSubIdClause (same with pduTable prefix) into the mms/part sub-select, before the words.table_to_use condition.",
      "MmsSmsProvider.java:query(), URI_SEARCH: null check returning emptyCursor (placed after the existing validation that throws for unsupported queries), then getTextSearchQuery(smsTable, pduTable, selectionBySubIds) is used for db.rawQuery.",
      "tests/.../MmsSmsProviderTest.java: adds testQuery_subIdEmpty_returnsEmptyCursor (mocks getSubscriptionInfoListAssociatedWithUser to return an empty list and asserts count 0 for the five URIs) and testQuery_subIdValid_doesNotReturnEmptyCursor (asserts a non-null cursor with the default sub mocks)."
     ],
     "completeness": [
      "Covers exactly five URI cases: URI_CONVERSATIONS (simple=true only), URI_CONVERSATIONS_RECIPIENTS, URI_CONVERSATIONS_SUBJECT, URI_SEARCH_SUGGEST, URI_SEARCH. The non-simple else branch of URI_CONVERSATIONS and all other URI cases are not touched by this diff; the material does not show whether those already consumed selectionBySubIds, so no gap can be asserted from the code.",
      "The raw-SQL paths (URI_SEARCH_SUGGEST, getTextSearchQuery) strip single quotes from selectionBySubIds before embedding it, while the concatenateWhere paths pass it unchanged; the filter relies entirely on the correctness of the pre-existing selectionBySubIds string.",
      "The null-check branches are enforced regardless of caller; no exemption for privileged or system callers is visible in the diff.",
      "Test coverage checks only the empty-subscription short-circuit; the positive test does not verify that cross-subscription rows are actually excluded."
     ],
     "behavior_changes": [
      "For the five patched URI paths, a caller whose user has no associated subscriptions now receives an empty (non-null) cursor instead of data, with a Log.d message.",
      "Results of those paths are additionally constrained by the sub_id clause, so messages/conversations tied to subscriptions not associated with the calling user no longer appear.",
      "SEARCH_QUERY no longer carries ORDER BY/LIMIT itself; the suggest query is now assembled at runtime (same final ordering and LIMIT 50).",
      "Private getTextSearchQuery signature gains a selectionBySubIds parameter (no external API impact)."
     ],
     "preconditions": [
      "Caller can query content://mms-sms/ URIs of MmsSmsProvider (conversations?simple=true, conversations/<id>/recipients, conversations/<id>/subject, search?pattern=, searchSuggest?pattern=); this normally requires SMS read access to the provider (general platform knowledge).",
      "A device state where the calling user's selectionBySubIds is null or covers only a subset of the sub_ids present in the stored messages."
     ],
     "per_major": {
      "14": "same fix: commit 475853ddf6b3 in android-security-14.0.0_r29, 47-line provider change plus tests, same change content as 15/16 (different blob hashes and line offsets).",
      "15": "same fix: commit 4fab244beaa3 in android-security-15.0.0_r17; same change content as 14, and byte-identical diff to 16 (same pre/post blob hashes dd9f07cf..0fa97e1f).",
      "16": "same fix: commit 190c0a3b8e44 in android-security-16.0.0_r8, byte-identical diff to 15.",
      "17": "adapted fix: commit 1fab99e951d5 in android-security-17.0.0_r1; getTextSearchQuery already takes canReadRestrictedMessages and otpFilter, so selectionBySubIds is added as a fifth parameter and the sub_id clauses are appended after the read-restriction (and, for sms, OTP) clauses; the URI_SEARCH_SUGGEST check is placed after the existing otpFilter early-return; new tests carry @DisableFlags(Flags.FLAG_SECURE_ACCESS_TO_RESTRICTED_RCS_MESSAGES)."
     },
     "backport_notes": [
      "The change depends on selectionBySubIds already being computed in MmsSmsProvider.query() (from SubscriptionManager.getSubscriptionInfoListAssociatedWithUser, per the test mock) and on the emptyCursor local; trees lacking that prior logic need it first.",
      "For forks with extra getTextSearchQuery parameters (as in Android 17) add selectionBySubIds as the last parameter and insert the table-qualified clauses before the words.table_to_use conditions."
     ],
     "confidence": "high",
     "unknowns": [
      "How selectionBySubIds is built (exact SQL form, column name, quoting) and whether it is null only when the subscription list is empty; the diff only shows its use.",
      "Whether the words table and the tables queried by getConversationById/getSimpleConversations carry the column referenced by selectionBySubIds; the material shows no schema.",
      "Which other URI cases in query() already applied selectionBySubIds before this change.",
      "The specific reporter scenario and the user/subscription configuration used in the manual test (b/510423582)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49895": {
     "cve": "CVE-2026-49895",
     "summary": "Raises the EHT Operation element minimum-length constant in src/common/ieee802_11_defs.h from 1 to 5 so the length check used by get_eht_operation_channel_width() accounts for the fixed-size fields that precede the EHT Operation Information field.",
     "component": "platform/external/wpa_supplicant_8, IEEE 802.11 common definitions (EHT Operation element parsing)",
     "weakness": "Incorrect bounds check constant leading to heap out-of-bounds read (information disclosure)",
     "root_cause": "Per the commit message and diff, EHT_OPERATION_IE_MIN_LEN (EHT_OPERATION_ELEM_MIN_LEN on Android 17) was defined as 1. That value predated the addition of basic_eht_mcs_nss_set[4] to struct ieee80211_eht_operation, so the fixed part that precedes the operation information is 5 octets. Per the commit message, get_eht_operation_channel_width() bounds-checks the element against this constant and, when EHT_OPER_INFO_PRESENT is set, accesses oper_info at offset 5. An element with length between 1 and 4 inclusive passed the check, which allowed a heap out-of-bounds read.",
     "fix_analysis": [
      "src/common/ieee802_11_defs.h: the constant EHT_OPERATION_IE_MIN_LEN is changed from 1 to 5 (Android 14, 15, 16). On Android 17 the already-renamed EHT_OPERATION_ELEM_MIN_LEN is changed from 1 to 5. On 14/15/16 the comment is also updated from the P802.11be/D2.3 draft reference (9.4.2.311) to IEEE Std 802.11be-2024 (9.4.2.321).",
      "No code in get_eht_operation_channel_width() is modified. Per the commit message, the existing bounds check that uses the macro now accounts for the 5 octets of fixed fields before oper_info."
     ],
     "completeness": [
      "The fix changes only the length-check constant. Every site that compares an EHT Operation element length against EHT_OPERATION_IE_MIN_LEN / EHT_OPERATION_ELEM_MIN_LEN gets the corrected minimum. Sites that use a different constant or no check are not affected by this change.",
      "The material does not show the body of get_eht_operation_channel_width(). It also does not show whether the length of the variable operation information (EHT_OPER_INFO_MIN_LEN = 3, plus the optional 2-octet disabled subchannel bitmap) is validated separately after the minimum-length check. That cannot be judged from the diff."
     ],
     "behavior_changes": [
      "EHT Operation elements with length 1 through 4 now fail the minimum-length check wherever the macro is used. Previously they were accepted."
     ],
     "preconditions": [
      "wpa_supplicant parses an EHT Operation element and reaches get_eht_operation_channel_width() (from the commit message).",
      "The element length is between 1 and 4 inclusive and the EHT_OPER_INFO_PRESENT flag is set (from the commit message)."
     ],
     "per_major": {
      "14": "same fix: 0ce102474997 in android-security-14.0.0_r29 (a cherry-pick per its commit message) changes EHT_OPERATION_IE_MIN_LEN from 1 to 5 and updates the spec comment.",
      "15": "same fix: 234d284145fc in android-security-15.0.0_r17, same content (EHT_OPERATION_IE_MIN_LEN 1 -> 5); only whitespace alignment differs.",
      "16": "same fix: 6579923889d6 in android-security-16.0.0_r8, same content (EHT_OPERATION_IE_MIN_LEN 1 -> 5); the surrounding context differs (RNR MLD param defines).",
      "17": "adapted fix: 14398e6a6663 in android-security-17.0.0_r1 changes the renamed macro EHT_OPERATION_ELEM_MIN_LEN from 1 to 5. The 802.11be-2024 comment was already present, so it is a one-line change."
     },
     "backport_notes": [
      "This is a single constant change. On older trees, check the macro name (EHT_OPERATION_IE_MIN_LEN vs EHT_OPERATION_ELEM_MIN_LEN) and confirm that struct ieee80211_eht_operation contains basic_eht_mcs_nss_set[4]: per the commit message, the fixed fields before oper_info are 5 octets because of that field. wpa_supplicant_8 is not a mainline module, so the fix ships with the platform image (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of get_eht_operation_channel_width() and the exact comparison it performs against the macro are not included in the material.",
      "Which frames and code paths (scan results, association, hostapd if built) lead to get_eht_operation_channel_width() on Android is not shown.",
      "Whether the macro is referenced at any other site in the tree is not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49913": {
     "cve": "CVE-2026-49913",
     "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now compares the mmap() result for the client-supplied source SharedBuffer against MAP_FAILED instead of NULL, and installs a ScopeGuard that munmap()s the mapping on every exit path after the check.",
     "component": "platform/hardware/interfaces, cas/aidl/default (default AIDL CAS HAL, IDescrambler implementation)",
     "weakness": "Incorrect check of function return value (mmap failure compared to NULL instead of MAP_FAILED), leading to use of an invalid pointer; secondary resource leak of the mapping",
     "root_cause": "In cas/aidl/default/DescramblerImpl.cpp, DescramblerImpl::descramble tested the mmap() result for the source buffer with `if (srcPtr == NULL)`. mmap() returns MAP_FAILED, not NULL, on failure, so a failed mapping passed the check and execution continued with MAP_FAILED as the source pointer. The commit describes this as leading to an arbitrary-address memory access. The comment above the check notes that the offset and size in the SharedBuffer are client-controlled. The commit also adds munmap() cleanup 'to prevent memory leaks' of the mapping.",
     "fix_analysis": [
      "cas/aidl/default/DescramblerImpl.cpp :: DescramblerImpl::descramble: the failure test changes from `srcPtr == NULL` to `srcPtr == MAP_FAILED`. On failure the function still logs 'Failed to map src buffer.' and returns toStatus(BAD_VALUE), so a failed mmap now stops the call before the pointer is used.",
      "cas/aidl/default/DescramblerImpl.cpp :: DescramblerImpl::descramble: right after the failure check, `::android::base::make_scope_guard` captures srcPtr and `size = srcBuffer.heapBase.size` and calls munmap(srcPtr, size) when the guard goes out of scope. This releases the mapping on every later return path, including the validateRangeForSize() check that follows.",
      "cas/aidl/default/DescramblerImpl.cpp: adds `#include <android-base/scopeguard.h>` for make_scope_guard."
     ],
     "completeness": [
      "Covers the source-buffer mmap() in DescramblerImpl::descramble: the failure check is now correct and the mapping is unmapped on all exit paths after the check.",
      "The ScopeGuard is installed after the MAP_FAILED check, so the failure path does not call munmap on a mapping that does not exist.",
      "The diff only touches the srcBuffer mapping. The material does not show the rest of descramble() (for example, whether a destination buffer is mapped with the same NULL-style check, or whether an existing munmap remains), so coverage of other mappings in this function or in the HIDL CAS HAL cannot be judged from the material.",
      "The guard unmaps srcBuffer.heapBase.size bytes. The mmap call is not shown, so the material does not confirm that the mapping used the same length."
     ],
     "behavior_changes": [
      "A descramble() call whose source heap cannot be mapped now fails with BAD_VALUE (logged 'Failed to map src buffer.') instead of continuing with an invalid pointer.",
      "The source mapping is now released when descramble() returns, including on the validateRangeForSize() error path.",
      "Only the default HAL implementation changes; the diff does not touch the interface."
     ],
     "preconditions": [
      "The device uses the default AIDL CAS HAL implementation (cas/aidl/default) for IDescrambler.",
      "A client reaches DescramblerImpl::descramble with a SharedBuffer whose heapBase makes mmap() fail. Per the code comment, the offset and size of the SharedBuffer are client-controlled.",
      "Reachability per index search: android.media.MediaDescrambler.descramble -> native_descramble -> android_media_MediaDescrambler_native_descramble (frameworks/base/media/jni) -> CAS descrambler; ATSParser::Stream::flushScrambled also uses a descrambler with shared memory. The indexed Android 14 snippets show the HIDL wrapper; the AIDL binding path is not shown."
     ],
     "per_major": {
      "14": "same fix: commit 8858a5521ba4 in android-security-14.0.0_r29, same hunk (file mode 100755, pre-image blob a96fd466bc, @@ -100,10 +101,14 @@). 7/8 lines of the patched region present at that tag.",
      "15": "same fix: commit 9bdc5c54c702 in android-security-15.0.0_r17, same hunk against blob 0f4e99b315. 7/8 lines of the patched region present.",
      "16": "same fix: commit 0b6f21543e54 in android-security-16.0.0_r8, same hunk against blob 0f4e99b315 (same pre-image as 15). 7/8 lines of the patched region present.",
      "17": "same fix: commit d9f6b94ba0f0 in android-security-17.0.0_r1, same hunk against blob f9c2a65e9f, shifted by one line (@@ -101,10 +102,14 @@). 7/8 lines of the patched region present."
     },
     "backport_notes": [
      "Single self-contained commit. The hunk is the same on all four majors and should apply wherever cas/aidl/default/DescramblerImpl.cpp has the `srcPtr == NULL` check. It needs android-base/scopeguard.h, so check that the fork's module build depends on libbase."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of DescramblerImpl::descramble (the mmap call, any destination-buffer handling, any existing munmap) is not shown. Whether other mappings use the same incorrect check, or whether a fork's existing munmap would interact with the new ScopeGuard, is unknown.",
      "Whether the HIDL CAS HAL default implementation has the same pattern is not shown in the material.",
      "Which line of the 8-line patched region is missing at each security tag is not stated.",
      "The privilege boundary behind the High EoP rating is not shown beyond the bulletin rating."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55290": {
     "cve": "CVE-2026-55290",
     "summary": "ResStringPool::setTo in libandroidfw now checks stringsStart and stylesStart against the chunk size unconditionally. It also checks that the style-offset array (styleCount * 4 bytes) fits inside the data buffer and ends before stringsStart. This closes an out-of-bounds heap read in ResStringPool::styleAt.",
     "component": "platform/frameworks/base, libs/androidfw (ResStringPool resource string pool parser)",
     "weakness": "Missing/insufficient bounds check on header fields leading to out-of-bounds heap read (information disclosure)",
     "root_cause": "In libs/androidfw/ResourceTypes.cpp, ResStringPool::setTo skipped validation of stringsStart when stringCount was zero (per the commit message), so stringsStart could hold an arbitrary value such as 0xFFFFFFFF. In the styleCount > 0 path, the old code had three weak checks:\n1. It only checked that the start of the style-offset array (mEntryStyles - mHeader) was not past size (an int comparison). It did not check that styleCount * sizeof(uint32_t) bytes fit.\n2. The overflow guard only bounded styleCount against the uint32_t maximum / 4.\n3. The overlap check `styleCount * sizeof(uint32_t) > (stringsStart - styleOffsetsStart)` did not fire when stringsStart was huge and unvalidated.\nA pool with stringCount == 0, a large styleCount and a huge stringsStart therefore passed setTo. Per the commit message, this led to an OOB heap read in ResStringPool::styleAt.",
     "fix_analysis": [
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo: two new unconditional checks go right after `mSize = mHeader->header.size;` and before any stringCount/styleCount branching. The pool is rejected (mError=BAD_TYPE) if mHeader->stringsStart > mSize or mHeader->stylesStart > mSize. This closes the stringCount == 0 gap.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, inside the styleCount > 0 block: the old start-only check `(mEntryStyles - mHeader) > (int)size` is replaced by these steps:\n1. styleOffsetsStart is computed.\n2. styleCount is checked against SIZE_MAX / sizeof(uint32_t).\n3. styleOffsetsSize = styleCount * sizeof(uint32_t) is computed.\n4. The pool is rejected if styleOffsetsSize > size or styleOffsetsStart > size - styleOffsetsSize. The subtraction is ordered to avoid underflow.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, same block: a new check rejects the pool if styleOffsetsStart > stringsStart or styleOffsetsSize > stringsStart - styleOffsetsStart. The style-offset array therefore cannot extend past the string data start, and stringsStart has already been bounded by mSize.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo: two older checks that came after `mStyles = ...` are deleted, because the new checks above replace them:\n- the uint32_t-max overflow check;\n- the original `styleCount * sizeof(uint32_t) > (stringsStart - styleOffsetsStart)` check.\nThe existing `stylesStart >= header.size` check and the mStylePoolSize computation are unchanged.",
      "libs/androidfw/tests/ResourceTypes_test.cpp adds two tests:\n- ResStringPool_StyleCountUnboundedWhenStringCountIsZero: stringCount 0, styleCount 100, stringsStart 0xFFFFFFFF, expects BAD_TYPE.\n- ResStringPool_StyleCountCalculationOverflow: stringCount 0, styleCount 0x40000000, expects BAD_TYPE."
     ],
     "completeness": [
      "The fix covers the style-offset array: both its start and its full styleCount * 4 extent are now bounded by the buffer size and by stringsStart, whatever the value of stringCount.",
      "stringsStart and stylesStart are now validated against header.size for every pool. The new early check uses `> mSize`, so a value equal to mSize is still accepted.",
      "This fix does not change the style span data region (mStyles / mStylePoolSize, derived from header.size - stylesStart). That region still relies on the existing `stylesStart >= header.size` check in setTo.",
      "The new extent check compares against the `size` argument rather than mSize (header.size).",
      "Only ResStringPool::setTo is modified; this change touches no other parser in ResourceTypes.cpp."
     ],
     "behavior_changes": [
      "ResStringPool::setTo now returns BAD_TYPE (and logs ALOGW) for pools whose stringsStart or stylesStart exceeds header.size, even when stringCount == 0 or styleCount == 0.",
      "Pools are now rejected if their style-offset array does not fit within the data buffer or extends past stringsStart.",
      "The overflow guard on styleCount is now against SIZE_MAX / 4 instead of the uint32_t maximum / 4. The log messages for these rejections have new text."
     ],
     "preconditions": [
      "A crafted RES_STRING_POOL_TYPE chunk has to reach ResStringPool::setTo. The material does not show which callers pass untrusted data.",
      "The header has stringCount == 0, styleCount > 0, and a stringsStart larger than the chunk (as in the commit message and the new test ResStringPool_StyleCountUnboundedWhenStringCountIsZero).",
      "A later call to ResStringPool::styleAt is where the OOB read happens (per the commit message)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 1eb9fc572c86 in android-security-14.0.0_r29, a cherry-pick of 968cb0736ca6 (Change-Id I3e1e4579fbda8ee89c5fea3945d523c6e5b72dc9). The hunks are identical to the other branches; only line offsets differ.",
      "15": "same fix: platform/frameworks/base ed4eedc69050 in android-security-15.0.0_r17, with identical code and test changes.",
      "16": "same fix: platform/frameworks/base 01bff648b538 in android-security-16.0.0_r8, with identical code and test changes.",
      "17": "same fix: platform/frameworks/base 7e42d365a0c2 in android-security-17.0.0_r1, with identical code and test changes."
     },
     "backport_notes": [
      "The change is self-contained in ResStringPool::setTo and was applied verbatim on 14 through 17. A backport has to do three things:\n- add the early stringsStart/stylesStart checks;\n- replace the old start-only `(int)size` check with the new extent and overlap checks;\n- remove the two later checks that the new ones replace.\nThe new tests build mock headers by hand with util::HostToDevice32/16, htodl and offsetof."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the body of ResStringPool::styleAt.",
      "The material does not show which callers reach setTo with untrusted data. The index search returned only unrelated Java TypedValue.setTo matches.",
      "The material does not show the body of validate_chunk, so it does not confirm that header.size is bounded by the buffer size.",
      "The material does not show the stringCount > 0 validation block.",
      "The material does not show whether upstream commit 968cb0736ca6 came with any other related changes."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55294": {
     "cve": "CVE-2026-55294",
     "summary": "In libhevc's ihevcd_get_tu_data_size(), the tu_map part of the TU data size is now budgeted with num_tu (luma plus chroma TUs) instead of num_luma_tu, so the size matches the space the initialization logic consumes.",
     "component": "platform/external/libhevc, HEVC software decoder (libhevcdec), TU data sizing in decoder/ihevcd_utils.c",
     "weakness": "Heap buffer overflow caused by an undersized buffer (size computed with the wrong element count)",
     "root_cause": "ihevcd_get_tu_data_size() computes the TU data size as the sum of several sub-regions: CTB start indices, tu_map, the tu_t array, coded-subblock/scan_idx bytes and coefficient data. Every per-TU sub-region except tu_map was sized with num_tu = num_luma_tu + num_chroma_tu. tu_map was sized with num_luma_tu * sizeof(UWORD8) only. According to the commit message, the initialization logic consumes tu_map space for both luma and chroma TUs, so the tu_map budget was too small and a heap buffer overflow resulted. The writer code is not in the material.",
     "fix_analysis": [
      "decoder/ihevcd_utils.c:ihevcd_get_tu_data_size: 'tu_data_size += num_luma_tu * sizeof(UWORD8);' (the tu_map budget) becomes 'tu_data_size += num_tu * sizeof(UWORD8);'. num_tu = num_luma_tu + num_chroma_tu is already computed earlier in the function. The returned size grows by num_chroma_tu bytes, which matches the other per-TU sub-regions.",
      "The code that writes tu_map is not changed; the fix is only on the sizing side. The file mode also changes from 100755 to 100644, which has no functional effect."
     ],
     "completeness": [
      "The fix covers the one mismatch visible in the diff, the tu_map budget. After the change every sub-region in ihevcd_get_tu_data_size() is scaled by num_tu, except the per-CTB index array, which is scaled by num_ctb + 1.",
      "No bounds check is added on the write side. The tu_map initialization code is not in the material, so it cannot be judged whether the consumer makes any other size assumptions."
     ],
     "behavior_changes": [
      "The size returned by ihevcd_get_tu_data_size() grows by num_chroma_tu bytes (num_luma_tu >> 1), so decoder memory use rises slightly. There are no new error codes, API changes or stricter input validation."
     ],
     "preconditions": [
      "An HEVC bitstream has to be decoded by the libhevc software decoder. Per index search, the decoder is driven from frameworks/av/media/codec2/components/hevc/C2SoftHevcDec.cpp (setDecodeArgs), frameworks/av/media/libstagefright/codecs/hevcdec/SoftHEVC.cpp (setDecodeArgs) and device/generic/goldfish-opengl/system/codecs/c2/decoders/hevcdec/GoldfishHevcHelper.cpp.",
      "The stream properties needed to trigger the overflow are not shown. The commit message refers to a PoC in the bug."
     ],
     "per_major": {
      "14": "same fix: platform/external/libhevc d9977ef8294a in android-security-14.0.0_r29, one-line hunk in decoder/ihevcd_utils.c (Change-Id I8ff89026f66addf34b18dbedf9001db292db8fe9).",
      "15": "same fix: platform/external/libhevc c43bbf882f97 in android-security-15.0.0_r17, identical hunk.",
      "16": "same fix: platform/external/libhevc 5492b022e4d6 in android-security-16.0.0_r8, identical hunk (same pre- and post-image blob ids as Android 15).",
      "17": "same fix: platform/external/libhevc 9aa38f2733f4 in android-security-17.0.0_r1, identical hunk."
     },
     "backport_notes": [
      "This is a single self-contained one-line change with no visible dependencies. The hunk and its context lines are identical on all four branches, though the file blobs differ. It should apply to any libhevc tree that still has 'num_luma_tu * sizeof(UWORD8)' for the tu_map term."
     ],
     "confidence": "high",
     "unknowns": [
      "The code that initializes or writes tu_map is not in the material. Its function name, the exact overrun size and the triggering stream conditions are known only from the commit message.",
      "The callers of ihevcd_get_tu_data_size() and how the buffer is allocated are not shown.",
      "What memory lies next to the overrun and the practical impact beyond the bulletin's High / EoP rating are not shown."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58822": {
     "cve": "CVE-2026-58822",
     "summary": "Adds early size guards to FreeType's LCD and LCD-vertical smooth rasterizer entry points (ft_smooth_raster_lcd, ft_smooth_raster_lcdv) so bitmaps whose width/3 or rows/3 exceed 0x7FFF are rejected with Raster_Overflow instead of producing truncated 16-bit span coordinates that drive an out-of-bounds write.",
     "component": "platform/external/freetype, smooth rasterizer (src/smooth/ftsmooth.c)",
     "weakness": "Missing bounds check leading to integer truncation (16-bit FT_Span.x) and heap out-of-bounds (underflow) write",
     "root_cause": "In src/smooth/ftsmooth.c, ft_smooth_raster_lcd renders the outline in direct mode with ft_smooth_lcd_spans as the span callback. That callback computes the destination as dst_line + spans->x * 3 and writes spans->len bytes at stride 3. Per the commit message and added comment, FT_Span.x is 16-bit and gets truncated when computing span x-coordinates. When that happens, the destination pointer can land before the bitmap buffer and the coverage write underflows the heap allocation. Neither ft_smooth_raster_lcd nor ft_smooth_raster_lcdv validated the bitmap dimensions before rasterizing, unlike the existing guard in ft_smooth_raster_overlap referenced by the commit message.",
     "fix_analysis": [
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcd: before setting up FT_Raster_Params, return FT_THROW( Raster_Overflow ) if bitmap->width / 3 > 0x7FFF, rejecting outlines too wide for the 16-bit FT_Span.x (per the added comment).",
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcdv: before setting params.target/source/flags, return FT_THROW( Raster_Overflow ) if bitmap->rows / 3 > 0x7FFF, rejecting outlines that are too high for the vertical LCD path.",
      "No change is made to ft_smooth_lcd_spans itself or to the FT_Span type; the fix is purely a precondition check at the two entry points, single commit, 8 added lines."
     ],
     "completeness": [
      "Covers both LCD entry points: horizontal (ft_smooth_raster_lcd, width check) and vertical (ft_smooth_raster_lcdv, rows check).",
      "The guard is a coarse bound on bitmap dimensions, not on the actual span coordinates produced by the rasterizer; ft_smooth_lcd_spans still performs unchecked pointer arithmetic (dst_line + spans->x * 3) and relies on the caller's clip box and this new size check for safety.",
      "ft_smooth_raster_lcd only checks width; ft_smooth_raster_lcdv only checks rows. The material does not show whether the other dimension needs a guard in each function.",
      "The clip box in ft_smooth_raster_lcd is set to bitmap->width (full LCD width) while the guard bounds width/3; the material does not show how the rasterizer relates clip_box.xMax to the span x values delivered to ft_smooth_lcd_spans, so whether the guard fully prevents 16-bit truncation cannot be judged from the diff.",
      "ft_smooth_raster_overlap already had a similar guard per the commit message; no other function is touched by the diff."
     ],
     "behavior_changes": [
      "Rendering a glyph in LCD mode with bitmap->width / 3 > 32767 now fails with FT_Err_Raster_Overflow instead of rasterizing.",
      "Rendering a glyph in LCD_V mode with bitmap->rows / 3 > 32767 now fails with FT_Err_Raster_Overflow instead of rasterizing.",
      "No change for normal-size glyphs."
     ],
     "preconditions": [
      "FreeType smooth renderer invoked in LCD (ft_smooth_raster_lcd) or LCD vertical (ft_smooth_raster_lcdv) render mode.",
      "A glyph bitmap whose width/3 (LCD) or rows/3 (LCD_V) exceeds 0x7FFF.",
      "On Android 14, hwui exposes a FreeType-backed Skia font manager (frameworks/base/libs/hwui/utils/TypefaceUtils.h FreeTypeFontMgr), per index search; whether callers select LCD render modes is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 51c2994abfc5 in android-security-14.0.0_r29, identical 8-line change to src/smooth/ftsmooth.c (hunks at lines 137 and 204), 8/8 lines present.",
      "15": "same fix: commit fc6e207660f9 in android-security-15.0.0_r17, identical content, hunks offset to lines 141 and 208, 8/8 lines present.",
      "16": "same fix: commit 6ca3925a3c97 in android-security-16.0.0_r8, identical content and offsets as 15, 8/8 lines present.",
      "17": "same fix: commit 4321c90fc79e in android-security-17.0.0_r1, identical content, same hunk offsets, 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit with no dependencies; apply the two checks at the top of ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c (only line offsets differ between branches). Note the added comment text contains a non-ASCII character; strip it if the target tree enforces ASCII sources."
     ],
     "confidence": "high",
     "unknowns": [
      "How the gray rasterizer derives span x values relative to clip_box.xMax = bitmap->width in LCD direct mode (not in the material), so the precise boundary at which truncation begins, and whether width/3 <= 0x7FFF fully prevents it, is not shown.",
      "Whether Android system font rendering paths (Skia via hwui) actually request LCD/LCD_V render modes.",
      "How a sufficiently large outline reaches the renderer on Android (input source) is not shown.",
      "Upstream FreeType commit or version corresponding to this change is not given."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58839": {
     "cve": "CVE-2026-58839",
     "summary": "Shrinks the stack-allocated read buffer in the static forEachLine() helper of incfs/MountRegistry.cpp from 128 KiB to 32 KiB, adds a compile-time maxLineLength template parameter with a static_assert against the buffer size, and marks the buffer __attribute__((uninitialized)).",
     "component": "platform/system/incremental_delivery, incfs library (MountRegistry mountinfo parsing)",
     "weakness": "Excessive stack allocation (large fixed-size automatic buffer) leading to stack overflow / stack exhaustion",
     "root_cause": "forEachLine() in incfs/MountRegistry.cpp declared `char buffer[128 * 1024]` on the stack to pread() the mountinfo file. It is invoked from MountRegistry::Mounts::loadFrom(), which runs from MountRegistry::ensureUpToDate() (called by copyMounts() and reload()) whenever poll() on the mountinfo fd does not return 0, meaning it reports an event or fails. If the calling thread's stack is already close to its limit, a 128 KiB frame can overrun the stack, as stated in the commit message (\"may cause stack overflow if we're already nearing the limit\").",
     "fix_analysis": [
      "incfs/MountRegistry.cpp:forEachLine - kBufSize reduced from 128 * 1024 to 32 * 1024 (and changed from static constexpr to constexpr). The buffer is still a stack array, but its frame cost is a quarter of the previous one.",
      "incfs/MountRegistry.cpp:forEachLine - new template parameter `size_t maxLineLength` with `static_assert(maxLineLength < kBufSize, ...)`. A caller that declares lines as long as the buffer or longer now fails to compile.",
      "incfs/MountRegistry.cpp:forEachLine - buffer declared `__attribute__((uninitialized))`, opting out of automatic variable zero-initialization for the 32 KiB array.",
      "incfs/MountRegistry.cpp:MountRegistry::Mounts::loadFrom - the call site is updated to `forEachLine<maxLineSize>` with `constexpr auto maxLineSize = 3 * PATH_MAX + 1024` (a mountinfo line holds 3 paths plus shorter fields), which satisfies the static_assert."
     ],
     "completeness": [
      "The fix only reduces the size of the stack frame. The buffer is still an automatic array (32 KiB), so stack usage is lowered, not eliminated. The static_assert message added in the code says a rewrite with a vector would be needed if the line size requirement grows.",
      "In this commit only forEachLine() and the call site in Mounts::loadFrom() are changed. No other stack buffers are touched.",
      "The static_assert is a compile-time guard on the declared maximum line length. The diff does not show any new runtime handling for a mountinfo line that exceeds the buffer, so behaviour in that case depends on the rest of the loop, which is not shown."
     ],
     "behavior_changes": [
      "forEachLine() uses a 32 KiB stack buffer instead of 128 KiB, so each pread() call reads at most 32 KiB and large mountinfo files may need more iterations.",
      "The buffer is no longer subject to automatic variable initialization (__attribute__((uninitialized))).",
      "forEachLine() now requires an explicit maxLineLength template argument. Callers that request a line length of 32 KiB or more fail at compile time."
     ],
     "preconditions": [
      "The code runs in a process that uses libincfs's MountRegistry. MountRegistry::ensureUpToDate() (from copyMounts() or reload()) calls Mounts::loadFrom() when poll() on the mountinfo fd reports an event or fails.",
      "The calling thread must already be near its stack limit for the 128 KiB frame to overflow. The material does not show which thread or call chain triggers this.",
      "Per index search, frameworks/base/services/incremental/IncrementalService.cpp uses incfs, but the index did not show a call into MountRegistry."
     ],
     "per_major": {
      "14": "same fix: commit b6d06e7ccfc2 in android-security-14.0.0_r29, same diff (blobs 376eaf2..eaf97dc) as the other branches. 6 of 8 lines of the patched region are present at that tag.",
      "15": "same fix: commit 877702e09486 in android-security-15.0.0_r17, same diff. 6 of 8 lines of the patched region are present.",
      "16": "same fix: commit 67b14f0e00af in android-security-16.0.0_r8, same diff. 6 of 8 lines of the patched region are present.",
      "17": "Not listed by the bulletin, which means not affected per the bulletin. 6 of 8 lines of the patched region are present at android-security-17.0.0_r1. The material shows no fix commit for 17."
     },
     "backport_notes": [
      "The diff is identical across 14, 15 and 16 (same pre/post blobs 376eaf2..eaf97dc), so the single commit is expected to apply onto trees where forEachLine in incfs/MountRegistry.cpp still has `static constexpr auto kBufSize = 128 * 1024;`.",
      "`__attribute__((uninitialized))` is a clang attribute that works with the clang used for Android platform builds (general platform knowledge). Removing it would not change the stack-size reduction."
     ],
     "confidence": "high",
     "unknowns": [
      "Which process, thread and call chain reach forEachLine() with a nearly exhausted stack, and how that leads to the EoP rating in the bulletin.",
      "How the existing loop body (not shown in full) handles a single mountinfo line that exceeds the buffer size.",
      "Whether Mounts::loadFrom() is the only caller of forEachLine() (the full file is not shown).",
      "Whether this fix was the only change under bug 466091304 (the material shows one commit per branch).",
      "Why only 6 of 8 lines of the patched region are present at each tag, and how Android 17 relates to the fix."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58874": {
     "cve": "CVE-2026-58874",
     "summary": "Adds the missing subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony). When the calling user is not associated with the subscription, the send is rejected with RESULT_USER_NOT_ALLOWED. The fix also makes the destAddr parameter of TelephonyPermissions.checkSubscriptionAssociatedWithUser nullable (frameworks/base) so the new callers can pass a null address.",
     "component": "platform/frameworks/opt/telephony (SmsController, IccSmsInterfaceManager) and platform/frameworks/base (telephony/common TelephonyPermissions)",
     "weakness": "Missing authorization check (the calling user is not verified as associated with the target subscription)",
     "root_cause": "Before the fix, SmsController.sendStoredText and SmsController.sendStoredMultipartText forwarded to IccSmsInterfaceManager.sendStoredText / sendStoredMultipartText for the given subId without calling TelephonyPermissions.checkSubscriptionAssociatedWithUser. sendTextForSubscriber is different: its existing SmsControllerTest cases show it already consults SubscriptionManager.isSubscriptionAssociatedWithUser (per index search). As a result, a caller running as one user could request a stored-message send over a subscription that is not associated with that user. Separately, the 4-argument checkSubscriptionAssociatedWithUser declared destAddr @NonNull and passed it straight to TelephonyManager.isEmergencyNumber, with no handling for callers that cannot resolve a destination address.",
     "fix_analysis": [
      "frameworks/base, telephony/common/com/android/internal/telephony/TelephonyPermissions.java:checkSubscriptionAssociatedWithUser(Context,int,UserHandle,String): the destAddr annotation changes from @NonNull to @Nullable, and the emergency-number short-circuit becomes `tm != null && destAddr != null && tm.isEmergencyNumber(destAddr)`. With a null address the method skips the emergency bypass and falls through to the 3-argument checkSubscriptionAssociatedWithUser(context, subId, callerUserHandle).",
      "frameworks/opt/telephony, src/java/com/android/internal/telephony/IccSmsInterfaceManager.java:loadTextAndAddress: visibility changes from private to public so SmsController can read the stored message body and address (index 1) for a message URI. The method clears calling identity around the ContentResolver query and returns null if no row is found or on SQLiteException.",
      "frameworks/opt/telephony, src/java/com/android/internal/telephony/SmsController.java: new private helper getDestAddress(IccSmsInterfaceManager, Uri). It returns null when iccSmsIntMgr is null or loadTextAndAddress returns null, and otherwise returns textAndAddress[1].",
      "SmsController.java:sendStoredText: after the caller log line, the method gets callingUser = Binder.getCallingUserHandle(), uid = Binder.getCallingUid() and destAddr = getDestAddress(...), then calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, callingUser, destAddr). If that returns false, it calls TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, uid, callingPkg), logs, sends SmsManager.RESULT_USER_NOT_ALLOWED through sendErrorInPendingIntent(sentIntent, ...) and returns before touching iccSmsIntMgr.",
      "SmsController.java:sendStoredMultipartText: the same gate, using sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED).",
      "tests/telephonytests/.../SmsControllerTest.java: four new tests (sendStoredText_ and sendStoredMultipartText_, each with associatedUser_sendsSms and notAssociatedUser_fails). They stub loadTextAndAddress and SubscriptionManager.isSubscriptionAssociatedWithUser, then verify whether IccSmsInterfaceManager.sendStoredText or sendStoredMultipartText is invoked."
     ],
     "completeness": [
      "Both stored-message entry points in SmsController (sendStoredText and sendStoredMultipartText) get the gate, so the single-message and multipart paths are handled the same way.",
      "The gate also runs when iccSmsIntMgr is null: destAddr becomes null and the 3-argument association check still executes. The existing null-iccSmsIntMgr error path after it is unchanged.",
      "The association check is skipped when the stored destination address is an emergency number, because of the existing emergency short-circuit in TelephonyPermissions. This follows the existing semantics of the 4-argument helper.",
      "The destination address used for the emergency decision is read from the stored message (through the message URI), not taken from a caller-supplied argument.",
      "This commit does not touch other SmsController entry points. Existing tests in the index show that sendTextForSubscriber already had an association check. The material does not show the state of the remaining methods."
     ],
     "behavior_changes": [
      "Callers of sendStoredText / sendStoredMultipartText whose user is not associated with the subscription now get SmsManager.RESULT_USER_NOT_ALLOWED in their sentIntent(s), and the message is not sent.",
      "TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate may now be invoked for these two calls.",
      "Each call now runs an extra SMS provider query (loadTextAndAddress, under cleared calling identity) before the send, plus a TelephonyManager.isEmergencyNumber lookup when an address is found.",
      "IccSmsInterfaceManager.loadTextAndAddress is now public (internal, non-SDK surface).",
      "The 4-argument TelephonyPermissions.checkSubscriptionAssociatedWithUser explicitly accepts a null destAddr and treats it as non-emergency."
     ],
     "preconditions": [
      "The caller reaches SmsController.sendStoredText or sendStoredMultipartText with a message URI. The material does not show what permission or app-op enforcement happens inside IccSmsInterfaceManager.",
      "A configuration in which the target subId is not associated with the calling user (SubscriptionManager.isSubscriptionAssociatedWithUser returns false).",
      "The stored destination address is not an emergency number. Otherwise the check is skipped by design."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/opt/telephony 2c10fa519945, plus platform/frameworks/base 2e8edff2ab34, in android-security-14.0.0_r29. It adds the android.os.UserHandle and ContentResolver imports, inserts the gate in both methods, and adds getDestAddress and four tests. The resolver reports 7/8 lines of the patched region present.",
      "15": "Same fix: platform/frameworks/opt/telephony 3ed5e1fdd5da in android-security-15.0.0_r17. The logic is identical; only the ContentResolver import is added in the diff, and test file offsets differ. The resolver reports 7/8 lines present.",
      "16": "Adapted fix: platform/frameworks/opt/telephony b77a21c16113 in android-security-16.0.0_r8. callingUser already existed in both methods, so only `int uid` is hoisted and reused in the SecurityException message. The gate is inserted after the package check, and the downstream IccSmsInterfaceManager calls pass callingUser.getIdentifier(). The tests use 7-argument signatures. The commit also adds an extra .txt file, SmsController_fixed_methods.txt, containing copies of the two patched methods. The resolver reports 7/8 lines present.",
      "17": "Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 6/8 lines of the patched region are present, and the material identifies no commit."
     },
     "backport_notes": [
      "Apply the frameworks/base TelephonyPermissions change together with the opt/telephony change, because the new gate can pass a null destAddr and the base commit is what adds the explicit null guard.",
      "On trees where SmsController already declares callingUser locals (as in Android 16), follow the b77a21c16113 shape to avoid duplicate declarations. The SmsController_fixed_methods.txt file in the 16 commit is not needed."
     ],
     "confidence": "high",
     "unknowns": [
      "Which permission or app-op checks IccSmsInterfaceManager.sendStoredText/sendStoredMultipartText perform downstream, and whether they read the message address again during the send.",
      "How TelephonyManager.isEmergencyNumber behaves with a null argument in the pre-fix code, which determines whether the base change is required or only defensive.",
      "Whether SmsController methods other than sendTextForSubscriber and the two patched methods carry the association check at these security tags.",
      "Whether the frameworks/base nullable change has separate commits on Android 15 and 16; the material lists only the opt/telephony commits for those versions.",
      "Internal behavior of TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate and of the 3-argument checkSubscriptionAssociatedWithUser, including the rejectBadSubIdInteraction flag behavior seen in TelephonyPermissionsTest (per index search)."
     ],
     "meta": {
      "generated": "2026-10-03",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "15": {
   "counts": {
    "announced": 68,
    "certain": 66,
    "probable": 0,
    "not_published": 2,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2026-27280",
     "osv_id": "ASB-A-491543354",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Bulk update of external/dng_sdk to Adobe DNG SDK 1.7.1 build 2502. It hardens pointer arithmetic in dng_pixel_buffer.cpp OptimizeOrder (64-bit, overflow-checked rebase deltas, zero-count early return, checked step negation) and fixes src/dst tile-area confusion in dng_render_task::ProcessArea (mask buffers indexed by srcArea, size-equality requirement). It also adds a checked unary negation for dng_point.",
      "confidence": "medium",
      "backport_notes": [
       "The diff applies verbatim on 14, 15 and 16. It assumes a tree already at DNG SDK 1.7.1 build 2471 (dng_flags.h), even though README.version still said 1.4.0. It uses SafeInt64Mult, SafeInt32Sub, ConvertUint32ToInt32, ThrowOverflow and DNG_REQUIRE, which must exist in the tree.",
       "No fork comparison was provided, so there is no per-file fork verdict."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "a23ee6aeb0ee5a837b70507008b84bf75e7d7fc6",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2026-28590",
     "osv_id": "ASB-A-483126552",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds enforcement of the minimum encryption key size (7 octets) to the SMP pairing request/response handlers (LE and SMP-over-BR/EDR) in packages/modules/Bluetooth. Pairing is rejected with SMP_ENC_KEY_SIZE when the peer's advertised key size is smaller, and the minimum now comes from a new btm_sec_get_min_enc_key_size() helper.",
      "confidence": "high",
      "backport_notes": [
       "Before applying, find where MIN_KEY_SIZE is defined in the target tree: in system/stack/btm/btm_sec.cc on 15/16, in system/stack/btu/btu_hcif.cc on 14. Google removed the local definition in each branch when adding the header constant. If the tree already has a sysprop-based get_min_enc_key_size() (as on 16), export that instead of adding the version that returns a constant.",
       "Trees with the mockcify-generated mock_stack_btm_sec.{cc,h} need the struct in the header and the free function plus return_value in the .cc (as in this commit). Older trees (as on 14) only need a plain mocked function. The material includes no fork comparison."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "f161543f33dea43b35c371989dbbbf4ecc5cdd07",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28604",
     "osv_id": "ASB-A-499053500",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks adbd_auth_tls_handshake in packages/modules/adb/daemon/auth.cpp so the background TLS handshake thread no longer holds a raw atransport* but a weak-pointer callback id plus the connection object, with all transport liveness checks and state updates moved back onto the fdevent looper, and adds a 3-second socket timeout to the daemon-side FdConnection::DoTlsHandshake.",
      "confidence": "high",
      "backport_notes": [
       "The main fix relies on the existing transport_to_callback_arg / transport_from_callback_arg helpers and the 'transports' weak_ptr map in daemon/auth.cpp, plus fdevent_run_on_looper and atransport::weak(). The companion commit cadd481efbc8 documents and hardens those helpers (fdevent_check_looper, std::map::extract) and is the diff base of the main fix on 15, so apply it first.",
       "Android 17 uses the extra CHECK_NOT_LOOPER_THREAD() macro in transport.cpp and the older branches omit it, so do not copy the 17 diff verbatim onto 14-16 without confirming the macro exists."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/adb",
       "sha": "119250e4194443a2f16785c62647907a0d00d8dc",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/adb",
       "sha": "cadd481efbc8b501fac4fa002038de1ce32bd521",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/modules/adb"
     ]
    },
    {
     "cve": "CVE-2026-28639",
     "osv_id": "ASB-A-508389703",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (MIFARE Classic NDEF read), the else branch that logs a length-inflation attempt (android_errorWriteLog(0x534e4554, \"178725766\")) now also sets failed = true, so the NDEF read operation is marked as failed instead of continuing. Per the commit message, this prevents a heap out-of-bounds write.",
      "confidence": "medium",
      "backport_notes": [
       "Single-line, self-contained change: find the else branch containing android_errorWriteLog(0x534e4554, \"178725766\") in rw_mfc_handle_read_op and add failed = true. On Android 16+ the file is at packages/modules/Nfc/libnfc-nci instead of system/nfc.",
       "The Android 15 code context shows a restructured inflation check with failed = true in addition to the patched branch. Check both places in a fork instead of relying only on a hunk match."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "e22f0f7b91dd26089481e260b6262f368e3d7eac",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-28653",
     "osv_id": "ASB-A-508389055",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a pre-loop range check in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (src/nfc/tags/rw_t3t.cc). The check fails the command with NFC_STATUS_FAILED when the first block number plus the block count would exceed the uint16 range, preventing the block_id loop counter from wrapping and writing block descriptors past the end of the command buffer.",
      "confidence": "medium",
      "backport_notes": [
       "The patch is self-contained: 14 added lines in two hunks. It applies to src/nfc/tags/rw_t3t.cc in platform/system/nfc on 14/15 and to libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc on 16+.",
       "Consider the refined form found in the android-security-15.0.0_r17 tag, which calls GKI_freebuf(p_cmd_buf) before the NFC_STATUS_FAILED return, to avoid leaking the command buffer. Confirm the declared type of block_id before choosing between the `UINT16_MAX -` and `> 0x10000` conditions."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "089a1a4123dba4c9a8cab8ed11c6cbaf760b5981",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-33636",
     "osv_id": "ASB-A-496616027",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restricts the NEON palette-expansion loops in external/libpng (png_do_expand_palette_rgba8_neon and png_do_expand_palette_rgb8_neon) to full chunks only, so a partial final chunk is left to the scalar fallback instead of being read and written out of bounds, and undoes the *ddp pre-adjustment before handing the pointers back.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, single-file change with no shown dependency on other patches. It applies to source blob 92c7d6f9f on 14/15/16 and to 3068e9b6e (one-line offset) on 17. Check that the scalar fallback in pngrtran.c in the target tree expects the un-pre-adjusted dp, because this commit does not change pngrtran.c."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libpng",
       "sha": "fa7e33a0a0cc8e7a911ed27aea11063b265011e1",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libpng"
     ]
    },
    {
     "cve": "CVE-2026-45515",
     "osv_id": "ASB-A-503554165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the A2DP Opus decoder, the fix passes opus_decode() the capacity of decode_buf in samples per channel (A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t))) instead of its raw byte length. It also rejects packets whose TOC channel count is not 1 or 2, decodes each packet once instead of once per frame, and sizes the callback payload by the fixed stereo output channel count.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching one function. The code uses the A2DP_OPUS_CODEC_OUTPUT_CHS constant, and the new test calls libopus encoder APIs (opus_encoder_create/opus_encode).",
       "For trees that use the older logging macros (as on Android 14), use LOG_ERROR/LOG_INFO with %d format strings instead of log::error/log::info. Otherwise the decode logic is identical across 14 to 17."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "b837ddeb7125e8b2d30e6a27cf7b31e53e384152",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-45531",
     "osv_id": "ASB-A-508645975",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens fsck.exfat boot-sector validation in read_boot_region against integer overflow/underflow from attacker-controlled vol_length, clu_offset and clu_count fields, and makes DIV_ROUND_UP compute in 64 bits; a companion build change enables host builds so the new exfatprogs test image can be run.",
      "confidence": "medium",
      "backport_notes": [
       "The security-relevant content is ad273085b771 (fsck/fsck.c read_boot_region checks + libexfat.h DIV_ROUND_UP cast); the host_supported Android.bp commit appears to be test infrastructure only. On trees without the `if (verbose)` wrapping (as on Android 14), apply the checks with unconditional exfat_err as in 54cf500d4abd.",
       "Verify that EXFAT_MAX_NUM_CLUSTER is defined in include/libexfat.h on the target branch (present at line 31 on Android 15) and that the earlier EXFAT_SECTOR_SIZE range check exists so the `bd->size >> sect_size_bits` shift is bounded."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/exfatprogs",
       "sha": "2957a37c1d7fd1db3e49caecc682f100a4554311",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/external/exfatprogs",
       "sha": "ad273085b7710510a173ded82f46e1b1959afe74",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/external/exfatprogs"
     ]
    },
    {
     "cve": "CVE-2026-49879",
     "osv_id": "ASB-A-503544522",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the NFC Type 3 Tag reader/writer (rw_t3t.cc), the block loop variable in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd is widened from uint16_t to uint32_t, the loop upper bound and the preceding range check are computed in uint32_t against a 0x10000 limit, and the GKI command buffer is now freed when the range check fails.",
      "confidence": "high",
      "backport_notes": [
       "The patch assumes both functions already contain the earlier range check with android_errorWriteLog(0x534e4554, \"508389055\"). Trees without that prior change need the check and its error return added, not just modified. On Android 16+ the file path is libnfc-nci/src/nfc/tags/rw_t3t.cc under packages/modules/Nfc instead of system/nfc."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "78dd34a6a026d976158dc8e2852ad56fdadb641d",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-49882",
     "osv_id": "ASB-A-503553866",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (rw_mfc.cc), the fix saves ndef_length and ndef_start_pos before rw_nfc_decodeTlv runs on the first block read. It treats a decode failure, or any increase of ndef_length, as a failure and restores both saved values, so an inflated length from tag data no longer stays in the Mifare Classic control block and cannot bound the copy into p_ndef_buffer.",
      "confidence": "medium",
      "backport_notes": [
       "Each branch gets one self-contained commit. The hunks are the same across branches except for the log macro (DLOG_IF on 14, LOG(VERBOSE) on 15 and later) and the file path: src/nfc/tags/rw_mfc.cc in platform/system/nfc on 14/15, and libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc on 16/17, where libnfc-nci ships as part of the NFC mainline module (general platform knowledge).",
       "When porting to a tree that still has the old `saved_length` and else-branch form, remove both and keep the android_errorWriteLog call so the SafetyNet marker 178725766 is preserved."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "5dd24f8b4a6da6ec82be773b9f18ed6ac346251e",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-49884",
     "osv_id": "ASB-A-503557185",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (Mifare Classic NDEF read), the fix saves ndef_length and ndef_start_pos before rw_nfc_decodeTlv, treats either a decode failure or a larger ndef_length as failure, and rolls both fields back so an inflated length is not left in the control block.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk in rw_mfc_handle_read_op. On 14/15 the file is src/nfc/tags/rw_mfc.cc in platform/system/nfc; on 16/17 it is libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc. Adjust only the log macro (DLOG_IF vs LOG(VERBOSE)) to match the branch.",
       "Remove the old saved_length >= ndef_length check and its else branch in the same change that adds the rollback. If they are kept, failed would still route into the old else branch and log a second time."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/nfc",
       "sha": "5dd24f8b4a6da6ec82be773b9f18ed6ac346251e",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/nfc"
     ]
    },
    {
     "cve": "CVE-2026-49919",
     "osv_id": "ASB-A-503556301",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the unchecked `size = rows * pitch; FT_ALLOC(buf, size)` in tt_face_colr_blend_layer (src/sfnt/ttcolr.c) with FT_ALLOC_MULT(buf, rows, pitch), so the allocator detects an overflowing multiplication and the function returns an error instead of allocating an undersized buffer.",
      "confidence": "high",
      "backport_notes": [
       "Single-hunk change with no other changes in the commit. The same hunk was applied on the 14, 15, 16 and 17 trees. On trees where the function still references `size` after the allocation, check that the variable is still assigned, or remove its declaration."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "9f67316aa41d3f486291aeea377e102fcb8da57d",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-49921",
     "osv_id": "ASB-A-503554490",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the fixed 400-byte (SDP_MAX_ATTR_LEN) heap buffer used by sdpu_build_partial_attrib_entry with an allocation sized to the serialized attribute. Rejects attribute payloads larger than SDP_MAX_ATTR_LEN both when serving ServiceAttribute / ServiceSearchAttribute requests and when inserting attributes into the local SDP database. Rejects continuation offsets at or beyond the serialized length.",
      "confidence": "high",
      "backport_notes": [
       "Apply all three code changes together. The sdp_server.cc checks bound only the payload, so sdpu_build_partial_attrib_entry must also switch from the fixed SDP_MAX_ATTR_LEN allocation to osi_malloc(attr_len). For trees older than Android 15, use the Android 14 variant as the template: SDP_TRACE_* logging, check placed after sdpu_get_attrib_entry_len, removal of the partial-branch attr_len >= SDP_MAX_ATTR_LEN check, and osi_allocator mocks added to StackSdpUtilsTest."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "c738041c8ce83cca3f4ba643c25629231a5c16a9",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-49932",
     "osv_id": "ASB-A-513581684",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens PduParser.parseParts in frameworks/base telephony/common against malformed MMS multipart PDUs by adding a recursion depth cap of 30 for nested multipart/alternative parts, rejecting a declared part dataLength larger than the remaining stream bytes before allocating, and checking that a nested child body is non-null and non-empty before taking its first part.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The only production code touched is parseParts in telephony/common/com/google/android/mms/pdu/PduParser.java, and the hunks are identical from Android 14 through 17, so it should apply cleanly to any tree whose parseParts still matches the upstream shape. The new test file lives in tests/TelephonyCommonTests.",
       "Trees that carry their own copy of this parser (for example a forked mmslib PduParser) do not get the fix through this commit and need the same three checks applied by hand."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b86eef8c2ffeef7e2ca7d8aaf57594548f77e67e",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55256",
     "osv_id": "ASB-A-513581389",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds null checks in PduParser.parsePartHeaders so that a null return from parseWapString for the Content-Disposition token-text, the filename parameter, or the header/value pair checked against Content-Transfer-Encoding no longer causes a NullPointerException while parsing an MMS PDU.",
      "confidence": "high",
      "backport_notes": [
       "Two single-file commits, one per project (frameworks/base telephony/common PduParser.java and development samples/ApiDemos mmslib PduParser.java). The hunks are self-contained, and the same diff text appears on every shown branch (offsets 1726/1735/1769 in frameworks/base for 15 and 16, 1681/1690/1724 in development for 14, 15 and 17)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/development",
       "sha": "59bbdb0dddcf50ebff7b66176a4df89090247b4c",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "e2b80df1360a78e96451161c356edc22cd996e91",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/development",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48564",
     "osv_id": "ASB-A-403565650",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity (in frameworks/base and in the IntentResolver module) now resolves the forwarded intent's MIME type once. It passes that same string to the IPackageManager.canForwardTo check and to a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload, so the type used in the check and the type used in resolution can no longer differ. The fix also sanitizes the intent selector on the resolver-launch path, and in frameworks/base it identifies the privileged caller with getLaunchedFromPackage() instead of getCallingPackage().",
      "confidence": "high",
      "backport_notes": [
       "Apply the frameworks/base commit first. The IntentResolver module's InjectorImpl calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId), a hidden PackageManager overload that the base commit introduces. The canForward signature change also means every caller has to be updated (AbstractMultiProfilePagerAdapter / CrossProfileIntentsChecker and any OEM callers).",
       "Injector implementations, including test injectors, need the new resolveActivityAsUser overload. The security commit's IntentForwarderActivityTest assertions are inverted (assertNull on a captured Intent that must be non-null). Only the later 323651e81585/323c8b968a88 commit corrects them, so pick that commit too if you run the test."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bf9e58d7edb5b0a5a55baf9af47ff67a528109c3",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "dcb671e00d68972f33b370bff234934394adc21e",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "323651e815851726324f996fb880cc6c8d5a5bbd",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "577f0333c2c3490aa302e70b4e56c30c885d6da9",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "2772217bd3d6d39a8c13ea14e5df7d7af75ac680",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48565",
     "osv_id": "ASB-A-407763772",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the incoming intent's MIME type once and passes that fixed string through both the canForwardTo check and the target-user resolveActivityAsUser call (via new hidden PackageManager.resolveActivityAsUser(Intent, String, ...) overloads). It also uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-consent check, and sanitizes the intent selector (package/component/nested selector) everywhere sanitizeIntent is applied.",
      "confidence": "high",
      "backport_notes": [
       "Apply in order: bf9e58d7edb5 first (the PackageManager/ApplicationPackageManager overloads are required by IntentForwarderActivity and AbstractMultiProfilePagerAdapter in the same commit, and any out-of-tree PackageManager subclass or canForward caller must be updated to the new signatures). Then apply 323651e81585 (not security-relevant itself, but 577f0333c2c3 is based on its result) and 577f0333c2c3. On branches without the private-profile branch or singleTabOnly, follow the Android 14 variant 042b52a8551f.",
       "Per index search, packages/modules/IntentResolver has its own IntentForwarderActivity copy, which these frameworks/base commits do not touch; check whether it is used on the device."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bf9e58d7edb5b0a5a55baf9af47ff67a528109c3",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "323651e815851726324f996fb880cc6c8d5a5bbd",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "577f0333c2c3490aa302e70b4e56c30c885d6da9",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "2772217bd3d6d39a8c13ea14e5df7d7af75ac680",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48566",
     "osv_id": "ASB-A-397216638",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the incoming intent's MIME type once and passes that string both to the canForwardTo check and to a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload, so the type used for the cross-profile permission check is the same one used for the actual target resolution; it also uses getLaunchedFromPackage() for the privileged skip-confirmation check and sanitizes the received intent's selector before launching the resolver.",
      "confidence": "high",
      "backport_notes": [
       "Apply Change-Id I8453f25ed5ddd938718f4d2be4983c881778f281 first (it changes the canForward signature, so AbstractMultiProfilePagerAdapter must be updated in the same step), then optionally Iade781e0758c477ca888bd1541e0c53e2dee3761 which is test-only but fixes assertions that cannot pass as written. Forks with their own PackageManager subclasses or additional canForward callers need to add the new overload / pass a resolved type; per the diff the new PackageManager overloads are concrete (the ResolveInfoFlags one throws UnsupportedOperationException by default), so other subclasses compile unchanged but throw if the new overload is called on them.",
       "Trees that use the IntentResolver mainline module's own IntentForwarderActivity (packages/modules/IntentResolver, per index search) still have the ContentResolver-based canForward/canForwardInner; this frameworks/base commit does not touch that copy."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r12",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bf9e58d7edb5b0a5a55baf9af47ff67a528109c3",
       "tag": "android-security-15.0.0_r12",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "323651e815851726324f996fb880cc6c8d5a5bbd",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2026-0010",
     "osv_id": "ASB-A-379695596",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces an sprintf(\"%lu\", (unsigned long)fd) into a fixed 16-byte stack buffer with String8::format(\"%d\", fd) when BnDrmManagerService::onTransact stores the \"FileDescriptorKey\" value of an incoming DRM info request in drmserver.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk with no dependencies. The Android 14 and 16 commits have the same diff as the 15 one, so it should apply to any tree that still has the char buffer[16]/sprintf sequence in the \"FileDescriptorKey\" branch. String8::format is part of libutils (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r13",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "c8713a0652327cbf435b3318fa05cacdeabc3087",
       "tag": "android-security-15.0.0_r13",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 183,
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-0054",
     "osv_id": "ASB-A-467684755",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an onTransact-level caller check to SystemUI's WalletContextualLocationsService binder so that only privileged apps holding the android.app.role.SYSTEM_UI_INTELLIGENCE role can invoke IWalletContextualLocationsService methods; all other callers get a SecurityException.",
      "confidence": "high",
      "backport_notes": [
       "Each branch has one self-contained commit. The only adaptation was re-formatting the binder stub block on Android 14. STS tests are in a separate change, I4c59ae71f66035d2aeeb9b9e4dd13d7911e5fae5, and the fix does not depend on them."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r16",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r16",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3717e022bc2a79d2b900a4ac6bbfb0970d0f4638",
       "tag": "android-security-15.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 92,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0065",
     "osv_id": "ASB-A-464526458",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The \"process bound by foreground uid\" background activity launch (BAL) exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned (non-PiP) window, via a new ActivityTaskManagerService.hasActiveVisibleNotPinnedWindow predicate, while FGS-start checks keep using the original any-visible-window predicate.",
      "confidence": "high",
      "backport_notes": [
       "Apply 3d4a174f1def first. 18d845a1ee4b is a test-only follow-up that depends on the testBoundByForegroundFgs test added by the first commit. The fix relies on VisibleActivityProcessTracker.hasVisibleNotPinnedActivity(int) already existing (the diff does not add it), so check that it is present in the target tree.",
       "The condition around the exemption differs per branch (A14: no appSwitchState check; A15: flag-gated appSwitchState check; A16: BalCheckConfiguration with checkVisibility), so the areBackgroundActivityStartsAllowed hunk must be adapted by hand. The ATMS and WindowProcessController hunks have the same content across branches."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r16",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3d4a174f1defc6a5b16fa7542e64e8eb4df09239",
       "tag": "android-security-15.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "18d845a1ee4bebe7245c718cc27f0fff6d6b9ca5",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 92,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28581",
     "osv_id": "ASB-A-479515555",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Telecom's CallIntentProcessor.fixInitiatingUserIfNecessary now decides whether a managed (work) profile has a dialer by asking RoleManager for an enabled ROLE_DIALER holder in that profile, instead of checking whether any activity handles ACTION_DIAL, so calls from a work profile without a real dialer are correctly re-attributed to the parent user.",
      "confidence": "high",
      "backport_notes": [
       "Apply both commits in order: the second (Change-Id I5f004f94...) edits doesUserHaveDialerRoleHolder, which the first (Change-Id I1c10a6e0...) introduces. Per the second commit message, the first alone is insufficient on Android 14. On 14-style trees, adapt the surrounding fixInitiatingUserIfNecessary signature (no FeatureFlags, UserManager.get(context)) as the 14 commit did.",
       "The material includes no 'Fix in fork' comparison, so fork status could not be checked."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r16",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "34079b6596644e36284319273ac6e50244604948",
       "tag": "android-security-15.0.0_r16",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "ec63a79133e65ccbfbecf90fbac98a66690615b4",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 92,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/services/Telecomm"
     ]
    },
    {
     "cve": "CVE-2026-28582",
     "osv_id": "ASB-A-487522353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ConfirmDeviceCredentialActivity.onCreate now finishes immediately when it receives KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through any component other than the com.android.settings.ConfirmRemoteDeviceCredentialActivity alias. As a result, the permission checks tied to that alias can no longer be skipped by launching the underlying activity directly.",
      "confidence": "high",
      "backport_notes": [
       "One self-contained commit per branch. The only required source change is the constant plus the guard block placed right after the remoteValidation boolean in ConfirmDeviceCredentialActivity.onCreate. The test and shadow changes are not part of the Android 14 diff.",
       "The check is an exact string compare against the class name com.android.settings.ConfirmRemoteDeviceCredentialActivity, so a fork that renamed that alias needs to change the constant to match."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "839f927839657e48ade5d092a09483f62ac864bc",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28583",
     "osv_id": "ASB-A-486597445",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "validate_camera_metadata_structure in system/media camera_metadata.c now converts entry_capacity (an entry count) to bytes with sizeof(camera_metadata_buffer_entry_t), with an overflow guard, before checking that the entry region ends at or before data_start.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching one function in camera/src/camera_metadata.c; the same hunk applied without adaptation on 14, 15 and 16, so it should apply cleanly to any branch whose validate_camera_metadata_structure still computes entries_end = entries_start + entry_capacity.",
       "libcamera_metadata is a platform library built from system/media rather than a mainline module, but it is also vendor-available, so vendor processes such as camera HALs may link their own copy; check which copies a fork rebuilds (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/media",
       "sha": "9b4fe9dce95ba6ac4a776abd8c7bb48a62b5c815",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/media"
     ]
    },
    {
     "cve": "CVE-2026-28593",
     "osv_id": "ASB-A-388272624",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Live TV app's \"Send Feedback\" settings action now builds an explicit intent targeting com.google.android.feedback/com.google.android.feedback.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent, so the intent can no longer resolve to an arbitrary app that registered for that action.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained Java change with no visible dependencies. The pre- and post-fix blob hashes of the file are the same on 14 through 17, so the patch should apply cleanly. The fix hard-codes com.google.android.feedback, so forks that ship a non-Google feedback handler need to substitute their own explicit component."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "7e516e62a48d344ce625f3a172b3b81c3e110692",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28596",
     "osv_id": "ASB-A-480975165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds OutOfMemoryError to the multi-catch around the game mode config XML meta-data parsing in GameManagerService, so an oversized XML resource no longer raises an uncaught Error that crashes the process.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained 2-line change with no visible dependencies. It applies where the same `catch (NameNotFoundException | XmlPullParserException | IOException ex)` block exists in GameManagerService.java (only the line offset differs across 14/15/16)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "47898903e7c304c44130f2e9008d94b2a0abe53d",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28600",
     "osv_id": "ASB-A-490605007",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PaymentDefaultDialog.onCreate no longer reads a UserHandle from the Intent.EXTRA_USER extra; the user id passed to buildDialog is now always UserHandle.myUserId().",
      "confidence": "medium",
      "backport_notes": [
       "One commit with one hunk, and the same pre-image (index d333b3da6ac) on 15, 16 and 17. It should apply cleanly wherever PaymentDefaultDialog.onCreate still reads Intent.EXTRA_USER. Settings is a platform app, not a mainline module, so the fix ships with the platform build (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "1d0237d77a380c71d08474bdad5ac7889438a868",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28602",
     "osv_id": "ASB-A-466379087",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ClipboardService now reads and writes the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS secure setting explicitly for the given userId (getIntForUser/putIntForUser), so the clipboard access notification decision follows the setting of the user whose clipboard was accessed.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; it requires a userId in scope in the notification method (present on all four branches per the diffs) and Settings.Secure.getIntForUser/putIntForUser, which all four branch diffs use."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "993d9100be4ca4bbed95d3b5ef2b3bd90ef76fca",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28603",
     "osv_id": "ASB-A-491253353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, assertSafeToStartCustomActivity now removes all four URI grant flags from its intent copy and clears its data and clipData. Settings then starts the custom restrictions activity with startActivityForResult.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. The six added lines go right after the setComponent/setPackage lines in assertSafeToStartCustomActivity. The Robolectric test sets mPackageManager through ReflectionHelpers and calls new RestrictionsResultReceiver(\"com.example.app\", null, false). Adjust the test if the constructor differs in a fork."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "856f8ccca12e64bb7b14e038ff0bab538bc89d8f",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28606",
     "osv_id": "ASB-A-495543559",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Bluetooth AdapterService now removes the per-device CallerInfo entry from mBondAttemptCallerInfo whenever a bond state transition lands in any state other than BOND_BONDING, so caller information from a finished or failed bonding attempt is no longer kept.",
      "confidence": "medium",
      "backport_notes": [
       "The fix is a self-contained change to AdapterService.java: one static import plus a 7-line block (a blank line and the removal block) in the bond state transition handler, 8 insertions in total. It needs mBondAttemptCallerInfo and the CallerInfo type to already exist in the target tree. If the target carries the flagged form (as Android 17 did), apply the unflag change and remove the aconfig flag definition instead of re-adding the block."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "9b196f44770c831b722ca8deba72f3f31770b133",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28607",
     "osv_id": "ASB-A-489225275",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds caller permission checks (REQUEST_INSTALL_PACKAGES requested or INSTALL_PACKAGES granted) plus the hide-non-system-overlays window flag to UnarchiveErrorActivity.onCreate, factors the UID-to-package and requested-permission helpers into PackageUtil, and on Android 17 moves the PIA v2 UnarchiveLaunch forwarding behind those checks.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. The PackageUtil helpers must land together with the activity changes, because UnarchiveActivity's private copies are deleted. On branches without the PIA v2 UnarchiveLaunch forwarding (15, 16 as shown), the 17 reordering hunk does not apply. On forks that carry v2, make sure the isVersionTwoEnabled block sits after the permission gate in both activities."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a4b48c54049c7de7872a2e35be598f961259d007",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28609",
     "osv_id": "ASB-A-485377744",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "announced-not-published",
     "detail": "platform/frameworks/av",
     "coverage": {
      "n_ingested_tags": 18,
      "first_ingested_tag": "android-security-15.0.0_r1",
      "latest_ingested_tag": "android-security-15.0.0_r18",
      "window_base_tag": "android-15.0.0_r1",
      "window_start": "2024-09-03",
      "partial_tags": [],
      "projects": [
       {
        "project": "platform/frameworks/av",
        "fetched_ok_in_tags": 18,
        "ingested_tags": 18,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-28611",
     "osv_id": "ASB-A-497544808",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a caller-UID-to-package-name verification (new NfcPermissions.checkPackage backed by AppOpsManager.checkPackage) at the start of the NfcAdapterService binder methods enable, disable and setObserveMode, so a caller can no longer pass an arbitrary package name into NFC authorization decisions.",
      "confidence": "high",
      "backport_notes": [
       "Backport the unflagged Android 15 form (direct mNfcPermissions.checkPackage calls) rather than the Flags.checkPassedInPackage()-guarded form seen in the 15 release tree. Also port the NfcPermissions constructor change so mAppOpsManager is initialized, and update any unit tests that pass a null package name.",
       "If the target tree has updateDiscoveryTechnology, setReaderMode or indicateDataMigration taking a package name, consider mirroring the Android 16 change, which also guards those methods."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Nfc",
       "sha": "d914feee6a9b9f139c12d640b176fce77d055b36",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28613",
     "osv_id": "ASB-A-329233015",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In LiveTv's ChannelImpl.initAppLinkTypeAndIntent, the fix refuses to build a channel app-link Intent when the resolved target activity is exported and declares a permission, addressing intent redirection to permission-protected activities (per the commit title).",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching only ChannelImpl.initAppLinkTypeAndIntent. The same blob transition (5be1179d -> 62c06cc2) applies on 14 through 17, so it should cherry-pick cleanly onto trees that already have the isProtectedActivity check. Forks that changed or lack that check should insert the isPermissionProtected test after activityInfo is resolved and before mAppLinkIntent is assigned."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "481a7a588acd8a20af0cff8b0ed24111e5fe8843",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28614",
     "osv_id": "ASB-A-486385459",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an isCallerValid() gate to SystemUI's SlicePermissionActivity.onCreate. The slice permission dialog is now shown only when the activity was actually launched by the slice provider's package, SystemUI itself, or the \"android\" package. Before, the activity trusted the package name supplied in the intent extra.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single commit with no dependencies on other commits. The Java hunk applies to any SlicePermissionActivity that still has the verifyCallingPkg() call site. The Kotlin test depends on SysuiTestCase and the SystemUI test harness and can be dropped if the harness differs."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "971f1d1962953e8a6db607e50c36f0ca82d74aa3",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28617",
     "osv_id": "ASB-A-498792914",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix makes WifiNetworkSuggestionsManager clear the vendor data (setVendorData(Collections.emptyList())) on every suggested WifiConfiguration before the suggestion is processed, so app-supplied PersistableBundle vendor data no longer enters the suggestion path that the commit message ties to unbounded XmlUtil (de)serialization.",
      "confidence": "medium",
      "backport_notes": [
       "Single-hunk, self-contained change. The target tree needs WifiConfiguration.setVendorData and com.android.modules.utils.build.SdkLevel. The commit message names XmlUtil as the vulnerable code but this commit leaves it unchanged, so check whether the target branch needs any XmlUtil hardening of its own."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Wifi",
       "sha": "5dab8604e9c01c19fe5ab88b5b62f8359886122e",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2026-28626",
     "osv_id": "ASB-A-332883816",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SetupPassthroughActivity in the LiveTv app now uses getLaunchedFromPackage() (on SDK_INT >= 34) to check that the package that launched it is CommonConstants.BASE_PACKAGE before it forwards the embedded setup Intent. If the check fails, it logs a warning and finishes without forwarding.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained change to a single file, with the same blob index 2a4a556f..87a66a0e on all four branches, so it applies cleanly wherever SetupPassthroughActivity.java matches. Activity.getLaunchedFromPackage() is a public API from API 34, which matches the SDK_INT >= 34 gate. A backport to a platform below 34 would need a different source for the caller's identity (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "f23429d7cad608500e289ff4b80331f5effb0918",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 25c98c910675",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28631",
     "osv_id": "ASB-A-461500706",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now calls getWindow().setHideOverlayWindows(true) when it shows the mini resolver consent dialog, so overlay windows from other apps are hidden while the cross-profile redirection prompt is displayed; a companion commit refactors the activity's Injector so the test suite can run against it.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the one-line 'Set hide overlays on mini resolver' change first; it is the actual security fix. Put it right after setContentView(R.layout.miniresolver) in whichever method shows the mini resolver. The test commit is optional. If you take it, apply it afterwards: it needs HIDE_OVERLAY_WINDOWS added to core/tests/coretests/AndroidManifest.xml and getExecutorService()/isDeviceProvisioned() in every Injector implementation.",
       "The material includes no comparison against a fork, so the state of the patched files in a given fork is unknown."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "323651e815851726324f996fb880cc6c8d5a5bbd",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "8e66b48894f7bafc2061d9aa573713f30c63e602",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28633",
     "osv_id": "ASB-A-430284594",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In safe mode, VoiceInteractionManagerService no longer parses the XML recognition-service metadata of non-system (3p) recognizers in RecognitionServiceInfo.getAvailableServices, and no longer parses the metadata of the already-configured recognizer. This guards safe-mode boot against oversized metadata from a malicious app.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It depends only on the existing mSafeMode field in VoiceInteractionManagerService and on ApplicationInfo.FLAG_SYSTEM. The RecognitionServiceInfo.java hunk applies identically on all four majors (same blob 05d4b9305d30 -> 376ba571f790). Only the VoiceInteractionManagerService.java line offsets differ. Any out-of-tree caller of getAvailableServices(Context, int) must be updated for the new boolean parameter."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "df4d44965279227a1ffb3bc4dbe63889c2f32575",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28634",
     "osv_id": "ASB-A-471796626",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a per-user subscription association check to PhoneInterfaceManager.handleUssdRequest so a caller can only send USSD requests on a subscription associated with its own user, throwing SecurityException otherwise.",
      "confidence": "high",
      "backport_notes": [
       "The production change is a single self-contained 5-line insertion near the top of handleUssdRequest, placed before Binder.clearCallingIdentity(). It requires TelephonyPermissions.checkSubscriptionAssociatedWithUser (called with a Context, an int subId and a UserHandle) to be available on the target branch. The test changes differ per branch only in mock scaffolding and can be adapted separately."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "83d27ac3f32fd28f498c7b47239d4274c2de4915",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/services/Telephony"
     ]
    },
    {
     "cve": "CVE-2026-28636",
     "osv_id": "ASB-A-484148949",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "announced-not-published",
     "detail": "platform/packages/apps/DocumentsUI",
     "coverage": {
      "n_ingested_tags": 18,
      "first_ingested_tag": "android-security-15.0.0_r1",
      "latest_ingested_tag": "android-security-15.0.0_r18",
      "window_base_tag": "android-15.0.0_r1",
      "window_start": "2024-09-03",
      "partial_tags": [],
      "projects": [
       {
        "project": "platform/packages/apps/DocumentsUI",
        "fetched_ok_in_tags": 18,
        "ingested_tags": 18,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2026-28638",
     "osv_id": "ASB-A-470260357",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider's XMP redaction now treats an XML element as sensitive when any of its attributes (not only its tag name) is an EXIF tag in the redacted set, and IsoInterface's flattened box list now includes the 'XMP_' (BOX_XMP) box, so location data stored as XMP attributes in ISO media files is redacted for apps lacking ACCESS_MEDIA_LOCATION.",
      "confidence": "high",
      "backport_notes": [
       "The core change is the attribute check in XmpDataParser (or XmpInterface on trees like 14). The IsoInterface `case BOX_XMP:` addition is needed only on trees that already have the filtered flatten switch. Code that flattens every box (mRoots + ArrayDeque, as in Android 15 before this commit) already indexes XMP_ boxes. The 15 commit also restructures flattening and renames getBoxBytes(UUID)/getBoxRanges(UUID), so any other callers of those methods must be updated when taking that version."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "cb04f3531ff2a44250d190707ad41caa1642fc9f",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-28642",
     "osv_id": "ASB-A-460778875",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ActivityStarter now clears the ActivityRecord's mLaunchTaskBehind flag when the background activity launch (BAL) verdict blocks the start, so a launch that BAL blocks no longer keeps the launch-behind flag.",
      "confidence": "high",
      "backport_notes": [
       "Single-hunk change. Place it directly after the ActivityRecord.Builder(...).build() that creates `r` and before `mLastStartActivityRecord = r`. On trees without the BalVerdict object, use the integer BAL code comparison (`balCode == BAL_BLOCK`) as Android 14 does. Make sure the BAL result variable is already computed at that point in your fork."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d98cb1490fc3b3e304f786a680bfbe8235129db6",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28644",
     "osv_id": "ASB-A-471797575",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ActivityTaskManagerService.startNextMatchingActivity() no longer always forwards the original caller's launchedFromUid/launchedFromPackage/launchedFromFeatureId to the next resolved activity. It now forwards them only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID. Otherwise the calling identity is reset to the intermediary's own UID and package.",
      "confidence": "high",
      "backport_notes": [
       "The ATMS change is self-contained: it needs only UserHandle, Process and ActivityStarter in scope and the existing r.getUid()/r.packageName/r.launchedFromUid fields. On trees without setUserId(userId) in the chain (as on 14), end the chain at setActivityOptions(options) as Google did. The tests also need the ActivityBuilder.setIntent() addition in WindowTestsBase.",
       "No 'Fix in fork' section was provided in the material, so no per-file fork verdict can be given."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ba9c05f370430eeefd65c1b2c766891cc6248871",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28650",
     "osv_id": "ASB-A-493714520",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowState.setHiddenWhileSuspended now classifies the window by getBaseType() instead of mAttrs.type, so sub-windows whose top parent is a system alert window or toast are hidden when the owning app is suspended.",
      "confidence": "high",
      "backport_notes": [
       "A single two-line production change. Google applied the identical getBaseType() hunk on 14, 15, 16 and 17. The 15.0.0_r17 context shows getBaseType() already used by the adjacent setForceHideNonSystemOverlayWindowIfNeeded. Only the test helper differs: createWindow(parent, type, name) on 14/15 versus newWindowBuilder(name, type).setParent(parent).build() on 16/17."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8130b605781797665ea07c8532e2b6f262e791c0",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28655",
     "osv_id": "ASB-A-477278752",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "RemoteViews stops enabling Parcel.allowSquashing() during writeToParcel and drops the ReadWriteHelper trick that forced eager Bundle unparceling in ReflectionAction; on Android 15 this is paired with a re-landed change that serializes a single ApplicationInfoCache list at the root and per-node int ids instead of a full ApplicationInfo per nested RemoteViews.",
      "confidence": "medium",
      "backport_notes": [
       "On Android 15 apply 04faeb30ca94 before cddac11f862e (the second diff's base blob 0b23c63ad1ae is the output of the first).",
       "For Android 14-era code adjust the ReflectionAction field name (value vs mValue) and the writeToParcel signature (no intentsToIgnore parameter); the removed lines are otherwise identical."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "04faeb30ca94d0f27cd9d9f60849ae514470e5dc",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "cddac11f862ea44cdc5bf701e677cffa989b4a04",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28656",
     "osv_id": "ASB-A-463364901",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the AppOpsManager user-restriction toggling (OP_SYSTEM_ALERT_WINDOW / OP_TOAST_WINDOW on show/hide and resume/pause) used by the autofill Fill/Save/Dialog UIs and by the DeviceAdminAdd screens of Settings, Car Settings and TvSettings with the per-window Window#setHideOverlayWindows(true) / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS mechanism, to prevent non-system overlay windows from being shown on top of these sensitive windows.",
      "confidence": "high",
      "backport_notes": [
       "The four commits change separate projects and the diffs show no ordering dependency between them. Each relies on Window#setHideOverlayWindows or the SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS private flag being available on the target branch.",
       "When adapting frameworks/base, remove every OverlayControl reference (the constructor parameters of FillUi, AnchoredWindow, SaveUi and DialogFillUi, and the AutoFillUI field), because the class is deleted. The Android 14 commit shows how the FillUi signature differs on older branches."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "90ce24ef376087c5c3f74d028ed7066f81067dcd",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "084a3e353820e929405ae5aabbe9b389dc7e5b1d",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "cc183b2887ea50ca914ddfba427e6388825e520f",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "7a21141ea7ef0386d35d966363c6693605c991d9",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/Car/Settings",
      "platform/packages/apps/Settings",
      "platform/packages/apps/TvSettings",
      "platform/vendor/google_clockwork"
     ]
    },
    {
     "cve": "CVE-2026-28658",
     "osv_id": "ASB-A-507575871",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountsDb.findMetaAuthUid now derives the account type from a meta_key by stripping the fixed \"auth_uid_for_type:\" prefix with substring instead of splitting the key on \":\" and taking the second element, so account types that themselves contain \":\" are no longer truncated and mis-keyed.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching one file. The two hunks should apply cleanly to any branch that still has META_KEY_DELIMITER and the TextUtils.split call in findMetaAuthUid. If a fork removed or renamed META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX, compute the prefix length from whatever constant is used to write the meta key."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "38dbed092b9d46a017667ca5908bff19fc29d66a",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28660",
     "osv_id": "ASB-A-498588487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a PackageInstallerService.getAllSessions(userId, filterCallingUid) overload and makes LauncherAppsService.getAllSessions pass the real caller UID into it. SessionInfo objects returned through LauncherApps.getAllPackageInstallerSessions() are therefore generated and filtered for the actual caller instead of for system_server.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. When backporting, keep the two-argument overload package-private, and pass the UID that LauncherAppsService captured before Binder.clearCallingIdentity(). If a fork has added other internal callers of PackageInstallerService session-info methods under cleared identity, apply the same filterCallingUid pattern there."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "498791aa27ab2289493dbaa18a38b0c6939d8cfa",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28664",
     "osv_id": "ASB-A-489675413",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "RuntimeImage::WriteImageToDisk in ART now calls fchmod() to set the runtime-generated app image (.art) file to 0444 right after creating it, and fails image generation if that cannot be done, so the file is no longer left writable on disk.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk: add <sys/stat.h> and insert the fchmod block right after the image_file creation failure check in WriteImageToDisk. Google applied the same hunk on 14, 15, 16 and 17. Context differs slightly on 14. ART is a mainline module (com.android.art), so the fix may also arrive through a module update rather than a platform patch (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/art",
       "sha": "19665de3fb4d2f1baf013b5a050973cf051d2b49",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2026-28668",
     "osv_id": "ASB-A-501541998",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that a realloc rejected by the android allocation limit returns nullptr without freeing the caller's still-valid original block.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with a two-line functional change. The malloc_limit.cpp hunk text is the same on all four majors (only line offsets differ), and the test hunk differs only in offset. The material shows no dependencies."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/bionic",
       "sha": "10a1e2ce6e59ea5025b906da220a6f685ba784d1",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/bionic"
     ]
    },
    {
     "cve": "CVE-2026-28671",
     "osv_id": "ASB-A-395640294",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the file-movement path, MediaProvider.update now prunes a stale MediaStore row (one whose _data matches the recomputed destination path but has no file on disk) before renaming, or requires write permission on that row if a file exists. If a UNIQUE(_data) conflict on the rename destination still occurs during the DB update, it deletes the conflicting row and retries in the same transaction. Another app's pre-inserted row can then no longer squat the name a pending file is being published to.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit with the same title on each branch. The Android 15 commit carries Change-Id/Merged-In Ica7cec90bb2d1870ce8902fa3d3e9899d59a743b. The diff adds no imports, so Objects, BaseColumns, FileNotFoundException, queryForSingleItem, enforceCallingPermission and clear/restoreLocalCallingIdentity must already be available on the target. Also confirm that DatabaseHelper.runWithTransaction tolerates the nested call that deleteEntryForPath makes.",
       "MediaProvider is also shipped as a mainline module, so devices may receive this change through a module update independent of the platform security tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "9e42f05c7c32648546f66f849a5daecd5479bfa3",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45517",
     "osv_id": "ASB-A-471796999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds structural checks (scheme, authority, picker path pattern) and a current-user-id check to caller-supplied preselection URIs in PickerUriResolver.processUrisForSelection, ahead of the per-URI permission check, so malformed, foreign-authority or cross-user URIs are dropped before their ids reach the picker query.",
      "confidence": "high",
      "backport_notes": [
       "The fix depends on LocalUriMatcher.matchUri(Uri, boolean allowHidden) and the PICKER_ID / PICKER_GET_CONTENT_ID constants, which the 15 commit uses without adding imports. Add PICKER_TRANSCODED_ID only if that constant exists on the target branch. If the target branch has the photopicker v2 MediaQueryForPreSelection class, port the 16/17 variant, which also patches that class and makes getUserId public.",
       "MediaProvider ships as a mainline module, so device fixes may arrive via module update rather than the platform security tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "1b032be5997826769f23c635ee546b32c87fb3af",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45518",
     "osv_id": "ASB-A-393377017",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an OPTION_CHECK_BRACKETS mode to android.database.sqlite.SQLiteTokenizer that rejects SQL fragments with unbalanced parentheses, and makes ContactsProvider's SelectionBuilder.addClause() run every clause through it so a caller-supplied selection cannot close the wrapping parentheses and escape the AND-concatenated restriction clauses.",
      "confidence": "high",
      "backport_notes": [
       "Two-repo dependency: the ContactsProvider change compiles only once frameworks/base provides SQLiteTokenizer.OPTION_CHECK_BRACKETS and the @Nullable Consumer overload; land the frameworks/base commit first, including the '&=' to '&' correction, otherwise the new flag is silently cleared during the scan.",
       "SQLiteTokenizer is in the platform framework (not an updatable module), so both commits must ship in the same platform build (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e93df405837f8bc9e7b056dcbbbeaa118ea1f210",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "b8e00892122ce50c6a6caa3ed223ece25a3cdb9e",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/providers/ContactsProvider"
     ]
    },
    {
     "cve": "CVE-2026-45519",
     "osv_id": "ASB-A-471797062",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds structural, scheme, authority and user-ID validation of caller-supplied photopicker preselection URIs (EXTRA_PICKER_PRE_SELECTION_URIS) in PickerUriResolver.processUrisForSelection before they are used to build the picker database query.",
      "confidence": "medium",
      "backport_notes": [
       "The fix depends on LocalUriMatcher and its PICKER_ID / PICKER_GET_CONTENT_ID constants (the PICKER_ID static import is visible at the 15 tag) and on the existing getUserId(Uri) helper. If the target tree contains photopicker/v2/model/MediaQueryForPreSelection.java, port the 16/17 variant instead, which also makes getUserId public and accepts PICKER_TRANSCODED_ID.",
       "Keep isValidPickerUri ahead of the getUserId check, so malformed URIs are rejected before the user segment is parsed. Verify that the surrounding catch handles any parse failure."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "1b032be5997826769f23c635ee546b32c87fb3af",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45520",
     "osv_id": "ASB-A-490286986",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricsSettingsBase now accepts the EXTRA_USER_ID and gatekeeper password handle intent extras only when the hosting SettingsActivity was launched by the Settings package itself; external launches fall back to the current user and ignore any supplied gatekeeper handle.",
      "confidence": "high",
      "backport_notes": [
       "Single commit; requires SettingsActivity.getInitialCallingPackage() and the test shadow ShadowPasswordUtils (setCallingAppPackageName/reset) to exist in the target branch. Android 14 needed an extra 'import android.app.Activity;'."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "783b7806995f237aac60c84f86d565decd7780ba",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-45521",
     "osv_id": "ASB-A-491748298",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid(), and AppFuseBridge.openFile rejects the request unless the caller UID equals the uid recorded in the MountScope for the requested mountId. The rejection is a FuseUnavailableMountException, which the caller turns into a null return.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. AppFuseBridge.java has the same pre- and post-fix blob hashes on 14 through 17, so that hunk should apply cleanly wherever AppFuseBridge.openFile exists. On a fork, grep for any other caller of AppFuseBridge.openFile(int,int,int), because the signature changes. Such callers must now pass a uid explicitly, and the open fails unless that uid equals the scope's recorded uid."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bca099fcb293c247ce616228bb9b0eb8b1c17723",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45525",
     "osv_id": "ASB-A-470265726",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes MediaProvider's ISO/MP4 metadata parser keep going when an XMP box exceeds the in-memory size limit and flags the whole unreadable XMP box range for redaction, instead of aborting box parsing and returning no XMP redaction ranges.",
      "confidence": "high",
      "backport_notes": [
       "The 15+ patch assumes IsoInterface.allocateBuffer (null when over the limit), getBoxBytesForXmpUuid/getBoxRangesForXmpUuid, and the XmpDataParser/XmpData split. Trees structured like Android 14 (XmpInterface.process, reading every UUID box) need the 14-style variant that adds the size constant and the null-buffer handling in XmpInterface.",
       "The fix relies on the Box still being added to the parsed box list when box.data is null so that getBoxRanges returns its range; the new test asserts exactly this, so run testContainer_IsoRedactionRanges_OversizedXmp after backporting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "ec0834680bfe8f35280e4401262b7d4aadff4959",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45527",
     "osv_id": "ASB-A-503550257",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the MP4/HEIF extractor, convertCleanApertureToRect() now checks the crop rectangle bounds with int64_t arithmetic before computing right/bottom, so crafted CleanAperture values can no longer trigger a signed int32 overflow (IntSan abort) in media.extractor.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The pre-image of HeifCleanAperture.cpp (blob f0a086703e) is identical on 15, 16 and 17, so the patch applies as-is wherever that file exists. The fix relies on the existing imageW/imageH > INT32_MAX rejection earlier in the function; keep that check when porting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "c92a92203957a50501813c7d0272fbfa24c2dd8e",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-45528",
     "osv_id": "ASB-A-504573577",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp, passing the target package name and uid. Before, it called PendingIntent.getActivity on a package context created from the system server's own context while the calling identity was cleared.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, single-function change. The added block is identical across 14 to 17; only the removed lines differ (createPackageContext on 14/15, createPackageContextAsUser on 16/17). The backport needs ActivityManagerInternal.getPendingIntentActivityAsApp(int, Intent, int, Bundle, String, int) in the branch. Google's single-file commits call it on all four majors, so it already exists there.",
       "If a fork customized the Intent or ActivityOptions in this function, keep those changes and replace only the PendingIntent.getActivity(targetAppContext, ...) call and the clearCallingIdentity/createPackageContext wrapper around it."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8c8709a5ad8c88e71da1496696649282d8ab3b40",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45529",
     "osv_id": "ASB-A-418776187",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsSmsProvider.query now applies the per-user subscription-ID filter (selectionBySubIds) to the simple conversations, conversation recipients/subject, search and searchSuggest URIs, and returns an empty cursor when the calling user has no associated subscriptions.",
      "confidence": "high",
      "backport_notes": [
       "This is a single commit. It requires query() to already compute selectionBySubIds and emptyCursor earlier in the method, and the test harness to mock SubscriptionManager.getSubscriptionInfoListAssociatedWithUser. On trees with the 17-style getTextSearchQuery signature (restricted RCS / OTP filter), add selectionBySubIds as the last parameter and place the clauses after the existing ones, as in 1fab99e951d5."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "4fab244beaa38c9cfe76b9ea9fddd1166907d530",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2026-49895",
     "osv_id": "ASB-A-503551046",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Raises the minimum EHT Operation element length constant in src/common/ieee802_11_defs.h from 1 to 5, so that the length check guarding access to the EHT Operation Information field accounts for the fixed fields that precede it.",
      "confidence": "high",
      "backport_notes": [
       "Constant change in the header. The only adaptation is the macro name (EHT_OPERATION_IE_MIN_LEN on 14/15/16, EHT_OPERATION_ELEM_MIN_LEN on 17). Confirm that the target tree's get_eht_operation_channel_width() actually checks the element length against this macro; otherwise the header change alone has no effect."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "234d284145fc3ccabbc64cdabdde2e600306fe6a",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/wpa_supplicant_8"
     ]
    },
    {
     "cve": "CVE-2026-49913",
     "osv_id": "ASB-A-505452999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now checks the mmap() result for the client-supplied source heap against MAP_FAILED instead of NULL. It also adds a ScopeGuard that calls munmap() on the mapping on every exit path after the check.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that needs <android-base/scopeguard.h> (make_scope_guard). The diff has the same hunk on 14, 15 and 16 and a one-line offset on 17. Forks with a customized DescramblerImpl::descramble should find the `srcPtr == NULL` check after the mmap() and make sure no other mmap() result in the function is compared against NULL."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/interfaces",
       "sha": "9bdc5c54c702849d689a1a00bdee128ecc52bcf5",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/interfaces"
     ]
    },
    {
     "cve": "CVE-2026-55290",
     "osv_id": "ASB-A-499091487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ResStringPool::setTo in libandroidfw now rejects string pool chunks whose stringsStart or stylesStart exceed the chunk size regardless of stringCount, and re-derives the styleCount bounds check so the style offset array (styleCount * 4 bytes) must fit both within the data size and before stringsStart, with an overflow guard against SIZE_MAX.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit touching only ResStringPool::setTo and its tests, with identical hunks on 14 through 17. The deletion hunk expects the earlier `numeric_limits<...>::max() / sizeof(uint32_t)` and `styleOffsetsStart` checks to be present. Trees without them will need manual adaptation."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ed4eedc690502212b6d340977f13b155fbabe030",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55294",
     "osv_id": "ASB-A-505000651",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix changes ihevcd_get_tu_data_size() in libhevc's decoder to size the tu_map region with num_tu (luma plus chroma TUs) instead of num_luma_tu, so the TU data buffer is large enough for what the initialization code writes into it.",
      "confidence": "high",
      "backport_notes": [
       "This is a single, self-contained one-line change in ihevcd_get_tu_data_size(). It should apply cleanly wherever the function still computes num_luma_tu, num_chroma_tu and num_tu as shown. Forks that changed the TU buffer layout should check that their tu_map consumer uses num_tu entries before adopting the same sizing."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libhevc",
       "sha": "c43bbf882f97d98f5680406069f131bc518d2d06",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libhevc"
     ]
    },
    {
     "cve": "CVE-2026-58822",
     "osv_id": "ASB-A-506109174",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds early bitmap-size guards to FreeType's LCD rasterizer functions in src/smooth/ftsmooth.c: ft_smooth_raster_lcd rejects width/3 > 0x7FFF and ft_smooth_raster_lcdv rejects rows/3 > 0x7FFF, both by returning Raster_Overflow. This prevents the short truncation of span x-coordinates that led to a heap underflow write.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that touches only src/smooth/ftsmooth.c. The two 3-line guards go at the top of ft_smooth_raster_lcd and ft_smooth_raster_lcdv, right after the local declarations. Google applied the same hunks to Android 14, 15, 16 and 17, with only the line offsets differing."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "fc6e207660f93997982b7d8da03cdf4a7180f585",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-58839",
     "osv_id": "ASB-A-466091304",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Shrinks the stack-allocated read buffer in incfs MountRegistry's forEachLine() from 128 KiB to 32 KiB and adds a compile-time check that the caller's declared maximum line length fits in it, to avoid overrunning the thread stack when parsing mount info.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit touching only incfs/MountRegistry.cpp; the identical diff was applied to 14, 15 and 16, so it should apply where forEachLine() still has the `static constexpr auto kBufSize = 128 * 1024; char buffer[kBufSize];` form. Requires a compiler supporting `__attribute__((uninitialized))` (clang) (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/incremental_delivery",
       "sha": "877702e09486aa35203d62e597f7b76d8472a77f",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/incremental_delivery"
     ]
    },
    {
     "cve": "CVE-2026-58874",
     "osv_id": "ASB-A-459460951",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds the missing subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony), and makes TelephonyPermissions.checkSubscriptionAssociatedWithUser tolerate a null destination address (frameworks/base).",
      "confidence": "high",
      "backport_notes": [
       "Take the opt/telephony and frameworks/base commits together, because the new SmsController code can pass a null destAddr to the 4-arg check. Check that the target branch has the 4-arg TelephonyPermissions.checkSubscriptionAssociatedWithUser overload, TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate and SmsManager.RESULT_USER_NOT_ALLOWED.",
       "On branches where IccSmsInterfaceManager.sendStoredText takes a callingUserId (the Android 16 shape), follow the 16 variant. Drop the stray SmsController_fixed_methods.txt file if cherry-picking from 16."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-15.0.0_r17",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d09bfc5ee8b44ebc99a79ef102ec0ed04e7b137b",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "3ed5e1fdd5da559697a411af035e655e3da3db6a",
       "tag": "android-security-15.0.0_r17",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/frameworks/opt/telephony"
     ]
    }
   ],
   "tags": [
    "android-security-15.0.0_r17"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "503553199"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "864123e86fc9da9f33434ef2a0071b5f9ecbcb71",
       "subject": "Fix race conditions in CryptoHal plugin usage",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "503541238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "3d1f8cef9a4293786a6e340084f15048d2e1e233",
       "subject": "Fix MediaBuffer size-inflation off-by-32 bug",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "505827492"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "98e472f24d54c31466c902e8a85f2defab969f48",
       "subject": "Fix OOB in DynamicsProcessing MBC band processing",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "396471524",
      "482172329"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "2d627fe89937d56a47db5b6c5e1adc38edbb6136",
       "subject": "[media] Fix heap-buffer-overflow in AudioAttributes unmarshalling",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "485020640"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "f19372384733df7c8acca90d5a31e8dd813757f2",
       "subject": "[media] Fix heap out-of-bounds write in MatroskaSource::read",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "525133623"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "35a55a5231456acadbd8c1ccfa0572186a35962d",
       "subject": "[24Q3] Mitigate BAL bypass via Companion Device Manager",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "472955222"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "78ff07aa12b887fe3bce7647a8f334234b28044f",
       "subject": "Sanitize labels in GrantCredentialsPermissionActivity",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "492093013"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8bbb435c9b251e91fcc46883f956f61b39e68e25",
       "subject": "Truncate long device admin descriptions",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "f9b731471afb2f6aaad72ab3bce334dc3bab13a1",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "8bdb3c884b6d4fbf860d3c5631fe61d86c3a730f",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "540f4ec31ceee463d26c98ed9cf8658eac27661c",
       "subject": "Use loadDescriptionSafe for DeviceAdmin descriptions.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "429417238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a6ff1f594051b6f1c60b2c127a5d0e19afbb4f7f",
       "subject": "Limit knownActivityEmbeddingCerts in manifest",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "505067993"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d71cc08e7ad0b472395475a3a5ce45e56b51bd2b",
       "subject": "Use byte count rather than string length for NMS component name check",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "472968114"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8c99389ef61e635898be35b403b3a336efc2b5ca",
       "subject": "SystemUi UsbDialog: fix label vulnerability",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "495409704"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a508a0a4dba6ec0825a8d77abeb64654e6f36824",
       "subject": "Fix path traversal and missing ownership check in LocaleManagerService",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "490083213"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "03c1d8e340693ba4e3111932315ba01b0dd946ad",
       "subject": "Add Task-level input sink to block cross-task tap pass-through",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      },
      {
       "project": "platform/frameworks/base",
       "sha": "6025638f24fa5fd8b7db5ec72e175b211d58b282",
       "subject": "Move input sink on top of the Activity when needed",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "498857374"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7cdef410dc98198e2afdb5d0fc6d6996cea6fdfd",
       "subject": "Check notification permission in getAuthToken(notifyOnAuthFailure).",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "451602444"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "df66a8f07d3b36f290693cfd9393c65dfb7578cc",
       "subject": "Add size validation and trimming for ShortcutInfo objects.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "478725173"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "d0fbd46bea7cf3af340fb22f70099988ed3f90e9",
       "subject": "Hide media on smartspace view when setting is disabled",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "501447778"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "34dc064114396cbde0632dbd6d1ce41cf19195d2",
       "subject": "[LocalImageResolver] Fix security check bypass",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "bulletin_history": [
      {
       "bulletin": "2026-09",
       "cve": "CVE-2026-28666",
       "severity": "Critical",
       "versions": [
        "14",
        "15",
        "16",
        "16-qpr2",
        "17"
       ],
       "status": "removed",
       "removed_seen": "2026-10-13",
       "removed_in_version": "1.3"
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "465446148"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6c6471fc471854bacb507c32edfb5343fc923e43",
       "subject": "Autofill SaveUi URL validation and FLAG_IMMUTABLE",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "382064697"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e7890bc1f9c067be1a351a2e90ef261aba98e57c",
       "subject": "CVE-2025-22442: set profile user restrictions earlier",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "466379326"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "fc3f36a616a3159740b111631573ac37123a3871",
       "subject": "Security bug fix: Prevent USB data changes for non-owner users during lockdown mode.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "463362836"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "aedf5f0fa70f29cf9bf54fed979dbecabb4af7f7",
       "subject": "RESTRICT AUTOMERGE [ExternalStorageProvider] Revoke URI permissions by path",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "4a47b19b9557a61b36a1b892459c4225418fc544",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions for specific document paths",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "5474b6871dd0f91bbee066566629b1e996d12905",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions on file path updates",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "262884935"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1fcacc7ecb6027cabcf8eba99f7fcc2021e6caa4",
       "subject": "Fix URI grant persistence bypass",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "475232460"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b94ba857121667a99a5fd74d093bd0b80ae6fb6c",
       "subject": "RESTRICT AUTOMERGE Filter ignorable Unicode codepoints in ExternalStorageProvider",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "517377278"
     ],
     "commits": [
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "64d94aa15e7b3cf98f3971c2df19e1bd91667c0a",
       "subject": "Fix ArrayIndexOutOfBoundsException in SIMRecords due to invalid EF_CFIS/EF_CFF",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "394083030"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "b2d58f33fb937dd2ccd100d927efb11b17b326b7",
       "subject": "Check getLaunchedFromPackage for CE Storage",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "482024712"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/ManagedProvisioning",
       "sha": "fc97ea693501d0c54fc673e3fa7f98afc38707a7",
       "subject": "Prevent Setup Wizard from starting provisioning if the device is already provisioned.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "471797085"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "0656c1bc9bc5025f3ae8b5b2c2f25179bf37fdba",
       "subject": "Fix confused deputy in Bluetooth settings dashboard",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "488559440"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "60b2a8568cabb10b2437e3f27cef4286606b66eb",
       "subject": "Sanitize package labels in SettingsApplication",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "465140520"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "e183838764458bc45b15669528c0e03f5ba52a3c",
       "subject": "GATT: Fix notifications sent to remote device without security checks",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "497102083",
      "497851840",
      "510038786"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "eb524b72724c4c8c1d507a8911e26fabe2f08fef",
       "subject": "[RESTRICT AUTOMERGE] Enforce incoming CTKD security requirements",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "413510838",
      "415689442"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "5a95b20635a969344a05420ca65a5719f4bd6630",
       "subject": "Handle concurrency issues in SDP record creation",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "444443479"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "3a93150039081cd8e4829cadf6dc6a9bba61652e",
       "subject": "Sanitize alternate intents.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "486763102"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "f71ae192b898f3810bba4d81f79b10a8b1dff642",
       "subject": "Restrict max size for Note.NOTE field",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "482947423"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "58e8f2d880d075301e61312232a284aa188d01bf",
       "subject": "RESTRICT AUTOMERGE Fix ZWSP path bypass in DownloadProvider",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "498162425"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "842086d94f370937d00cf15abc7d79ecde37d182",
       "subject": "RESTRICT AUTOMERGE Fix DownloadProvider completed download security bypass",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "490768196"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "1975d36d64a379b3761c43261195069024d72dab",
       "subject": "RESTRICT AUTOMERGE Fix path traversal vulnerability in DownloadStorageProvider",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "494725584"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "854197b25e1aaa4971a803ee9dbce50213be3e5e",
       "subject": "TelephonyProvider: Fix SQL injection in projection and sortOrder",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "503542266"
     ],
     "commits": [
      {
       "project": "platform/packages/services/BuiltInPrintService",
       "sha": "b82964c3f229fd3e01aec7ed7ef669f0714d4ce5",
       "subject": "Limit media-supported values",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "504612754"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "8adbb86845d0e6e53ff6f1b449ec64e0a34ff0bc",
       "subject": "Check caller in CSW#queryRemoteConnectionServices",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "508585261"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "34f4edcdb5b0cd10a7fd0ff81f6c658afb4395ce",
       "subject": "Fix MMI check bypass via leading whitespace in tel URI.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "356604577"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "0d4024bee09ca45c9d6feb01a4b6fd992076c865",
       "subject": "Resolve cross account user ringtone validation.",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "517377772"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "5b53784d2dee712d1371f38fdf28aabf26f7a022",
       "subject": "Fix StackOverflowError in SatelliteEntitlementController",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "490916793"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "40d1716b1076255e22e87126740b6acf5f1208f2",
       "subject": "[Telephony] Secure contact URI access in Call Forwarding",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    },
    {
     "bug_ids": [
      "477022787"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "f4699b1d48e94eadd829dfd479071a8b2be42a3c",
       "subject": "Prevent SDK Sandbox from bypassing isSystemApp check",
       "tag": "android-security-15.0.0_r17",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-15.0.0_r17"
     ],
     "tag": "android-security-15.0.0_r17"
    }
   ],
   "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-2026-27280": {
     "cve": "CVE-2026-27280",
     "summary": "Bulk update of external/dng_sdk to Adobe DNG SDK 1.7.1 build 2502. It hardens pointer arithmetic in dng_pixel_buffer.cpp OptimizeOrder (64-bit, overflow-checked rebase deltas, zero-count early return, checked step negation) and fixes src/dst tile-area confusion in dng_render_task::ProcessArea (mask buffers indexed by srcArea, size-equality requirement). It also adds a checked unary negation for dng_point.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK, pixel buffer copy ordering and render pipeline)",
     "weakness": "Integer overflow in pointer offset computation, and mismatched source/destination area indexing, leading to out-of-bounds buffer access. The diff shows both; the material does not say which one the CVE is attributed to.",
     "root_cause": "Two memory-unsafe patterns are visible in the pre-fix code. (1) source/dng_pixel_buffer.cpp OptimizeOrder: when a step was negative, it rebased sPtr/dPtr with \"(int32)(count - 1) * step * (int32)pixelSize\", a pure int32 product with no overflow check, and negated steps with \"-step\" (undefined for INT32_MIN). The sRange/dRange spread estimates were computed in uint32 and could wrap, and a zero count made \"count - 1\" wrap to 0xFFFFFFFF. A wrapped product yields a pointer far outside the pixel buffer, which the copy then uses. (2) source/dng_render.cpp dng_render_task::ProcessArea: the code located the mask plane in the per-tile mask/RGBTables temporary buffers with DirtyPixel(dstArea.t, dstArea.l, ...), iterated the background-mask rows from dstArea.t to dstArea.b, and used cols = dstArea.W(), while the rest of the row processing works in srcArea coordinates. The fix switches these to srcArea and adds a comment that results are translated to dstArea only at the final step, which suggests the temporary buffers map srcArea; dng_render_task carries an fSrcOffset. Nothing enforced that srcArea and dstArea have the same size. (3) source/dng_big_table.cpp PutCompressedStream computed \"dng_point(0,0) - TL()\". This is replaced by a new SafeInt32Sub-based unary negation, but the material does not show whether the old binary operator was checked.",
     "fix_analysis": [
      "source/dng_pixel_buffer.cpp OptimizeOrder: adds an early return when count0, count1 or count2 is zero, before any range or offset math is done.",
      "source/dng_pixel_buffer.cpp OptimizeOrder: sRange and dRange are now computed in uint64, with explicit casts of Abs_int32(step) and (count - 1).",
      "source/dng_pixel_buffer.cpp: a new static helper, ComputeRebaseDelta(count, step, pixelSize), computes (count-1)*step*pixelSize in int64 via SafeInt64Mult (the SDK's safe-arithmetic multiply). New OffsetPointer(ptr, int64 delta) overloads apply the delta to const and non-const pointers. All six rebase blocks (dStep0/1/2 and sStep0/1/2 branches) use these helpers instead of the raw int32 expression.",
      "source/dng_pixel_buffer.cpp: a new NegateStepChecked(step) calls ThrowOverflow(\"OptimizeOrder step negate\") when step == INT32_MIN. All twelve step negations use it, and <climits> is included.",
      "source/dng_render.cpp dng_render_task::ProcessArea: adds DNG_REQUIRE(srcArea.Size() == dstArea.Size(), \"area size mismatch\") and derives a single cols = srcArea.W() (plus colsSigned via ConvertUint32ToInt32), used throughout. This replaces all former srcCols uses and the dstArea.W()-based cols.",
      "source/dng_render.cpp dng_render_task::ProcessArea: both tempMaskBuffer.fData computations now use DirtyPixel(srcArea.t, srcArea.l, dstMaskPlane). The background-mask loop iterates rows over srcArea.t..srcArea.b and indexes ConstPixel_real32/DirtyPixel_real32 at srcArea.l. The per-row RGBTables buffer area is renamed srcRowArea.",
      "source/dng_render.cpp dng_render_task::ProcessArea: buffer.fPlaneStep now uses ConvertUint32ToInt32(buffer.fArea.W()) instead of a raw int32 cast. The ProfileGainTableMap selection and the exposureWeightGain computation are moved out of the row loop.",
      "source/dng_point.h: adds a unary operator-(const dng_point&), implemented with SafeInt32Sub(0, v/h), and a real64 counterpart. source/dng_big_table.cpp dng_image_table::PutCompressedStream now uses -fImage->Bounds().TL().",
      "Other changes in the same commit: dng_camera_profile.cpp SetFourColorBayer, SetProfileGainTableMap and SetMaskedRGBTables now call ClearFingerprint(). dng_negative AspectRatio is renamed BaseAspectRatio, with callers updated in dng_negative.cpp ConvertToProxy and dng_render.cpp Render. dng_utils.cpp adds a printf format attribute on qMacOS/qiPhone only. The version goes from build 2471 to 2502 in dng_flags.h, and README.version is updated."
     ],
     "completeness": [
      "OptimizeOrder: all six pointer-rebase blocks and all twelve step negations go through the new helpers, and the zero-count case is handled up front.",
      "ProcessArea: every dstArea-based access into the temporary mask/RGBTables buffers shown in the diff is switched to srcArea, and the size-equality requirement makes the shared cols value valid for both areas. The remaining dstArea use shown (DirtyPixel_real32 at dstArea.l with dstPlane for the final output) targets the destination buffer.",
      "The new checks rely on SDK error paths (ThrowOverflow, DNG_REQUIRE, the safe-arithmetic helpers) rather than clamping; the bodies of SafeInt64Mult and ConvertUint32ToInt32 are not shown.",
      "The unary operator- is used only at the one call site changed in dng_big_table.cpp; other places that compute dng_point(0,0) - p are not shown in the material.",
      "This is a vendor SDK drop (build 2471 to 2502), so the material does not isolate which hunk corresponds to the CVE."
     ],
     "behavior_changes": [
      "OptimizeOrder returns immediately (no reordering) when any count is zero.",
      "OptimizeOrder throws an overflow exception when a step equals INT32_MIN; the rebase delta is computed with an overflow-checked int64 multiply.",
      "dng_render_task::ProcessArea fails via DNG_REQUIRE when the source and destination tile areas differ in size, and converts the tile width with ConvertUint32ToInt32.",
      "Negating a dng_point now uses SafeInt32Sub.",
      "The camera profile fingerprint is cleared when SetFourColorBayer, SetProfileGainTableMap or SetMaskedRGBTables is called.",
      "The ProfileGainTableMap lookup and exposureWeightGain are computed once per ProcessArea call instead of once per row.",
      "The reported SDK build string changes from 1.7.1 (2471) to 1.7.1 (2502)."
     ],
     "preconditions": [
      "A DNG/raw image processed by dng_sdk. OptimizeOrder sits in dng_pixel_buffer.cpp, and dng_render_task::ProcessArea runs when a negative is rendered (dng_render::Render). The callers of OptimizeOrder are not shown in the material.",
      "The ProcessArea mask paths need a negative with RGBTables/mask data (fRGBTablesData, fNumTableTransformPlanes), and the area confusion matters when srcArea and dstArea coordinates differ.",
      "On Android, dng_sdk is used by Skia's SkRawCodec to decode DNG/raw images (general platform knowledge). The index matches in frameworks/base android_hardware_camera2_DngCreator.cpp use img_utils TiffWriter in the shown snippets and do not show a dng_sdk call path."
     ],
     "per_major": {
      "14": "same fix: commit 52ff589c0c55 (\"Update to DNG SDK 1.7.1 2502\") in android-security-14.0.0_r29, with the same diff as Android 15. 6/8 patched-region lines present at that tag.",
      "15": "same fix: commit a23ee6aeb0ee in android-security-15.0.0_r17, cherry-picked from internal changes 51fac39e7e2a and cd0255e40725 (Change-Id I98050384600e0f4ac77f4378e00eee9be4830aa7). 6/8 patched-region lines present.",
      "16": "same fix: commit c4ffcdb3285d in android-security-16.0.0_r8, same diff. 6/8 patched-region lines present.",
      "17": "not affected: the bulletin does not list Android 17 (the bulletin/OSV affected-versions field lists 17-next). At android-security-17.0.0_r1, 6/8 patched-region lines are present."
     },
     "backport_notes": [
      "The diff applies verbatim on 14, 15 and 16. It assumes a tree already at DNG SDK 1.7.1 build 2471 (dng_flags.h), even though README.version still said 1.4.0. It uses SafeInt64Mult, SafeInt32Sub, ConvertUint32ToInt32, ThrowOverflow and DNG_REQUIRE, which must exist in the tree.",
      "No fork comparison was provided, so there is no per-file fork verdict."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which hunk (OptimizeOrder integer overflow, ProcessArea area confusion, or dng_point negation) is the flaw tracked as CVE-2026-27280. The commit is a bulk SDK update and the bulletin only states Critical EoP.",
      "Whether the old binary dng_point operator- was already overflow-checked.",
      "The exact behavior of SafeInt64Mult (3-argument form) and of ConvertUint32ToInt32 on overflow.",
      "The callers of OptimizeOrder, and the exact conditions under which the srcArea and dstArea origins differ in ProcessArea.",
      "Whether the second internal cherry-pick (cd0255e407256883abd7a64dab2b925c2b1deff1) added changes beyond this diff.",
      "Why Android 17 is not listed by the bulletin while the affected-versions field includes 17-next, and whether 17.0.0_r1 contains the full fix."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28590": {
     "cve": "CVE-2026-28590",
     "summary": "Adds enforcement of the minimum encryption key size (7 octets) to the SMP pairing request/response handlers (LE and SMP-over-BR/EDR) in packages/modules/Bluetooth. Pairing is rejected with SMP_ENC_KEY_SIZE when the peer's advertised key size is smaller, and the minimum now comes from a new btm_sec_get_min_enc_key_size() helper.",
     "component": "platform/packages/modules/Bluetooth, system/stack (BTM security manager btm_sec.cc and SMP smp_act.cc)",
     "weakness": "Missing validation of a peer-supplied security parameter (encryption key size lower bound not enforced for LE pairing)",
     "root_cause": "In system/stack/smp/smp_act.cc, smp_proc_pair_cmd() parsed the peer's encryption key size field into p_cb->peer_enc_size but never compared it against the minimum allowed key size. The only SMP_ENC_KEY_SIZE failure path depended on p_cb->cert_failure == SMP_ENC_KEY_SIZE. smp_br_process_pairing_command() (SMP over BR/EDR) had no key size check either. According to the commit message, the minimum key size (MIN_KEY_SIZE = 7) was enforced only for BR/EDR. In btm_sec.cc that check lives in read_encryption_key_size_complete_after_encryption_change() and read_encryption_key_size_complete_after_key_refresh(). The commit message says the Core specification forbids key sizes below 7 octets, but the LE pairing path accepted any peer_enc_size.",
     "fix_analysis": [
      "system/stack/btm/btm_sec.h: adds constexpr int MIN_KEY_SIZE = 7, MIN_KEY_SIZE_DEFAULT = MIN_KEY_SIZE and MAX_KEY_SIZE = 16, and declares uint8_t btm_sec_get_min_enc_key_size(). On 15 only MIN_KEY_SIZE existed before (file-local in btm_sec.cc); the other two constants are new.",
      "system/stack/btm/btm_sec.cc: removes the file-local constexpr uint8_t MIN_KEY_SIZE = 7 and adds btm_sec_get_min_enc_key_size(), which returns a static uint8_t initialized to MIN_KEY_SIZE. On Android 15 this is a fixed 7 with no sysprop lookup.",
      "system/stack/btm/btm_sec.cc: read_encryption_key_size_complete_after_encryption_change() and read_encryption_key_size_complete_after_key_refresh() now compare key_size against btm_sec_get_min_enc_key_size() instead of the local constant. Behavior is unchanged: they still disconnect with HCI_ERR_HOST_REJECT_SECURITY via acl_disconnect_from_handle.",
      "system/stack/smp/smp_act.cc:smp_proc_pair_cmd(): after smp_command_has_invalid_length() and the STREAM_TO_UINT8 parsing of the pairing fields, the fix reads min_key_size = btm_sec_get_min_enc_key_size(). Pairing fails if p_cb->cert_failure == SMP_ENC_KEY_SIZE or p_cb->peer_enc_size < min_key_size. In that case it logs a warning and raises SMP_AUTH_CMPL_EVT with status SMP_ENC_KEY_SIZE via smp_sm_event(), before smp_command_has_invalid_parameters() runs.",
      "system/stack/smp/smp_act.cc:smp_br_process_pairing_command(): adds the same check for the BR/EDR SMP transport. If p_cb->peer_enc_size < min_key_size, it raises SMP_BR_AUTH_CMPL_EVT with status SMP_ENC_KEY_SIZE via smp_br_state_machine_event() and returns.",
      "system/test/mock/mock_stack_btm_sec.cc and .h: add a mockcify struct btm_sec_get_min_enc_key_size with return_value = 7, plus the mocked free function."
     ],
     "completeness": [
      "Covers both SMP pairing-command entry points shown in the diff: smp_proc_pair_cmd() (LE transport) and smp_br_process_pairing_command() (SMP over BR/EDR).",
      "The new check runs right after the pairing command fields are parsed, before smp_command_has_invalid_parameters().",
      "The new code enforces only the lower bound; the added lines do not check values above MAX_KEY_SIZE (16). The material does not show whether smp_command_has_invalid_parameters() checks the upper bound.",
      "The existing BR/EDR post-encryption checks in read_encryption_key_size_complete_after_encryption_change() and read_encryption_key_size_complete_after_key_refresh() are kept and only switched to the new helper. The diff adds no post-encryption check for LE links.",
      "On Android 15 the minimum is a compile-time 7 with no system property. On Android 16 the existing sysprop-based value is reused."
     ],
     "behavior_changes": [
      "LE pairing and SMP-over-BR/EDR pairing with a peer whose encryption key size field is below 7 now fail immediately with SMP status SMP_ENC_KEY_SIZE (SMP_AUTH_CMPL_EVT / SMP_BR_AUTH_CMPL_EVT) instead of proceeding.",
      "New log::warn line 'Encryption key size {} smaller than the minimum {}' in smp_act.cc.",
      "The cert_failure == SMP_ENC_KEY_SIZE path now shares the same branch and still reports SMP_ENC_KEY_SIZE.",
      "MIN_KEY_SIZE, MIN_KEY_SIZE_DEFAULT and MAX_KEY_SIZE become constexpr int constants visible to every file that includes stack/btm/btm_sec.h, and the new function btm_sec_get_min_enc_key_size() is added.",
      "BR/EDR key-size enforcement after encryption is unchanged in effect: the minimum is still 7 and the link is still disconnected with HCI_ERR_HOST_REJECT_SECURITY."
     ],
     "preconditions": [
      "Bluetooth enabled and an SMP pairing exchange with a remote device over LE in progress. smp_proc_pair_cmd() processes the pairing request or response from the peer.",
      "For the second path, an SMP-over-BR/EDR pairing command handled in smp_br_process_pairing_command().",
      "The peer-controlled field is the encryption key size octet of the pairing command (parsed into p_cb->peer_enc_size). In smp_proc_pair_cmd() the command must first pass smp_command_has_invalid_length()."
     ],
     "per_major": {
      "14": "adapted fix (3475f39c4c16 in android-security-14.0.0_r29). Same SMP checks in smp_proc_pair_cmd() and smp_br_process_pairing_command(), using SMP_TRACE_WARNING. Constants are added to btm_sec.h as constexpr uint8_t. On this branch the local MIN_KEY_SIZE lived in system/stack/btu/btu_hcif.cc, so it is removed there, the two read_encryption_key_size_complete_* functions in btu_hcif.cc switch to btm_sec_get_min_enc_key_size(), and an include of stack/btm/btm_sec.h is added. The helper returns a static MIN_KEY_SIZE_DEFAULT. The mock is a plain function returning MIN_KEY_SIZE_DEFAULT (no mockcify struct). The commit also has unrelated edits in system/gd/hci/controller.cc and a const-correctness change to btm_proc_sp_req_evt() in btm_sec.h.",
      "15": "this commit (f161543f33de in android-security-15.0.0_r17). MIN_KEY_SIZE moves from btm_sec.cc to btm_sec.h as constexpr int, alongside the new MIN_KEY_SIZE_DEFAULT and MAX_KEY_SIZE. Adds btm_sec_get_min_enc_key_size() returning a fixed 7, the SMP checks, and a mockcify mock.",
      "16": "adapted fix (8b61335e4984 in android-security-16.0.0_r8). The branch already had a static get_min_enc_key_size() in btm_sec.cc that clamps android::sysprop::bluetooth::Gap::min_key_size() to [MIN_KEY_SIZE, MAX_KEY_SIZE]. The fix exports it as btm_sec_get_min_enc_key_size(), moves the constants to btm_sec.h and adds the same SMP checks. The mock return_value is MIN_KEY_SIZE_DEFAULT. On 16 the minimum can therefore be configured through the sysprop.",
      "17": "not affected (not listed by the bulletin). 3/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Before applying, find where MIN_KEY_SIZE is defined in the target tree: in system/stack/btm/btm_sec.cc on 15/16, in system/stack/btu/btu_hcif.cc on 14. Google removed the local definition in each branch when adding the header constant. If the tree already has a sysprop-based get_min_enc_key_size() (as on 16), export that instead of adding the version that returns a constant.",
      "Trees with the mockcify-generated mock_stack_btm_sec.{cc,h} need the struct in the header and the free function plus return_value in the .cc (as in this commit). Older trees (as on 14) only need a plain mocked function. The material includes no fork comparison."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether smp_command_has_invalid_parameters() enforces the MAX_KEY_SIZE upper bound on peer_enc_size (not shown).",
      "What p_cb->cert_failure is used for (for example, test hooks); the material does not show it.",
      "How the final LE key size is negotiated from peer_enc_size and when the key is generated, relative to the new check.",
      "Why the bulletin rates this as a Critical EoP, and the concrete consequence of accepting a short LE key; the material only gives the spec-violation rationale."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28604": {
     "cve": "CVE-2026-28604",
     "summary": "Reworks adbd_auth_tls_handshake in packages/modules/adb/daemon/auth.cpp so the background TLS handshake thread no longer holds a raw atransport* but a weak-pointer callback id plus the connection object, with all transport liveness checks and state updates moved back onto the fdevent looper, and adds a 3-second socket timeout to the daemon-side FdConnection::DoTlsHandshake.",
     "component": "platform/packages/modules/adb, adbd daemon authentication / TLS transport (daemon/auth.cpp, transport.cpp)",
     "weakness": "Use-after-free (race between a detached handshake thread holding a raw atransport pointer and transport destruction on the fdevent thread)",
     "root_cause": "adbd_auth_tls_handshake (daemon/auth.cpp) spawned a detached std::thread that captured the raw pointer 't' to an atransport. The thread blocked in t->connection()->DoTlsHandshake(), which had no timeout, and afterwards dereferenced 't' (t->IsTcpDevice(), t->auth_key, adbd_wifi_secure_connect(t), adbd_auth_verified(t), adbd_notify_framework_connected_key(t), t->Kick()). Nothing kept the transport alive or checked that it still existed. If the transport was destroyed on the fdevent thread while the handshake was in flight (for example the peer socket closed), the detached thread used freed memory. The transport state mutations were also performed off the looper thread. In addition, the daemon-side handshake had no socket timeout, so a peer could hold the handshake thread indefinitely.",
     "fix_analysis": [
      "Commit cadd481efbc8 (daemon/auth.cpp) comes first: its diff goes 05f51801 -> 0be3f995, which is the base of the main fix. It documents the global 'transports' map (uint32_t id -> weak_ptr<atransport>) and the transport_to_callback_arg / transport_from_callback_arg helpers. It adds fdevent_check_looper() at the start of both helpers. It rewrites transport_from_callback_arg to use std::map::extract, so the entry is always removed on lookup (previously find, then erase only after a non-null get()). The commit message says CHECK_LOOPER_THREAD(), but the diff uses fdevent_check_looper().",
      "Commit 119250e41944, daemon/auth.cpp:adbd_auth_tls_handshake: on the fdevent thread it now calls transport_to_callback_arg(t) to register a weak_ptr and get an opaque id, and copies t->connection() into a local 'connection'. The detached thread captures only [arg, connection], runs connection->DoTlsHandshake(rsa_pkey, &auth_key) and stores the result in 'connected'. It then posts a lambda via fdevent_run_on_looper that calls transport_from_callback_arg(arg). If that returns nullptr, the result is dropped (with LOG(ERROR) if the handshake had succeeded). If !connected, it calls t->Kick(). Otherwise it runs the previous success handling: t->auth_key = auth_key and adbd_wifi_secure_connect(t) for TCP devices, else adbd_auth_verified(t) and adbd_notify_framework_connected_key(t), now annotated 'TODO(505788736): This code is most likely dead'. The handshake thread no longer touches any transport pointer.",
      "Commit 119250e41944, transport.cpp:FdConnection::DoTlsHandshake: split into two separate definitions under #if ADB_HOST / #else. The host (client role) variant keeps the same logic. The daemon (server role) variant sets SO_RCVTIMEO and SO_SNDTIMEO to 3 seconds on fd_ via adb_setsockopt before tls_->DoHandshake(). A setsockopt failure is logged with PLOG(ERROR) and the handshake continues. Both timeouts are reset to 0 right after the handshake returns, before the result is checked. On failure tls_ is reset and false is returned, as before."
     ],
     "completeness": [
      "Covers the daemon (adbd) path adbd_auth_tls_handshake: the background thread no longer holds any atransport pointer, and the liveness check and all state updates run on the looper thread through the existing weak_ptr map.",
      "The 3-second handshake timeout is applied only in the non-ADB_HOST (daemon, server role) DoTlsHandshake; the host client variant has no timeout.",
      "The detached thread still captures 'connection' (the result of t->connection()) and runs DoTlsHandshake on it, possibly after the transport has been destroyed. The material does not show the type or lifetime of that object, so whether this is safe depends on connection ownership that is not visible here.",
      "The host-side counterpart adb_auth_tls_handshake in client/auth.cpp (per index search) still captures raw 't' in a detached thread and calls t->Kick() after the handshake. These commits do not change it (host adb, not adbd).",
      "The fdevent_check_looper() assertions in the helpers are hardening; the main fix already calls both helpers on the looper thread."
     ],
     "behavior_changes": [
      "The daemon (server role) TLS handshake now runs with 3-second SO_RCVTIMEO/SO_SNDTIMEO socket timeouts, meant to stop a slow or malicious client from blocking the handshake thread indefinitely. Both timeouts are reset to 0 after the handshake.",
      "Handshake failure now kicks the transport from the fdevent looper (deferred) rather than directly from the handshake thread.",
      "A handshake that completes after the transport was destroyed is ignored, with LOG(ERROR) 'TLS handshake succeeded for deleted transport, ignoring'.",
      "transport_to_callback_arg / transport_from_callback_arg now call fdevent_check_looper(), which enforces use on the looper thread.",
      "The 'transports' map entry is always removed on lookup via std::map::extract."
     ],
     "preconditions": [
      "adbd (non-host build) must reach adbd_auth_tls_handshake, i.e. a transport is in the TLS authentication phase.",
      "The atransport must be destroyed while the background DoTlsHandshake is still running or has just finished (transport teardown on the fdevent thread racing the detached thread).",
      "The material does not show whether wireless debugging must be enabled or what network exposure is required. Both the Kick path and the success paths dereferenced the raw pointer."
     ],
     "per_major": {
      "14": "same fix: c7995e05cc7b in android-security-14.0.0_r29, identical daemon/auth.cpp and transport.cpp hunks (LOG(INFO) variant); resolved by bug_id.",
      "15": "same fix: 119250e41944 plus companion hardening cadd481efbc8 in android-security-15.0.0_r17; resolved by bug_id.",
      "16": "same fix: f487f473c82d in android-security-16.0.0_r8, identical except for the surrounding VLOG(AUTH) logging; resolved by bug_id.",
      "17": "adapted fix: 12700491ebb3 in android-security-17.0.0_r1, same logic plus CHECK_NOT_LOOPER_THREAD() at the top of both DoTlsHandshake variants; resolved by bug_id."
     },
     "backport_notes": [
      "The main fix relies on the existing transport_to_callback_arg / transport_from_callback_arg helpers and the 'transports' weak_ptr map in daemon/auth.cpp, plus fdevent_run_on_looper and atransport::weak(). The companion commit cadd481efbc8 documents and hardens those helpers (fdevent_check_looper, std::map::extract) and is the diff base of the main fix on 15, so apply it first.",
      "Android 17 uses the extra CHECK_NOT_LOOPER_THREAD() macro in transport.cpp and the older branches omit it, so do not copy the 17 diff verbatim onto 14-16 without confirming the macro exists."
     ],
     "confidence": "high",
     "unknowns": [
      "The type and ownership semantics of the value returned by t->connection() (whether it keeps the FdConnection alive after transport destruction) are not shown.",
      "The exact trigger for transport destruction during the handshake and the network or feature exposure required (e.g. wireless debugging state) are not in the material.",
      "How tls_->DoHandshake() behaves when a socket read or write hits the new 3-second timeout is not shown.",
      "Whether the companion commit cadd481efbc8 was also applied to the 14, 16 and 17 security branches is not shown (only the main fix commit is given for those)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28639": {
     "cve": "CVE-2026-28639",
     "summary": "In rw_mfc_handle_read_op (MIFARE Classic NDEF read), the else branch that logs a length-inflation attempt (android_errorWriteLog(0x534e4554, \"178725766\")) now also sets failed = true, so the NDEF read operation is marked as failed instead of continuing. Per the commit message, this prevents a heap out-of-bounds write.",
     "component": "platform/system/nfc (libnfc-nci), src/nfc/tags/rw_mfc.cc, MIFARE Classic (MFC) tag reader/writer. On Android 16/17 the same file is in platform/packages/modules/Nfc at libnfc-nci/src/nfc/tags/rw_mfc.cc.",
     "weakness": "Missing failure handling after a detected length inflation (the condition was logged but not acted on), which per the commit message could lead to a heap out-of-bounds write",
     "root_cause": "In rw_mfc_handle_read_op (RW_MFC_STATE_READ_NDEF), the branch reached on an NDEF length inflation attempt only emitted android_errorWriteLog(0x534e4554, \"178725766\") and did not set the local failed flag, so the read operation was not marked as failed and continued. Per the commit message, this could lead to a heap out-of-bounds write. The material does not show the exact write path or how the NDEF buffer is sized.",
     "fix_analysis": [
      "src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: in the else branch containing android_errorWriteLog(0x534e4554, \"178725766\"), add failed = true, so the operation is explicitly marked as failed (commit title: terminate the MFC NDEF read on a length inflation attempt).",
      "The same one-line change is in the Android 14, 15, 16 and 17 commits."
     ],
     "completeness": [
      "The fix covers the single length-inflation else branch shown in the hunk. It adds no new bounds check. It only sets the existing failed flag.",
      "The code after the hunk (if (p_mfc->work_offset >= p_mfc->ndef_length) ... else rw_mfc_readBlock(...)) is unchanged. How failed is consumed afterwards (completion/notification path) is not shown in the material.",
      "The code context provided for android-security-15.0.0_r17 shows a differently structured block (combined if (!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len) with roll back of ndef_length and ndef_start_pos and failed = true), while the diff hunk shows an else-branch structure. The resolver reports 2/2 patched lines present in every major."
     ],
     "behavior_changes": [
      "A MIFARE Classic tag that triggers the length-inflation branch now causes the NDEF read operation to be marked failed instead of continuing the read. The SafetyNet log line (0x534e4554, \"178725766\") is still emitted.",
      "No change for tags that do not reach that branch."
     ],
     "preconditions": [
      "libnfc-nci is handling a MIFARE Classic (MFC) tag NDEF read (p_mfc->state == RW_MFC_STATE_READ_NDEF).",
      "The tag response contents trigger the length-inflation branch. Per the shown code context, the related check compares an NDEF length re-decoded from the first read block (work_offset == 0) against the previously saved length.",
      "The index search found no callers of rw_mfc_handle_read_op outside the nfc component."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc e884267aee53 in android-security-14.0.0_r29, identical one-line failed = true in src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op (hunk at line ~1057).",
      "15": "same fix: platform/system/nfc e22f0f7b91dd in android-security-15.0.0_r17, one-line failed = true in src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op (hunk at line ~1054).",
      "16": "same fix: platform/packages/modules/Nfc ca73224fb2c2 in android-security-16.0.0_r8, same one-line change in libnfc-nci/src/nfc/tags/rw_mfc.cc (hunk at line ~1289).",
      "17": "same fix: platform/packages/modules/Nfc 9f4efdd67acb in android-security-17.0.0_r1, same one-line change in libnfc-nci/src/nfc/tags/rw_mfc.cc (hunk at line ~1378)."
     },
     "backport_notes": [
      "Single-line, self-contained change: find the else branch containing android_errorWriteLog(0x534e4554, \"178725766\") in rw_mfc_handle_read_op and add failed = true. On Android 16+ the file is at packages/modules/Nfc/libnfc-nci instead of system/nfc.",
      "The Android 15 code context shows a restructured inflation check with failed = true in addition to the patched branch. Check both places in a fork instead of relying only on a hunk match."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact pre-fix code structure around the patched else branch (the provided code context differs from the diff hunk context).",
      "How the failed flag is consumed after the shown region (error notification / state reset).",
      "How p_mfc->p_ndef_buffer is sized and where exactly the out-of-bounds write would occur.",
      "Whether the copy into p_ndef_buffer runs before or after the patched branch in the pre-fix code."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28653": {
     "cve": "CVE-2026-28653",
     "summary": "Adds a pre-loop range check in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (src/nfc/tags/rw_t3t.cc). The check fails the command with NFC_STATUS_FAILED when the first block number plus the block count would exceed the uint16 range, preventing the block_id loop counter from wrapping and writing block descriptors past the end of the command buffer.",
     "component": "platform/system/nfc (libnfc-nci, moved to platform/packages/modules/Nfc libnfc-nci/ on 16+), Type 3 Tag (T3T) reader/writer NDEF CHECK/UPDATE command builder",
     "weakness": "Integer wrap of a uint16 loop counter leading to an unbounded heap out-of-bounds write (missing bounds check on block range)",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd the block descriptor loop runs `for (block_id = first_block_...; block_id < (first_block_... + blocks_...); block_id++)`. Per the commit title, block_id is a uint16 loop counter. The upper bound sum can exceed 0xFFFF, presumably because it is evaluated in a wider type after integer promotion (the declarations are not shown). When it does, block_id wraps instead of reaching the bound, and the loop keeps emitting 2- or 3-byte block descriptors into the command buffer `p` via UINT8_TO_STREAM/UINT16_TO_STREAM. The commit message describes the result as an unbounded heap out-of-bounds write. For UPDATE, the block count is clamped against the tag-supplied p_cb->ndef_attrib.nbw, but before the fix there was no check that first_block + count stays within 16 bits.",
     "fix_analysis": [
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: after the UPDATE header is written and the timeout computed, and before the descriptor loop, adds `if (ndef_blocks_to_write > UINT16_MAX - first_block_to_write)`. On that condition it logs \"Heap out-of-bounds\", emits android_errorWriteLog(0x534e4554, \"508389055\") and returns NFC_STATUS_FAILED, so the loop is never entered when first_block_to_write + ndef_blocks_to_write would exceed 0xFFFF.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: adds the identical check `if (cur_blocks_to_read > UINT16_MAX - first_block_to_read)` before the CHECK descriptor loop, with the same logging and NFC_STATUS_FAILED return.",
      "The code at android-security-15.0.0_r17 differs from the reviewed diff (per-major facts: 7/8 lines of the patched region present). There, the UPDATE check reads `if ((uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000)`, calls GKI_freebuf(p_cmd_buf) before returning, and casts the loop bound as `(uint32_t)first_block_to_write + ndef_blocks_to_write`. This suggests a later change in the tag that frees the command buffer on the failure path and allows the boundary case first_block + count == 0x10000."
     ],
     "completeness": [
      "Both T3T NDEF paths that contain the block_id loop (UPDATE and CHECK) are covered. The fix does not change how first_block_* or the block counts are derived; it only rejects the out-of-range combination immediately before the loop.",
      "The reviewed commit returns NFC_STATUS_FAILED without freeing p_cmd_buf. The version in the android-security-15.0.0_r17 tag adds GKI_freebuf(p_cmd_buf) on that path, so the standalone commit appears to leak the buffer on the failure path.",
      "The tag's refined condition (`> 0x10000`) accepts first_block + count == 0x10000, which the reviewed diff rejects. Whether that boundary is safe depends on the declared type of block_id, which the material does not show."
     ],
     "behavior_changes": [
      "Building an NDEF UPDATE or CHECK command now fails with NFC_STATUS_FAILED when first block + block count exceeds the uint16 range, and emits an ERROR log plus an android_errorWriteLog SafetyNet event for bug 508389055. Per the commit message, such a command previously led to a heap out-of-bounds write.",
      "For T3T tags whose NDEF attribute values produce such a block range, the read or write operation is aborted."
     ],
     "preconditions": [
      "NFC is enabled and the device performs an NDEF read (CHECK) or write (UPDATE) against a Type 3 tag through the libnfc-nci RW T3T code.",
      "The tag's NDEF attribute information must yield a block range that crosses 0xFFFF. For example, p_cb->ndef_attrib.nbw clamps blocks_per_update. The derivation of first_block_to_write/first_block_to_read is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc bea12a1b3e25 in android-security-14.0.0_r29, the same two-hunk change to src/nfc/tags/rw_t3t.cc (line offsets differ). 7/8 lines of the patched region are present in the tag.",
      "15": "same fix: platform/system/nfc 089a1a4123db in android-security-15.0.0_r17. The tag's UPDATE path contains a refined form of the check: a uint32_t sum compared to 0x10000, GKI_freebuf(p_cmd_buf) on failure, and a uint32_t loop bound.",
      "16": "same fix: platform/packages/modules/Nfc 4648c160b825 in android-security-16.0.0_r8, in libnfc-nci/src/nfc/tags/rw_t3t.cc, with identical hunks.",
      "17": "same fix: platform/packages/modules/Nfc e55c93242c56 in android-security-17.0.0_r1, in libnfc-nci/src/nfc/tags/rw_t3t.cc, with identical hunks."
     },
     "backport_notes": [
      "The patch is self-contained: 14 added lines in two hunks. It applies to src/nfc/tags/rw_t3t.cc in platform/system/nfc on 14/15 and to libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc on 16+.",
      "Consider the refined form found in the android-security-15.0.0_r17 tag, which calls GKI_freebuf(p_cmd_buf) before the NFC_STATUS_FAILED return, to avoid leaking the command buffer. Confirm the declared type of block_id before choosing between the `UINT16_MAX -` and `> 0x10000` conditions."
     ],
     "confidence": "medium",
     "unknowns": [
      "The declarations of block_id, first_block_to_write/first_block_to_read and the block count variables are not in the material. The uint16 wrap is inferred from the commit title and the added checks.",
      "How first_block_to_write/first_block_to_read are derived from tag data, and therefore which inputs the tag controls, is not shown.",
      "The change that produced the refined check in the android-security-15.0.0_r17 tag is not identified, and it is unknown whether the 14/16/17 tags contain the same refinement.",
      "The size of the command buffer and how much capacity it has relative to the descriptor bytes are not shown.",
      "The bulletin does not say whether its Critical/DoS rating reflects the out-of-bounds write or the loop behaviour."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-33636": {
     "cve": "CVE-2026-33636",
     "summary": "Restricts the NEON palette-expansion loops in external/libpng (png_do_expand_palette_rgba8_neon and png_do_expand_palette_rgb8_neon) to full chunks only, so a partial final chunk is left to the scalar fallback instead of being read and written out of bounds, and undoes the *ddp pre-adjustment before handing the pointers back.",
     "component": "platform/external/libpng, ARM NEON palette expansion (arm/palette_neon_intrinsics.c), whose scalar fallback is in pngrtran.c",
     "weakness": "Missing bounds check on a fixed-size vector loop (out-of-bounds read and write when row width is not a multiple of the chunk size)",
     "root_cause": "Both NEON functions walk the row backwards from the end pointers (*ssp, *ddp) in fixed chunks of pixels_per_chunk pixels (4 for RGBA8 via vld1q_lane_u32/vst1q_u32, 8 for RGB8 via vst3_u8). The loop condition was `i < row_width`, so when row_width was not a multiple of pixels_per_chunk the last iteration still loaded a full chunk of palette indices from `*ssp - i` downward and stored a full chunk at `*ddp - (i<<2)` (or `*ddp - 3*i`), touching bytes outside the row buffers. The post-loop `if (i != row_width) i -= pixels_per_chunk;` only corrected the returned pixel count after the out-of-bounds access had already happened. In addition, the pre-adjustment `*ddp = *ddp - ((pixels_per_chunk * sizeof(...)) - 1)` applied at entry was not undone before the handoff to the scalar fallback in pngrtran.c, per the commit message.",
     "fix_analysis": [
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: loop condition changed from `i < row_width` to `i + pixels_per_chunk <= row_width`, so NEON processes only complete 4-pixel chunks and leaves the remainder for the scalar code.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: the `if (i != row_width) i -= pixels_per_chunk;` post-loop correction is removed because i can no longer overshoot row_width.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: `*ddp = *ddp + (pixels_per_chunk * 4 - 1);` is added before the existing `*ssp = *ssp - i; *ddp = *ddp - (i << 2);` handoff. This undoes the entry pre-adjustment so that, per the added comment, the scalar fallback in pngrtran.c receives a dp that points to the correct position.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgb8_neon: the same changes with the 8-pixel chunk: loop condition `i + pixels_per_chunk <= row_width`, post-loop correction removed, and `*ddp = *ddp + (pixels_per_chunk * 3 - 1);` added before `*ssp = *ssp - i; *ddp = *ddp - ((i << 1) + i);`.",
      "Only arm/palette_neon_intrinsics.c is touched (1 file, 13 insertions, 16 deletions). pngrtran.c is not modified by this commit."
     ],
     "completeness": [
      "Covers both NEON palette expansion entry points in the file (RGBA8 and RGB8). The pixels of a partial final chunk are now left to the scalar fallback instead of being handled by vector loads and stores.",
      "The returned i and the adjusted *ssp/*ddp now match the number of pixels the NEON loop actually processed, and the *ddp pre-adjustment is undone before the handoff.",
      "png_riffle_palette_neon in the same file is not changed. The diff gives no sign that it needed changing.",
      "The commit does not touch the dispatch code in pngrtran.c. Whether the handoff is correct depends on the existing scalar fallback consuming the remaining pixels from the returned pointers, and that code is not shown in the material."
     ],
     "behavior_changes": [
      "For row widths not divisible by 4 (RGBA8) or 8 (RGB8), the trailing pixels are now expanded by the scalar path instead of the NEON path.",
      "The *ddp value returned to the caller no longer carries the (pixels_per_chunk * bytes_per_pixel - 1) pre-adjustment.",
      "No new error paths, return codes or logging appear in the diff."
     ],
     "preconditions": [
      "libpng built with PNG_ARM_NEON_IMPLEMENTATION == 1 (ARM NEON path compiled in), per the #if guard in arm/palette_neon_intrinsics.c.",
      "A palette PNG (PNG_COLOR_TYPE_PALETTE) read with palette expansion enabled (png_set_palette_to_rgb) is the likely path to these functions. For example, frameworks/base/libs/androidfw/Png.cpp readPng calls png_set_palette_to_rgb for palette images, per index search.",
      "Image row width not a multiple of the chunk size (4 pixels for png_do_expand_palette_rgba8_neon, 8 pixels for png_do_expand_palette_rgb8_neon).",
      "The exact dispatch conditions in pngrtran.c (which function is chosen, any minimum width) are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/external/libpng 843457e59be7 in android-security-14.0.0_r29, same diff (blob 92c7d6f9f..bdd15849d) as the Android 15 commit. 7/8 lines of the patched region are present at that tag.",
      "15": "same fix: platform/external/libpng fa7e33a0a0cc in android-security-15.0.0_r17 (the commit analysed here), cherry-picked with Change-Id I56e19b3e1a226de21e91e400124e36021357689a. 6/8 lines of the patched region are present at that tag.",
      "16": "same fix: platform/external/libpng 0b9147198ed6 in android-security-16.0.0_r8, same diff (blob 92c7d6f9f..bdd15849d). 6/8 lines of the patched region are present at that tag.",
      "17": "same fix, trivially adapted: platform/external/libpng 7d98050ef8d9 in android-security-17.0.0_r1. The file has no leading blank line, so hunk offsets shift by one (blob 3068e9b6e..f3355bef5), but the code change is the same. 6/8 lines of the patched region are present at that tag."
     },
     "backport_notes": [
      "Single-commit, single-file change with no shown dependency on other patches. It applies to source blob 92c7d6f9f on 14/15/16 and to 3068e9b6e (one-line offset) on 17. Check that the scalar fallback in pngrtran.c in the target tree expects the un-pre-adjusted dp, because this commit does not change pngrtran.c."
     ],
     "confidence": "high",
     "unknowns": [
      "The pngrtran.c caller (dispatch condition, and how it uses the returned i, *ssp and *ddp) is not included in the material.",
      "The material does not show whether the bulletin's Critical EoP rating comes from a specific process or caller. It only identifies the libpng code path.",
      "The material does not show whether non-ARM palette expansion paths (scalar or other SIMD) have a similar issue. This commit touches only the NEON file.",
      "The material does not show whether output for affected row widths was different before the fix."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45515": {
     "cve": "CVE-2026-45515",
     "summary": "In the A2DP Opus decoder, the fix passes opus_decode() the capacity of decode_buf in samples per channel (A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t))) instead of its raw byte length. It also rejects packets whose TOC channel count is not 1 or 2, decodes each packet once instead of once per frame, and sizes the callback payload by the fixed stereo output channel count.",
     "component": "platform/packages/modules/Bluetooth, system/stack/a2dp (A2DP sink Opus vendor codec decoder)",
     "weakness": "Heap buffer overflow: the decoder was given its output-buffer capacity in the wrong unit (bytes instead of samples per channel)",
     "root_cause": "a2dp_vendor_opus_decoder_decode_packet() in system/stack/a2dp/a2dp_vendor_opus_decoder.cc called opus_decode(handle, pBuffer, bufferSize, decode_buf, A2DP_OPUS_DECODE_BUFFER_LENGTH, 0). Per the commit message, opus_decode expects that argument as samples per channel, but A2DP_OPUS_DECODE_BUFFER_LENGTH is the buffer size in bytes. The decoder was therefore told the buffer was larger than it is, by a factor of A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t). A crafted Opus packet with a large frame duration could then overflow the heap-allocated decode_buf. The regression test uses a 60 ms packet (2880 samples per channel at 48 kHz). In addition, the function looped numFrames times, re-decoding the same whole packet in each iteration. It also computed the callback length from the packet's channel count instead of the fixed stereo output channel count.",
     "fix_analysis": [
      "system/stack/a2dp/a2dp_vendor_opus_decoder.cc:a2dp_vendor_opus_decoder_decode_packet: both opus_decode() calls (the normal decode and the NULL-data concealment retry) now receive frame_size = A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t)). This is the number of stereo int16 samples per channel that decode_buf can hold, so the capacity given to libopus matches the real buffer.",
      "Same function: adds an early check `if (numChannels != 1 && numChannels != 2) return false;` on the value returned by opus_packet_get_nb_channels(pBuffer), dropping packets with an unexpected channel count before decoding.",
      "Same function: removes the `for (frame = 0; frame < numFrames; ++frame)` loop. The packet is now decoded once and decode_callback is invoked once per packet.",
      "Same function: the length passed to decode_callback is now ret_val * A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(decode_buf[0]) instead of ret_val * numChannels * sizeof(decode_buf[0]). The new code comment says Opus is always configured as stereo.",
      "Same function: removes the `num_frames = pBuffer[0] & 0xf` variable, which only fed a log line, and replaces the two log::error lines with one log::info line.",
      "system/stack/test/a2dp/a2dp_opus_unittest.cc: adds TEST_F(A2dpOpusTest, decode_packet_60ms_overflow_repro). The test encodes a real 60 ms stereo Opus packet with libopus, prepends the 1-byte A2DP header and feeds it to decoder_iface_->decode_packet. On Android 15 it also adds #include <stdio.h> and #include <opus.h>."
     ],
     "completeness": [
      "Covers both opus_decode() call sites in a2dp_vendor_opus_decoder_decode_packet (the primary decode and the concealment retry). No other opus_decode call appears in the material.",
      "The capacity is derived from A2DP_OPUS_CODEC_OUTPUT_CHS (2, per the commit message) and sizeof(int16_t). Its correctness depends on decode_buf being allocated with A2DP_OPUS_DECODE_BUFFER_LENGTH bytes and on the OpusDecoder handle being created with 2 channels. The allocation in a2dp_vendor_opus_decoder_init is not in the material.",
      "The fix validates the TOC channel count but adds no check that bufferSize > 0. With p_buf->len == 1, bufferSize is 0 and opus_packet_get_nb_channels(pBuffer) still reads pBuffer[0], which is at p_buf->data + offset + 1. This pre-existing pattern is unchanged."
     ],
     "behavior_changes": [
      "Opus packets whose TOC channel count is not 1 or 2 are dropped with an error log, and decode_packet returns false.",
      "Multi-frame Opus packets are decoded once and decode_callback is invoked once per packet, instead of numFrames repeated decodes and callbacks.",
      "The length passed to decode_callback is always computed with 2 output channels, independent of the packet's TOC channel count.",
      "Per-packet logging changed from two log::error lines (LOG_ERROR on Android 14) to one log::info line (LOG_INFO on Android 14)."
     ],
     "preconditions": [
      "Device acts as an A2DP sink with the Opus vendor codec selected, so incoming media packets reach a2dp_vendor_opus_decoder_decode_packet. In the unit test the decoder is invoked as decoder_iface_->decode_packet.",
      "A remote source sends an Opus media packet whose TOC indicates a frame duration large enough for the decoded output to exceed decode_buf. The test uses a 60 ms, 48 kHz stereo packet.",
      "Whether a given device enables the A2DP sink role and the Opus codec is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit 4f7ed05b1660 in android-security-14.0.0_r29. Same logic, but it uses the printf-style LOG_ERROR/LOG_INFO macros instead of log::error/log::info. The same test is added, and the test file replaces <stdio.h> with <opus.h>. Subject is tagged [RESTRICT AUTOMERGE].",
      "15": "same fix: commit b837ddeb7125 in android-security-15.0.0_r17, a cherry-pick of e87caba4b8df (Change-Id I3a1b150c0a761de54dedc5a27e0e9de96b19b16c), tagged [RESTRICT AUTOMERGE].",
      "16": "same fix: commit 5eefb7cb0b0c in android-security-16.0.0_r8. The logic is identical. The diff is smaller because the frameSize/frameLen lines were already formatted on a single line. Subject is tagged [RESTRICT AUTOMERGE].",
      "17": "same fix: commit 3b78dbef3ec6 in android-security-17.0.0_r1, identical to the Android 16 diff. Subject is tagged [RESTRICT AUTOMERGE]."
     },
     "backport_notes": [
      "Single self-contained commit touching one function. The code uses the A2DP_OPUS_CODEC_OUTPUT_CHS constant, and the new test calls libopus encoder APIs (opus_encoder_create/opus_encode).",
      "For trees that use the older logging macros (as on Android 14), use LOG_ERROR/LOG_INFO with %d format strings instead of log::error/log::info. Otherwise the decode logic is identical across 14 to 17."
     ],
     "confidence": "high",
     "unknowns": [
      "The numeric value of A2DP_OPUS_DECODE_BUFFER_LENGTH and how decode_buf is allocated in a2dp_vendor_opus_decoder_init are not in the material.",
      "The material does not show how libopus behaves after the fix when a packet's frame duration exceeds the stated capacity (whether it returns an error and the concealment path runs), or whether a 60 ms packet now fits in the buffer.",
      "The real caller chain from the A2DP sink media path into a2dp_vendor_opus_decoder_decode_packet is not shown; only the test's decoder_iface_->decode_packet call is.",
      "The material does not explain why the bulletin rates this EoP rather than RCE."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45531": {
     "cve": "CVE-2026-45531",
     "summary": "Hardens fsck.exfat boot-sector validation in read_boot_region against integer overflow/underflow from attacker-controlled vol_length, clu_offset and clu_count fields, and makes DIV_ROUND_UP compute in 64 bits; a companion build change enables host builds so the new exfatprogs test image can be run.",
     "component": "platform/external/exfatprogs, fsck.exfat (fsck/fsck.c, include/libexfat.h)",
     "weakness": "Integer overflow / underflow from insufficient bounds checking of on-disk boot sector fields (CWE-190 / CWE-191 style)",
     "root_cause": "In fsck/fsck.c read_boot_region, the volume size sanity check was written as `le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) > bd->size`. vol_length is a 64-bit value taken from the untrusted boot sector, so the multiplication could wrap and a huge vol_length could pass the check. No check ensured clu_offset < vol_length, so code that subtracts clu_offset from vol_length (per the fix comment) could underflow. clu_count (32-bit, untrusted) was not bounded against the exFAT maximum EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5), so arithmetic on it could overflow (per the fix comment). DIV_ROUND_UP in include/libexfat.h performed `(__i + __d - 1)` in the operand's native width, so narrow inputs near their maximum could wrap before the division. The bulletin rates the issue Critical EoP.",
     "fix_analysis": [
      "Commit 2957a37c1d7f (ANDROID: Add support for building exfat tests): Android.bp, fsck/Android.bp, lib/Android.bp add `host_supported: true` to libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat (plus a whitespace fix of the \"exfat2img\" entry). No code logic change; it lets the tools be built for host so the test image can be exercised.",
      "Commit ad273085b771, fsck/fsck.c:read_boot_region: the vol_length check is rewritten as `le64_to_cpu(bs->bsx.vol_length) > (bd->size >> bs->bsx.sect_size_bits)`, replacing a wrap-prone 64-bit multiply with a shift of the device size (sector size is already constrained to 512..4K by the earlier EXFAT_SECTOR_SIZE check).",
      "fsck/fsck.c:read_boot_region: new check rejects the image if `le32_to_cpu(bs->bsx.clu_offset) >= le64_to_cpu(bs->bsx.vol_length)` to prevent underflow when clu_offset is subtracted from vol_length.",
      "fsck/fsck.c:read_boot_region: new check rejects the image if `le32_to_cpu(bs->bsx.clu_count) > EXFAT_MAX_NUM_CLUSTER` (0xFFFFFFF5), bounding cluster count to the spec limit.",
      "include/libexfat.h: DIV_ROUND_UP now casts the dividend to uint64_t (`((uint64_t)(__i) + (__d) - 1) / (__d)`) so the rounding addition is done in 64 bits.",
      "tests/clu_count_overflow/exfat.img.tar.xz: adds a regression test image."
     ],
     "completeness": [
      "Covers the vol_length size check, the clu_offset/vol_length relation, an upper bound on clu_count, and the DIV_ROUND_UP macro, all in read_boot_region / libexfat.h.",
      "The following check in read_boot_region, `le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > bd->size`, is left unchanged. EXFAT_CLUSTER_SIZE is an int shift (up to 32 MB per the earlier check) and clu_count is up to 0xFFFFFFF5 after the cap; if le32_to_cpu yields a 32-bit unsigned value the product may be computed in 32 bits and could wrap. The types involved are not shown in the material.",
      "Only fsck.exfat is patched; other exfatprogs tools sharing the same headers (mkfs, tune, label, dump, exfat2img are listed in export_include_dirs) are not touched in these diffs. The material does not show whether they have equivalent parsing paths.",
      "All checks `goto err`, so a malformed image is rejected rather than repaired."
     ],
     "behavior_changes": [
      "fsck.exfat now rejects boot sectors where clu_offset >= vol_length (new error: \"cluster offset (%u) is larger than volume length\") and where clu_count > 0xFFFFFFF5 (new error: \"too large cluster count: %u, max allowed: %u\"); on Android 15/16 these messages are only printed when verbose, on Android 14 unconditionally.",
      "The vol_length check compares sector counts (bd->size >> sect_size_bits) instead of byte products; images that previously passed via wraparound now fail with \"too large sector count\".",
      "DIV_ROUND_UP yields a uint64_t result for all users of libexfat.h, which may change implicit conversions in callers.",
      "Build: libexfat, fsck.exfat and the exfatprogs header/defaults modules are now also built for host."
     ],
     "preconditions": [
      "fsck.exfat must be run against a volume whose boot sector is attacker-controlled, e.g. a crafted exFAT image; the material does not show which Android component invokes fsck.exfat.",
      "The boot sector must pass the earlier checks in read_boot_region (sector size 512..4K, cluster size <= 32 MB, fs_version 1.0, num_fats == 1) so execution reaches the patched checks."
     ],
     "per_major": {
      "14": "adapted fix: commit 54cf500d4abd in android-security-14.0.0_r29 applies the same three read_boot_region checks, the DIV_ROUND_UP cast and the test image, but the exfat_err calls are unconditional (no `if (verbose)`) and the hunk sits at a different offset (line ~709); resolver reports 7/8 patched lines present, reason not shown in the material.",
      "15": "same fix: commits 2957a37c1d7f (build/test support) and ad273085b771 (overflow checks) in android-security-15.0.0_r17; 8/8 lines present.",
      "16": "same fix: commit c0e2d5cc6945 in android-security-16.0.0_r8 is textually identical to the Android 15 fsck.c/libexfat.h diff (same blob ids cd9ee9a..c7c823c, 0623501..689b017); resolver reports 6/8 lines, which the material does not explain.",
      "17": "resolved (android-security-17.0.0_r1): the material shows only the build/test commit a6faf5dfd40a; the overflow-fix commit is not shown, but 8/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The security-relevant content is ad273085b771 (fsck/fsck.c read_boot_region checks + libexfat.h DIV_ROUND_UP cast); the host_supported Android.bp commit appears to be test infrastructure only. On trees without the `if (verbose)` wrapping (as on Android 14), apply the checks with unconditional exfat_err as in 54cf500d4abd.",
      "Verify that EXFAT_MAX_NUM_CLUSTER is defined in include/libexfat.h on the target branch (present at line 31 on Android 15) and that the earlier EXFAT_SECTOR_SIZE range check exists so the `bd->size >> sect_size_bits` shift is bounded."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact downstream expression(s) in fsck that overflow or underflow (the clu_offset subtraction and the clu_count arithmetic) are not shown; only the fix comments reference them.",
      "Whether read_boot_region gates all later uses of these fields is not shown.",
      "Which Android process invokes fsck.exfat and with what privileges is not shown in the material.",
      "Why the resolver reports 7/8 lines on Android 14 and 6/8 on Android 16.",
      "Whether the overflow-fix commit itself is present on Android 17 (only the build commit is shown).",
      "The types of le32_to_cpu's result and bd->size, which determine whether the remaining clu_count * cluster size product is 32-bit."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49879": {
     "cve": "CVE-2026-49879",
     "summary": "In the NFC Type 3 Tag reader/writer (rw_t3t.cc), the block loop variable in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd is widened from uint16_t to uint32_t, the loop upper bound and the preceding range check are computed in uint32_t against a 0x10000 limit, and the GKI command buffer is now freed when the range check fails.",
     "component": "platform/system/nfc (Android 14/15) and platform/packages/modules/Nfc libnfc-nci (Android 16/17), libnfc-nci Type 3 Tag reader/writer, src/nfc/tags/rw_t3t.cc",
     "weakness": "Integer width / overflow handling in block-range computation (loop counter declared uint16_t with a 16-bit derived bound), plus a leaked command buffer on the error path of the existing bounds check.",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd the iteration variable block_id was a uint16_t and the loop bound (first_block_to_write + ndef_blocks_to_write, or first_block_to_read + cur_blocks_to_read) was formed from two uint16_t values. A range check (logging SafetyNet id 508389055) guarded the loop with \"count > UINT16_MAX - first\". When that check failed, the function returned without releasing the command buffer obtained from rw_t3t_get_cmd_buf(), so a GKI buffer leaked. The 16-bit block_id also could not safely cover the full range of block IDs up to 65535, so a sum reaching 0x10000 had to be rejected instead of handled. The commit calls this a potential integer overflow when determining the range of blocks to write or check.",
     "fix_analysis": [
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: block_id is moved from a uint16_t declaration to a uint32_t declaration (shared with timeout).",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: the range check becomes '(uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000'; on failure GKI_freebuf(p_cmd_buf) is called before the existing LOG(ERROR), android_errorWriteLog(0x534e4554, \"508389055\") and return.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: the for-loop condition becomes 'block_id < ((uint32_t)first_block_to_write + ndef_blocks_to_write)' so the bound and the counter are both 32-bit.",
      "src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: the same three changes with first_block_to_read and cur_blocks_to_read. block_id becomes uint32_t, the check becomes '(uint32_t)first_block_to_read + cur_blocks_to_read > 0x10000' with GKI_freebuf(p_cmd_buf) on failure, and the loop bound is cast to uint32_t.",
      "The rest of the loop bodies (2-byte vs 3-byte block list encoding based on block_id < 256) is unchanged."
     ],
     "completeness": [
      "The fix changes only rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, and both get the same treatment. Whether other functions in rw_t3t.cc build block lists the same way is not shown in the material.",
      "The fix relies on the 0x10000 limit: sums up to exactly 65536 are now accepted (block IDs 0..65535), which the previous check rejected; the widened block_id makes this safe for the loop counter itself.",
      "The command buffer leak is closed only for this specific range-check failure path; other early-return paths in these functions are not shown in the diff and cannot be judged.",
      "Whether the inputs (first_block_to_write, ndef_blocks_to_write, first_block_to_read, cur_blocks_to_read) are validated earlier against the tag attribute data is not visible in the material."
     ],
     "behavior_changes": [
      "When first+count exceeds 0x10000, the command buffer is now returned to the GKI pool instead of being leaked; the error log and SafetyNet log (0x534e4554 / 508389055) are unchanged.",
      "The range check is slightly more permissive: a range whose first+count equals exactly 0x10000 is now processed rather than rejected.",
      "Block IDs up to 65535 are iterated with a 32-bit counter in both functions."
     ],
     "preconditions": [
      "NFC is enabled and the device acts as reader/writer against a Type 3 tag. The NDEF update or NDEF check path in rw_t3t.cc must reach rw_t3t_send_next_ndef_update_cmd or rw_t3t_send_next_ndef_check_cmd.",
      "The block range values come from the tRW_T3T_CB control block (p_cb); where they originate (tag attribute data or caller parameters) is not shown in the material.",
      "Per index search, no chunks mentioning either function were returned, so their callers are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc c1fa4599a13e in android-security-14.0.0_r29, identical diff to src/nfc/tags/rw_t3t.cc (line offsets differ by a few lines).",
      "15": "same fix: platform/system/nfc 78dd34a6a026 in android-security-15.0.0_r17, the reference diff analysed here.",
      "16": "same fix: platform/packages/modules/Nfc 92d884176dcd in android-security-16.0.0_r8, identical diff applied to libnfc-nci/src/nfc/tags/rw_t3t.cc (libnfc-nci lives inside the Nfc module project here).",
      "17": "same fix: platform/packages/modules/Nfc 442e948e4be2 in android-security-17.0.0_r1, identical diff to libnfc-nci/src/nfc/tags/rw_t3t.cc."
     },
     "backport_notes": [
      "The patch assumes both functions already contain the earlier range check with android_errorWriteLog(0x534e4554, \"508389055\"). Trees without that prior change need the check and its error return added, not just modified. On Android 16+ the file path is libnfc-nci/src/nfc/tags/rw_t3t.cc under packages/modules/Nfc instead of system/nfc."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact error status returned after the range check (the return statement is outside the diff hunks).",
      "How first_block_to_write/ndef_blocks_to_write and first_block_to_read/cur_blocks_to_read are derived and whether they are attacker-influenced via tag data; the material does not show it.",
      "Why the bulletin rates this Critical EoP: the diff shows a loop-counter width change and a buffer leak fix, and the material does not show the concrete memory-safety consequence.",
      "The relationship between bug 503544522 (this commit) and the SafetyNet id 508389055 logged by the pre-existing check.",
      "The callers of the two patched functions (the index search returned none)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49882": {
     "cve": "CVE-2026-49882",
     "summary": "In rw_mfc_handle_read_op (rw_mfc.cc), the fix saves ndef_length and ndef_start_pos before rw_nfc_decodeTlv runs on the first block read. It treats a decode failure, or any increase of ndef_length, as a failure and restores both saved values, so an inflated length from tag data no longer stays in the Mifare Classic control block and cannot bound the copy into p_ndef_buffer.",
     "component": "NFC tag reader/writer, Mifare Classic (rw_mfc): platform/system/nfc on Android 14/15, platform/packages/modules/Nfc libnfc-nci on Android 16/17",
     "weakness": "Out-of-bounds (heap) write: an NDEF length decoded from tag data was not rolled back after inflation was detected, and it bounds a copy loop into p_ndef_buffer",
     "root_cause": "In rw_mfc_handle_read_op, on the first block (work_offset == 0), rw_nfc_decodeTlv(data) parses the NDEF TLV from the tag response and can update p_mfc->ndef_length and p_mfc->ndef_start_pos. The pre-fix code kept the earlier value in a local saved_length. It skipped the copy and set failed = true when the decode failed or the length grew, but it left the possibly inflated ndef_length and ndef_start_pos in the control block. The copy loop `p_mfc->p_ndef_buffer[p_mfc->work_offset] = p[offset]` is bounded only by `offset < len` and `work_offset < p_mfc->ndef_length`. The commit is titled as fixing an OOB write, and the commit message says Buganizer issue b/503557185 reports a heap buffer overflow in com.android.nfc during Mifare Classic NDEF reads. The material does not show the exact pre-fix path by which the inflated value reached the copy, or how p_ndef_buffer is sized.",
     "fix_analysis": [
      "src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: the function-level `saved_length` is removed. Inside the `work_offset == 0` branch, the fix saves `saved_len = p_mfc->ndef_length` and `saved_pos = p_mfc->ndef_start_pos` right before calling rw_nfc_decodeTlv(data).",
      "Same function: the failure condition becomes `!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len`. On failure, the code logs, calls android_errorWriteLog(0x534e4554, \"178725766\") only when inflation was detected, restores `p_mfc->ndef_length = saved_len` and `p_mfc->ndef_start_pos = saved_pos`, and sets failed = true. The `offset = p_mfc->ndef_start_pos` assignment that follows then uses the restored value.",
      "Same function: the copy guard is simplified to `if (!failed)` and the old `else { android_errorWriteLog(...); failed = true; }` branch is removed. The copy loop body is unchanged and is still bounded by `offset < len` and `work_offset < ndef_length`. After this decode, ndef_length cannot exceed the value it had before decoding.",
      "The commit message says control flow was also updated to prevent further block reads on failure. The shown hunks change no control-flow lines beyond the rollback and failed = true, and the code that consumes `failed` is not shown."
     ],
     "completeness": [
      "Covers the TLV decode at work_offset == 0 in rw_mfc_handle_read_op: any growth of ndef_length is rejected and the previous ndef_length and ndef_start_pos are restored.",
      "A decoded ndef_length that is smaller than or equal to the saved value is still accepted and used. Only inflation is rejected.",
      "rw_nfc_decodeTlv itself is not changed. The protection is applied only at its call site in rw_mfc_handle_read_op.",
      "The rest of rw_mfc_handle_read_op is not in the diff: how `failed` is consumed, the `work_offset >= ndef_length` completion check, and issuing the next block read. The material shows no change there.",
      "Other tag-type readers in the component are not touched by this commit. The material does not show whether they have similar logic."
     ],
     "behavior_changes": [
      "A first-block TLV decode that yields a larger ndef_length than the stored one is now a read failure, and ndef_length and ndef_start_pos are reverted to their prior values.",
      "android_errorWriteLog(0x534e4554, \"178725766\") is now emitted only when inflation is detected. Before the fix, the else branch logged for both a plain decode failure and inflation.",
      "The Android 14 variant logs through DLOG_IF(INFO, nfc_debug_enabled) instead of LOG(VERBOSE). The logic is otherwise identical."
     ],
     "preconditions": [
      "A Mifare Classic tag is read for NDEF by the NFC stack, so rw_mfc_handle_read_op processes the first block read (work_offset == 0) and rw_nfc_decodeTlv parses TLV bytes supplied by the tag.",
      "The tag data must make the decoded NDEF length larger than the ndef_length stored before decoding.",
      "The bulletin rates this Critical RCE. The material does not describe the user-interaction requirements."
     ],
     "per_major": {
      "14": "Same fix: platform/system/nfc 7eaf8e16d9ad in android-security-14.0.0_r29, identical logic in src/nfc/tags/rw_mfc.cc, with DLOG_IF(INFO, nfc_debug_enabled) logging instead of LOG(VERBOSE). 7/8 lines of the patched region present.",
      "15": "Same fix: platform/system/nfc 5dd24f8b4a6d in android-security-15.0.0_r17 (cherry-pick of 5a99b04ef8b3, Change-Id I959c5b5fde81c16cd489e829d976efa93e23efc5), src/nfc/tags/rw_mfc.cc. 7/8 lines of the patched region present.",
      "16": "Same fix, relocated: platform/packages/modules/Nfc b79e5fd8cf14 in android-security-16.0.0_r8, file libnfc-nci/src/nfc/tags/rw_mfc.cc (function at a later line offset), same hunk content. 7/8 lines of the patched region present.",
      "17": "Same fix, relocated: platform/packages/modules/Nfc 19fc329133fe in android-security-17.0.0_r1, file libnfc-nci/src/nfc/tags/rw_mfc.cc, same hunk content. 7/8 lines of the patched region present."
     },
     "backport_notes": [
      "Each branch gets one self-contained commit. The hunks are the same across branches except for the log macro (DLOG_IF on 14, LOG(VERBOSE) on 15 and later) and the file path: src/nfc/tags/rw_mfc.cc in platform/system/nfc on 14/15, and libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc on 16/17, where libnfc-nci ships as part of the NFC mainline module (general platform knowledge).",
      "When porting to a tree that still has the old `saved_length` and else-branch form, remove both and keep the android_errorWriteLog call so the SafetyNet marker 178725766 is preserved."
     ],
     "confidence": "medium",
     "unknowns": [
      "The rest of rw_mfc_handle_read_op is not shown: how `failed` is handled after the copy block and when the next block read is issued. The exact pre-fix path by which an inflated length reached the copy loop therefore cannot be confirmed from the material.",
      "How p_ndef_buffer is allocated and sized is not in the material.",
      "The internals of rw_nfc_decodeTlv, and which TLV encodings change the length, are not shown.",
      "How the fix prevents further block reads on failure, as the commit message claims, is not visible in the hunks.",
      "The commit lists its test as 'manual'. No regression test is included."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49884": {
     "cve": "CVE-2026-49884",
     "summary": "In rw_mfc_handle_read_op (Mifare Classic NDEF read), the fix saves ndef_length and ndef_start_pos before rw_nfc_decodeTlv, treats either a decode failure or a larger ndef_length as failure, and rolls both fields back so an inflated length is not left in the control block.",
     "component": "platform/system/nfc (Android 14/15) and platform/packages/modules/Nfc libnfc-nci (Android 16/17), NFC reader/writer Mifare Classic tag handling (rw_mfc.cc)",
     "weakness": "Heap out-of-bounds write (per commit title/message): an inflated, tag-derived NDEF length was not rolled back from the control block after validation failed",
     "root_cause": "In src/nfc/tags/rw_mfc.cc rw_mfc_handle_read_op, on the first block read (work_offset == 0), rw_nfc_decodeTlv(data) parses the NDEF TLV from tag data and updates p_mfc->ndef_length and p_mfc->ndef_start_pos. Before copying, the old code checked saved_length >= p_mfc->ndef_length. When the length had grown it set failed = true and logged, but it never restored the control block, so the inflated ndef_length and the new ndef_start_pos stayed in p_mfc. The copy loop into p_mfc->p_ndef_buffer[p_mfc->work_offset] is bounded only by p_mfc->ndef_length, and the following completion check (work_offset >= ndef_length) reads the same field. The commit message describes the result as a heap buffer overflow in com.android.nfc during Mifare Classic NDEF reads (b/503557185); the exact path from the stale length to the overflowing write is not shown in the material. If rw_nfc_decodeTlv updates fields before returning false, the old code did not roll those back either.",
     "fix_analysis": [
      "src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: removes the function-scope saved_length. Inside the work_offset == 0 branch it instead saves uint16_t saved_len = p_mfc->ndef_length and uint8_t saved_pos = p_mfc->ndef_start_pos immediately before rw_nfc_decodeTlv(data).",
      "The failure condition becomes (!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len). If ndef_length grew, it calls android_errorWriteLog(0x534e4554, \"178725766\"). In both failure cases it restores p_mfc->ndef_length = saved_len and p_mfc->ndef_start_pos = saved_pos and sets failed = true. offset = p_mfc->ndef_start_pos is then read from the restored value.",
      "The copy guard becomes if (!failed) { while ((offset < len) && (p_mfc->work_offset < p_mfc->ndef_length)) copy }. The old else branch (log + failed = true) is removed because the inflation check now sits next to the decode.",
      "The same patch is applied on Android 14 (7eaf8e16d9ad; only the log macro differs, DLOG_IF(INFO, nfc_debug_enabled)), and on Android 16 (b79e5fd8cf14) and Android 17 (19fc329133fe) in libnfc-nci/src/nfc/tags/rw_mfc.cc."
     ],
     "completeness": [
      "Covers the first-block read path (work_offset == 0) in rw_mfc_handle_read_op: after either a failed TLV decode or a decode that increases ndef_length, p_mfc->ndef_length and p_mfc->ndef_start_pos keep their earlier values.",
      "The new length is checked against the previously stored ndef_length, not against the actual size of p_ndef_buffer. The fix therefore relies on the stored value matching the buffer allocation, which is not shown in the material.",
      "A decoded ndef_length that is smaller than or equal to the saved value is still accepted and overwrites the control block. The copy loop is still bounded only by ndef_length.",
      "rw_nfc_decodeTlv itself is not changed; the rollback is done in the caller. The commit message says further block reads are prevented on failure, but the visible diff only restructures the failure handling inside this branch, and how failed is handled later in the function is not shown."
     ],
     "behavior_changes": [
      "A first-block TLV decode that reports a larger NDEF length than previously recorded is rejected, and the earlier length and start position are kept instead of the inflated values.",
      "A failed rw_nfc_decodeTlv now also restores ndef_length and ndef_start_pos (previously only failed was set).",
      "The SafetyNet log 0x534e4554/178725766 now fires only for the length-inflation case. Previously the else branch also logged it on a plain decode failure.",
      "No new return codes or API changes appear in the diff. Within the diff, failed is set in the same cases as before."
     ],
     "preconditions": [
      "A Mifare Classic tag is presented to the device (NFC in physical proximity) during an NDEF read handled by rw_mfc_handle_read_op in rw_mfc.cc.",
      "The read is at work_offset == 0 (first block of the NDEF read), so rw_nfc_decodeTlv parses the tag-supplied TLV into p_mfc->ndef_length / ndef_start_pos.",
      "The tag data gives an NDEF length larger than the length previously recorded in the control block.",
      "Per the commit message, the code runs in the com.android.nfc process. rw_mfc_handle_read_op is static, and no callers outside the nfc component were found (per index search)."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc 7eaf8e16d9ad in android-security-14.0.0_r29, identical logic; only the verbose log macro is DLOG_IF(INFO, nfc_debug_enabled) instead of LOG(VERBOSE).",
      "15": "same fix: platform/system/nfc 5dd24f8b4a6d in android-security-15.0.0_r17 (cherry-pick of 5a99b04ef8b3, Change-Id I959c5b5fde81c16cd489e829d976efa93e23efc5).",
      "16": "same fix: platform/packages/modules/Nfc b79e5fd8cf14 in android-security-16.0.0_r8; the file is at libnfc-nci/src/nfc/tags/rw_mfc.cc, identical hunk at different line offsets.",
      "17": "same fix: platform/packages/modules/Nfc 19fc329133fe in android-security-17.0.0_r1, libnfc-nci/src/nfc/tags/rw_mfc.cc, identical hunk."
     },
     "backport_notes": [
      "Single self-contained hunk in rw_mfc_handle_read_op. On 14/15 the file is src/nfc/tags/rw_mfc.cc in platform/system/nfc; on 16/17 it is libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc. Adjust only the log macro (DLOG_IF vs LOG(VERBOSE)) to match the branch.",
      "Remove the old saved_length >= ndef_length check and its else branch in the same change that adds the rollback. If they are kept, failed would still route into the old else branch and log a second time."
     ],
     "confidence": "medium",
     "unknowns": [
      "How p_ndef_buffer is allocated and whether its size equals the ndef_length recorded before the read.",
      "What rw_nfc_decodeTlv parses and whether it can update fields before returning false.",
      "How the failed flag is handled in the rest of rw_mfc_handle_read_op (the commit message says further block reads are prevented, but that code is not in the diff).",
      "The exact path from the stale ndef_length to the out-of-bounds write, and the trigger conditions of b/503557185 and b/503553866 beyond 'heap buffer overflow during Mifare Classic NDEF reads'."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49919": {
     "cve": "CVE-2026-49919",
     "summary": "Replaces the unchecked `size = rows * pitch; FT_ALLOC(buf, size)` in tt_face_colr_blend_layer (src/sfnt/ttcolr.c) with FT_ALLOC_MULT(buf, rows, pitch), so the allocator detects an overflowing multiplication and the function returns an error instead of allocating an undersized buffer.",
     "component": "platform/external/freetype, sfnt driver, COLR (color glyph layer) blending in src/sfnt/ttcolr.c",
     "weakness": "Integer overflow in allocation size computation (rows * pitch) leading to an undersized heap allocation",
     "root_cause": "In tt_face_colr_blend_layer, the size of the temporary layer buffer was computed with a plain multiplication, `size = rows * pitch`, and passed to FT_ALLOC. If the product wrapped, FT_ALLOC could succeed with a buffer smaller than rows*pitch. The commit message confirms the issue is an overflow, which is now detected in ft_mem_qrealloc. How buf is used after the allocation is not shown beyond the `p = dstSlot->bitmap.buffer;` context line.",
     "fix_analysis": [
      "src/sfnt/ttcolr.c:tt_face_colr_blend_layer: the two lines `size = rows * pitch;` and `if ( FT_ALLOC( buf, size ) )` are replaced by `if ( FT_ALLOC_MULT( buf, rows, pitch ) )`. FT_ALLOC_MULT passes the two factors separately to the memory layer. The commit message states the overflow is detected in ft_mem_qrealloc, and the resulting error is propagated via `return error`.",
      "No other code in the function or file is changed. The fix is a single hunk of 2 deletions and 1 insertion."
     ],
     "completeness": [
      "Covers only the single allocation of `buf` in tt_face_colr_blend_layer. The overflow check is now delegated to the FreeType memory layer instead of being done at the call site.",
      "This hunk no longer assigns the `size` variable. The material does not show whether `size` is declared or used later in the function, so the diff does not tell whether an unassigned variable remains.",
      "The material shows no other `rows * pitch` style allocations in ttcolr.c, so a sibling pattern can be neither confirmed nor ruled out from the provided context."
     ],
     "behavior_changes": [
      "When a font's COLR layer bitmap dimensions make rows*pitch overflow, tt_face_colr_blend_layer now fails with the error returned by the allocator instead of continuing with an undersized buffer. The specific error code is not shown in the material.",
      "No change for fonts whose rows*pitch does not overflow."
     ],
     "preconditions": [
      "A font with a COLR table is processed through FreeType's sfnt driver so that tt_face_colr_blend_layer is invoked (color glyph layer blending), with layer bitmap rows and pitch whose product overflows.",
      "Reachability on Android 15: per index search, frameworks/rs/rsFont.cpp uses FreeType directly (FT_New_Memory_Face / FT_New_Face in Font::init), and an hwui unit test (TypefaceTests.cpp) uses android::FreeTypeFontMgr. Whether production paths enable color glyph loading that reaches this function is not shown."
     ],
     "per_major": {
      "14": "same fix: platform/external/freetype ab9caf444825 in android-security-14.0.0_r29, identical hunk at line 1803 (different pre-image blob 5d98dcab8). Resolver status: resolved (bug_id). The tag check found 1/3 lines of the patched region present.",
      "15": "same fix: platform/external/freetype 9f67316aa41d in android-security-15.0.0_r17, hunk at line 1803. Resolver status: resolved (bug_id). The tag check found 1/3 lines of the patched region present.",
      "16": "same fix: platform/external/freetype 6770ef4d9faf in android-security-16.0.0_r8, with the same hunk and blob hashes as Android 15 (281e7135e -> b4e58fe0e). Resolver status: resolved (bug_id). The tag check found 1/3 lines of the patched region present.",
      "17": "same fix: platform/external/freetype 65f3c6ba20e7 in android-security-17.0.0_r1, same hunk shifted to line 1805 (pre-image blob 2bf9a7e95). Resolver status: resolved (bug_id). The tag check found 1/3 lines of the patched region present."
     },
     "backport_notes": [
      "Single-hunk change with no other changes in the commit. The same hunk was applied on the 14, 15, 16 and 17 trees. On trees where the function still references `size` after the allocation, check that the variable is still assigned, or remove its declaration."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of tt_face_colr_blend_layer beyond the hunk (how buf is written later, whether `size` is used later) is not included in the material.",
      "The exact error code returned when ft_mem_qrealloc detects the overflow is not shown.",
      "Whether Android's Skia/minikin font loading paths request color glyph rendering that reaches tt_face_colr_blend_layer is not shown.",
      "Why the tag check reports only 1/3 lines of the patched region present in each major is not explained."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49921": {
     "cve": "CVE-2026-49921",
     "summary": "Replaces the fixed 400-byte (SDP_MAX_ATTR_LEN) heap buffer used by sdpu_build_partial_attrib_entry with an allocation sized to the serialized attribute. Rejects attribute payloads larger than SDP_MAX_ATTR_LEN both when serving ServiceAttribute / ServiceSearchAttribute requests and when inserting attributes into the local SDP database. Rejects continuation offsets at or beyond the serialized length.",
     "component": "platform/packages/modules/Bluetooth, system/stack/sdp (SDP server: sdp_server.cc, sdp_utils.cc, sdp_db.cc)",
     "weakness": "Heap buffer overflow from a fixed-size allocation with no bounds check on the serialized attribute length (missing bounds check), plus an unchecked offset leading to out-of-bounds read",
     "root_cause": "sdpu_build_partial_attrib_entry (system/stack/sdp/sdp_utils.cc) called osi_malloc(SDP_MAX_ATTR_LEN) and then immediately called sdpu_build_attrib_entry(p_attr_buff, p_attr), which serializes the attribute entry including the full payload of p_attr->len bytes. Nothing checked p_attr->len, or the total entry length from sdpu_get_attrib_entry_len (which adds header bytes on top of p_attr->len), against the 400-byte allocation. SDP_AddAttributeToRecord (sdp_db.cc) limited attributes only by the remaining record pad (SDP_MAX_PAD_LEN - free_pad_ptr), not by SDP_MAX_ATTR_LEN, so the database could hold attributes whose serialized entry exceeds 400 bytes. When a remote request forced a partial attribute response, serialization could write past the heap buffer. The function also did not check *offset against attr_len before computing attr_len - *offset and calling memcpy from &p_attr_buff[*offset], which could cause an out-of-bounds read. The commit message describes the bug as an oversized attribute serialized into a fixed 400-byte allocation during partial attribute responses.",
     "fix_analysis": [
      "system/stack/sdp/sdp_utils.cc:sdpu_build_partial_attrib_entry: computes attr_len = sdpu_get_attrib_entry_len(p_attr) first. If p_attr->len > SDP_MAX_ATTR_LEN, it logs an error and returns p_out unchanged. It allocates p_attr_buff with osi_malloc(attr_len) instead of the fixed SDP_MAX_ATTR_LEN, null-checks it, and only then calls sdpu_build_attrib_entry. If *offset >= attr_len, it frees the buffer and returns p_out. The existing clamp of the caller-supplied len to SDP_MAX_ATTR_LEN is kept.",
      "system/stack/sdp/sdp_server.cc:process_service_attr_req and process_service_search_attr_req: inside the per-attribute loop, right after sdp_db_find_attr_in_rec returns p_attr, if p_attr->len > SDP_MAX_ATTR_LEN the code logs 'SDP attr too big', calls sdpu_build_n_send_error(p_ccb, trans_num, SDP_NO_RESOURCES, NULL) and returns. On Android 15 this check runs before the AVRCP/HFP rewrites and before the serialization branches.",
      "system/stack/sdp/sdp_db.cc:SDP_AddAttributeToRecord: the overflow condition becomes attr_len > SDP_MAX_ATTR_LEN || free_pad_ptr + attr_len >= SDP_MAX_PAD_LEN. For TEXT_STR_DESC_TYPE the value is truncated to max_allowed_len = std::min<uint32_t>(SDP_MAX_ATTR_LEN, SDP_MAX_PAD_LEN - free_pad_ptr) and NUL-terminated at p_val[max_allowed_len - 1]. For any other type attr_len is set to 0. That was already the behavior for the pad-overflow case and now also applies to the > SDP_MAX_ATTR_LEN case. <algorithm> is included for std::min.",
      "Tests: stack_sdp_test.cc adds write_read_max_attr_len_slicing, which stores a 400-byte text attribute, drives sdp_server_handle_client_req with max_list_len 50 through continuations and checks the reassembled entry (sequence length 406). It also adds SDP_AddAttribute__exceed_max_attr_len_truncation, which checks that a 410-byte text value is stored as 400 bytes. stack_sdp_utils_test.cc adds sdpu_build_partial_attrib_entry_exceeds_max_attr_len (len 500 returns p_out) and sdpu_build_partial_attrib_entry_within_bounds."
     ],
     "completeness": [
      "Both server request paths that call sdpu_build_partial_attrib_entry (process_service_attr_req and process_service_search_attr_req) now reject oversized payloads, and the helper itself is bounded and sized dynamically. The fix therefore has three layers: database insertion, request handling, and the serializer.",
      "The server-side check compares p_attr->len (payload only) with SDP_MAX_ATTR_LEN. A 400-byte payload still produces a larger serialized entry (the test expects a 406-byte sequence), so the dynamic osi_malloc(attr_len) in the helper is needed. The server checks alone would not keep a fixed 400-byte buffer within bounds.",
      "The *offset >= attr_len guard in the helper is new. The Android 14 diff context shows the callers already compared attr_len against p_ccb->cont_info.attr_offset; the helper now enforces this on its own as well.",
      "Attributes with len > SDP_MAX_ATTR_LEN that are already in the database are not removed. They are refused at request time with SDP_NO_RESOURCES, and the handler returns, so the whole response for that request is aborted.",
      "The commit message mentions 'mistaken bounds checks elsewhere in sdp_server.cc'. Their correction is visible only in the Android 14 diff, which removes the attr_len >= SDP_MAX_ATTR_LEN check inside the 'add partially' branch of both handlers. The Android 15/16/17 sdp_server.cc diffs contain only insertions.",
      "Only the server (local database) side is changed. SDP client/discovery parsing is not part of this change."
     ],
     "behavior_changes": [
      "ServiceAttribute and ServiceSearchAttribute requests that match a local attribute with a payload over 400 bytes now get an SDP_NO_RESOURCES error response instead of data. This applies to the whole request, not just that attribute.",
      "SDP_AddAttribute / SDP_AddAttributeToRecord now silently truncates TEXT_STR_DESC_TYPE values longer than SDP_MAX_ATTR_LEN to at most 400 bytes and NUL-terminates the caller's buffer at the truncation point. Non-text attributes longer than 400 bytes are stored with length 0. Previously such attributes were stored in full as long as the record pad had space.",
      "sdpu_build_partial_attrib_entry returns p_out unchanged when the payload exceeds SDP_MAX_ATTR_LEN, when allocation fails, or when *offset >= attr_len. It now allocates exactly the serialized entry size instead of 400 bytes.",
      "Test harness: the Android 14 variant adds osi_malloc/osi_free mock bodies to the StackSdpUtilsTest fixture."
     ],
     "preconditions": [
      "A record in the local SDP server database must contain an attribute whose serialized entry exceeds the 400-byte buffer. Attributes are inserted through SDP_AddAttribute / SDP_AddAttributeToRecord (callers include SDP_AddProfileDescriptorListToRecord in sdp_db.cc, per index search). Before the fix the only limit was the remaining record pad.",
      "A remote peer sends a ServiceAttribute or ServiceSearchAttribute request that selects that attribute and forces the partial-attribute path. These requests are handled by process_service_attr_req / process_service_search_attr_req via sdp_server_handle_client_req.",
      "The SDP server receives requests over an L2CAP connection. The bulletin rates this issue Critical RCE."
     ],
     "per_major": {
      "14": "adapted fix (51b0cc33b16c, android-security-14.0.0_r29): same three-part logic, with SDP_TRACE_ERROR/SDP_TRACE_WARNING logging. In sdp_server.cc the p_attr->len > SDP_MAX_ATTR_LEN check is placed after attr_len = sdpu_get_attrib_entry_len(p_attr), and the old attr_len >= SDP_MAX_ATTR_LEN check inside the 'add partially' branch is removed in both handlers. max_allowed_len is computed inside the TEXT_STR_DESC_TYPE branch. In the helper, the offset check comes after the len clamp. Tests use StackSdpMainTest and L2CA_ConnectReq2_cid, and add osi_malloc/osi_free mocks to StackSdpUtilsTest.",
      "15": "this commit (c738041c8ce8, android-security-15.0.0_r17), the analysed diff: the check sits at the top of the if (p_attr) block, logging uses log:: macros, and SDP_NO_RESOURCES is unscoped.",
      "16": "same fix (947e8bbe7dc6, android-security-16.0.0_r8): same logic, with tSDP_STATUS::SDP_NO_RESOURCES and reinterpret_cast for osi_malloc. The test uses tSDP_STATE::CONNECTED and a gmock L2CA_DataWrite expectation.",
      "17": "same fix (4a9e8875e210, android-security-17.0.0_r1): same logic as 16. The database function is the static sdp_add_attribute_to_record in sdp_db.cc, and there are minor context differences (for example, sdpu_set_avrc_target_version takes device_address by reference)."
     },
     "backport_notes": [
      "Apply all three code changes together. The sdp_server.cc checks bound only the payload, so sdpu_build_partial_attrib_entry must also switch from the fixed SDP_MAX_ATTR_LEN allocation to osi_malloc(attr_len). For trees older than Android 15, use the Android 14 variant as the template: SDP_TRACE_* logging, check placed after sdpu_get_attrib_entry_len, removal of the partial-branch attr_len >= SDP_MAX_ATTR_LEN check, and osi_allocator mocks added to StackSdpUtilsTest."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the numeric value of SDP_MAX_PAD_LEN, so how large an attribute could be stored before the fix is unknown.",
      "The material does not show the full body of sdpu_get_attrib_entry_len and sdpu_build_attrib_entry, so the exact per-type header overhead is unknown.",
      "The material does not show which local profiles or app-facing APIs can actually put an attribute longer than 400 bytes into the server database.",
      "Their sdp_server.cc hunks are insert-only, so the material does not show whether the Android 15/16/17 trees still contained the attr_len >= SDP_MAX_ATTR_LEN partial-branch check that the Android 14 variant removes.",
      "For Android 15, the material does not show whether the remote-supplied continuation offset reaches *offset in sdpu_build_partial_attrib_entry directly or only through the server-held cont_info.attr_offset."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49932": {
     "cve": "CVE-2026-49932",
     "summary": "Hardens PduParser.parseParts in frameworks/base telephony/common against malformed MMS multipart PDUs by adding a recursion depth cap of 30 for nested multipart/alternative parts, rejecting a declared part dataLength larger than the remaining stream bytes before allocating, and checking that a nested child body is non-null and non-empty before taking its first part.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parser (com.google.android.mms.pdu.PduParser)",
     "weakness": "Missing input validation on untrusted PDU data: unbounded recursion, unchecked allocation size from attacker-controlled length field, missing null/empty check before indexing",
     "root_cause": "In telephony/common/com/google/android/mms/pdu/PduParser.java, parseParts(ByteArrayInputStream) trusted fields of the multipart body. (1) Inside 'if (dataLength > 0)' it executed new byte[dataLength] with no comparison to pduDataStream.available(), so a huge declared length could cause an oversized allocation (OutOfMemoryError). (2) When the part content type was ContentType.MULTIPART_ALTERNATIVE it recursed into parseParts on the part data with no depth bound, so deeply nested parts could exhaust the stack (StackOverflowError). (3) After that recursion it called childBody.getPart(0) without checking that childBody was non-null or had any parts, giving NullPointerException or ArrayIndexOutOfBoundsException on an empty or failed child body. These conditions throw out of parseParts instead of returning null.",
     "fix_analysis": [
      "PduParser.java: adds the constant MAX_PARSING_DEPTH = 30 and turns the protected parseParts(ByteArrayInputStream) into a thin wrapper that calls a new private parseParts(ByteArrayInputStream, int depth) with depth 0. The private method logs (Log.e) and returns null when depth > MAX_PARSING_DEPTH, so recursion is bounded.",
      "PduParser.java:parseParts(stream, depth): inside the 'if (dataLength > 0)' block, before 'byte[] partData = new byte[dataLength]', it now returns null if dataLength > pduDataStream.available(). This prevents allocating an array larger than the data actually present.",
      "PduParser.java:parseParts(stream, depth): the recursive call for MULTIPART_ALTERNATIVE now passes depth + 1, and its result is checked. If childBody == null || childBody.getPartsNum() == 0, the method logs and returns null instead of calling childBody.getPart(0). Because a null child makes the parent return null too, a depth-limit hit propagates up and fails the whole PDU parse.",
      "tests/TelephonyCommonTests/.../PduParserTest.java (new): three regression tests feed 14 to 17 byte PDUs, labelled as AIOOBE, NPE and OOM triggers, and each asserts that PduParser(payload, true).parse() returns null."
     ],
     "completeness": [
      "Covers the three failure modes in parseParts (depth, oversized dataLength, empty/null nested child) in the one recursion path for MULTIPART_ALTERNATIVE. All nested calls go through the depth-tracking private overload.",
      "The dataLength check sits inside 'if (dataLength > 0)'. The code of the non-positive branch is not in the material.",
      "The commit message also says skipWapValue and parameter skipping were changed to use InputStream.skip instead of temporary byte arrays. The PduParser.java diff contains only the depth cap, the available() check and the child check, and none of the four branch commits changes skipWapValue.",
      "The protected parseParts(ByteArrayInputStream) signature is preserved, so subclasses and external callers keep working. Recursion inside the class calls the private overload.",
      "Only the frameworks/base copy of PduParser is modified. Other copies of the parser in the tree are not touched by this commit (see potential_variants)."
     ],
     "behavior_changes": [
      "parseParts now returns null, and the whole PduParser.parse fails, for PDUs with multipart/alternative nesting deeper than 30 levels.",
      "A part whose declared dataLength exceeds the bytes remaining in the stream now fails the parse with an error log. Previously the code allocated the full array and read whatever bytes were left.",
      "A nested multipart/alternative body that parses to null or to zero parts now fails the parse instead of throwing.",
      "New Log.e messages tagged LOG_TAG: 'parseParts: recursion depth limit exceeded', 'parseParts: dataLength ... exceeds available stream bytes', 'parseParts: nested multipart alternative parsing failed or empty'."
     ],
     "preconditions": [
      "Attacker-controlled MMS PDU bytes must reach PduParser.parse() in the frameworks/base copy. In the sibling forks shown by the index, parse() calls parseParts for MESSAGE_TYPE_SEND_REQ / MESSAGE_TYPE_RETRIEVE_CONF. The frameworks/base parse() body itself is not in the material.",
      "Per index search, packages/services/Mms/src/com/android/mms/service/MmsService.java:parsePduForAnyCarrier constructs PduParser(data, true/false) and calls parse() on raw PDU data, catching only RuntimeException. OutOfMemoryError and StackOverflowError are not RuntimeExceptions, so that catch does not cover those two failure modes.",
      "The PDU must contain a part with content type multipart/vnd.wap.multipart.alternative (for the recursion and child-part checks) or a part with an oversized declared dataLength (for the allocation check)."
     ],
     "per_major": {
      "14": "same fix: RESTRICT AUTOMERGE commit 8325de4e11e9 in android-security-14.0.0_r29, identical hunks (same blob 62eac7ac942a..ecab9562f72e) plus the new PduParserTest. Patched region fully present (8/8 lines).",
      "15": "same fix: RESTRICT AUTOMERGE cherry-pick b86eef8c2ffe in android-security-15.0.0_r17, identical hunks and test. Patched region fully present (8/8 lines).",
      "16": "same fix: RESTRICT AUTOMERGE commit a28fb127a57b in android-security-16.0.0_r8, identical hunks and test. Patched region fully present (8/8 lines).",
      "17": "same fix: RESTRICT AUTOMERGE commit 1c37e9ef69ea in android-security-17.0.0_r1. The PduParser.java base blob differs (ffd2a8b419bb), but the hunks are identical at the same line numbers. Patched region fully present (8/8 lines)."
     },
     "backport_notes": [
      "Single self-contained commit. The only production code touched is parseParts in telephony/common/com/google/android/mms/pdu/PduParser.java, and the hunks are identical from Android 14 through 17, so it should apply cleanly to any tree whose parseParts still matches the upstream shape. The new test file lives in tests/TelephonyCommonTests.",
      "Trees that carry their own copy of this parser (for example a forked mmslib PduParser) do not get the fix through this commit and need the same three checks applied by hand."
     ],
     "confidence": "high",
     "unknowns": [
      "Why the commit message describes a skipWapValue/parameter-skipping optimization that does not appear in the diff.",
      "The body of the frameworks/base PduParser.parse(), the code where dataLength is read, the non-positive dataLength branch, the full list of production callers, and whether any frameworks/base caller catches Error subclasses.",
      "Which process the crash occurs in and how the bulletin's Critical rating was derived. The material only states Critical / DoS."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55256": {
     "cve": "CVE-2026-55256",
     "summary": "Adds null checks in PduParser.parsePartHeaders so that a null return from parseWapString for the Content-Disposition token-text, the filename parameter, or the header/value pair checked against Content-Transfer-Encoding no longer causes a NullPointerException while parsing an MMS PDU.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parser (com.google.android.mms.pdu.PduParser); duplicated copy in platform/development samples/ApiDemos mmslib",
     "weakness": "Missing null check (unchecked return value from parseWapString leading to NullPointerException)",
     "root_cause": "In PduParser.parsePartHeaders, the byte[] returned by parseWapString(pduDataStream, TYPE_TEXT_STRING) was used without a null check in three places. (1) It was passed directly to part.setContentDisposition(...) in the token-text branch of Content-Disposition. (2) It was passed directly to part.setFilename(...) when the P_FILENAME parameter was present. (3) tempHeader was dereferenced via new String(tempHeader) in the Content-Transfer-Encoding comparison, and tempValue was then passed to part.setContentTransferEncoding(...). The added checks indicate that parseWapString can return null for some input, in which case parsing could throw a NullPointerException (bulletin type: DoS).",
     "fix_analysis": [
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (frameworks/base): in the Content-Disposition token-text branch, the result of parseWapString is stored in a local contentDisposition, and part.setContentDisposition is only called if it is non-null.",
      "Same function: the filename parameter result is stored in a local filename, and part.setFilename is only called if it is non-null.",
      "Same function: the condition changes from 'if (true == PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader)))' to 'if (null != tempHeader && null != tempValue && PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader)))'. tempHeader is no longer dereferenced, and tempValue is no longer stored, when either is null.",
      "samples/ApiDemos/src/com/example/android/mmslib/pdu/PduParser.java:parsePartHeaders (platform/development): the same three changes applied to the duplicated sample copy of the parser."
     ],
     "completeness": [
      "Covers exactly three parseWapString call sites inside parsePartHeaders (Content-Disposition token-text, filename parameter, Content-Transfer-Encoding header/value). The diff touches no other parseWapString call sites.",
      "The fix is applied to both copies of the parser in the material (frameworks/base telephony/common and development samples/ApiDemos).",
      "In the shown context, parseHeaders also calls parseWapString and then calls new String(bVal) without a null check, but only inside an 'if (LOCAL_LOGV)' block. This fix does not change that site.",
      "The fix does not change parseWapString itself and does not abort parsing on null. It skips setting the field instead."
     ],
     "behavior_changes": [
      "When parseWapString returns null for the Content-Disposition token-text or the filename parameter, the corresponding PduPart field is left unset and parsing continues instead of throwing NullPointerException.",
      "Content-Transfer-Encoding is now only recorded when both tempHeader and tempValue are non-null. Otherwise the header is ignored.",
      "No new error codes or return-value changes are visible in the diff."
     ],
     "preconditions": [
      "An MMS PDU must reach PduParser.parsePartHeaders containing a part with a Content-Disposition token-text, a filename parameter, or a Content-Transfer-Encoding header/value encoded such that parseWapString returns null.",
      "The material does not show the input conditions that make parseWapString return null, or the caller path that delivers attacker-controlled PDUs to this parser."
     ],
     "per_major": {
      "14": "same fix; resolved at android-security-14.0.0_r29, shown as platform/development 6aa9341a7695 (identical diff to the ApiDemos copy). The material does not show a frameworks/base commit for 14. The resolver reports 6/8 lines of the patched region present.",
      "15": "same fix; platform/frameworks/base e2b80df1360a and platform/development 59bbdb0dddcf in android-security-15.0.0_r17. Both are cherry-picks (Merged-In I4cad171d30b14f3b6fd9dd2a0ef6bb1e5320665d and Ia8ea444778992ac7a3c2cdfab855e3b1f7e9c53f). The resolver reports 6/8 lines of the patched region present.",
      "16": "same fix; platform/frameworks/base 01f51483eb2f in android-security-16.0.0_r8, identical diff to 15. The resolver reports 6/8 lines of the patched region present.",
      "17": "same fix; resolved at android-security-17.0.0_r1, shown as platform/development 21dbe0e7ba1f (identical diff). The material does not show a frameworks/base commit for 17. The resolver reports 6/8 lines of the patched region present."
     },
     "backport_notes": [
      "Two single-file commits, one per project (frameworks/base telephony/common PduParser.java and development samples/ApiDemos mmslib PduParser.java). The hunks are self-contained, and the same diff text appears on every shown branch (offsets 1726/1735/1769 in frameworks/base for 15 and 16, 1681/1690/1724 in development for 14, 15 and 17)."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact input conditions under which parseWapString returns null.",
      "Whether a frameworks/base commit exists for Android 14 and 17 (only the platform/development commit is shown for those majors).",
      "What the resolver's '6/8 lines of the patched region present' measurement means for each tag.",
      "Which process hosts the parser in the DoS scenario rated Critical by the bulletin.",
      "Where tempHeader comes from in parsePartHeaders (not visible in the diff context).",
      "The value of LOCAL_LOGV in each copy."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48564": {
     "cve": "CVE-2025-48564",
     "summary": "IntentForwarderActivity (in frameworks/base and in the IntentResolver module) now resolves the forwarded intent's MIME type once. It passes that same string to the IPackageManager.canForwardTo check and to a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload, so the type used in the check and the type used in resolution can no longer differ. The fix also sanitizes the intent selector on the resolver-launch path, and in frameworks/base it identifies the privileged caller with getLaunchedFromPackage() instead of getCallingPackage().",
     "component": "platform/frameworks/base (com.android.internal.app.IntentForwarderActivity, android.content.pm.PackageManager, ApplicationPackageManager, AbstractMultiProfilePagerAdapter) and platform/packages/modules/IntentResolver (com.android.intentresolver.IntentForwarderActivity, emptystate/CrossProfileIntentsChecker)",
     "weakness": "Time-of-check/time-of-use (double fetch) of the intent's resolved MIME type between the cross-profile forwarding check and activity resolution. Also incomplete sanitization of the intent selector, and caller identity taken from getCallingPackage().",
     "root_cause": "Before the fix, IntentForwarderActivity.canForwardInner() computed the type itself with intent.resolveTypeIfNeeded(contentResolver) and passed it to IPackageManager.canForwardTo(). After that check passed, onCreate() called mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId). This ended in ApplicationPackageManager.resolveActivityAsUser(), which called intent.resolveTypeIfNeeded(mContext.getContentResolver()) again on its own before mPM.resolveIntent(). The forwarding check and the target-activity resolution therefore used separately derived types that could differ. The commit message says: \"allowing the intent to declare its own type can lead to a security vulnerability if the intent changes its type after the IntentForwarder#canForward check\". The same commit fixes two secondary issues. The privilegedCallerAskedToSkipUserConsent decision used getCallingPackage(). And on the path that launches the resolver with EXTRA_SELECTED_PROFILE, only intentReceived went through sanitizeIntent(), not intentReceived.getSelector().",
     "fix_analysis": [
      "core/java/android/content/pm/PackageManager.java: adds two @hide overloads, resolveActivityAsUser(Intent, @Nullable String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, @Nullable String resolvedType, ResolveInfoFlags, int userId). The javadoc says resolvedType is 'Specified explicitly when the data type contained in the intent cannot be trusted'. The int-flags variant delegates to the ResolveInfoFlags variant, which throws UnsupportedOperationException unless a subclass overrides it.",
      "core/java/android/app/ApplicationPackageManager.java: implements the new overloads. resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) calls mPM.resolveIntent() with the caller-supplied resolvedType when it is non-null, and falls back to intent.resolveTypeIfNeeded(mContext.getContentResolver()) only when it is null. The existing resolveActivity/resolveActivityAsUser variants now route through it with resolvedType = null.",
      "core/java/com/android/internal/app/IntentForwarderActivity.java onCreate(): computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...), and then to mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId).",
      "IntentForwarderActivity.canForward() and canForwardInner(): the parameter changes from ContentResolver to String resolvedType. canForwardInner no longer calls intent.resolveTypeIfNeeded and passes the supplied resolvedType to packageManager.canForwardTo() for both the root intent and the selector. The ContentResolver import is removed.",
      "IntentForwarderActivity (hunk near line 239, in the method that reads EXTRA_SKIP_USER_CONFIRMATION): String callingPackage = getLaunchedFromPackage() replaces getCallingPackage() for the privilegedCallerAskedToSkipUserConsent check.",
      "IntentForwarderActivity (resolver-launch path, where sanitizeIntent(intentReceived) comes before putExtra(EXTRA_SELECTED_PROFILE)): adds sanitizeIntent(intentReceived.getSelector()) when a selector is present.",
      "IntentForwarderActivity.Injector / InjectorImpl: adds resolveActivityAsUser(Intent, String resolvedType, int flags, int userId), which calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId) through CompletableFuture.supplyAsync.",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents(): adapted to the new signature by passing intent.resolveTypeIfNeeded(mContentResolver).",
      "platform/packages/modules/IntentResolver dcb671e00d68: makes the matching changes in java/src/com/android/intentresolver/IntentForwarderActivity.java (type resolved once in onCreate, canForward/canForwardInner take resolvedType, selector sanitized on the EXTRA_SELECTED_PROFILE path, new Injector overload) and in emptystate/CrossProfileIntentsChecker.java (passes intent.resolveTypeIfNeeded(mContentResolver)). This module copy has no getCallingPackage change.",
      "Follow-up frameworks/base 323651e81585 (test fix): moves executor creation and the DEVICE_PROVISIONED read behind Injector.getExecutorService() and isDeviceProvisioned(), adds HIDE_OVERLAY_WINDOWS to the coretests manifest, and corrects the inverted assertNull/assertNotNull assertions that the security commit added to IntentForwarderActivityTest. The production change is limited to the injector refactor."
     ],
     "completeness": [
      "Covers both places where the forwarder itself uses the type: IPackageManager.canForwardTo (root intent and selector) and PackageManager.resolveActivityAsUser for the target user. Both now receive the single resolvedType string computed in onCreate.",
      "The selector is now checked with the root intent's resolvedType instead of its own resolveTypeIfNeeded result. canForwardInner no longer resolves the selector's own data type separately.",
      "Only the new explicit-type overload avoids re-resolution. The existing PackageManager.resolveActivity/resolveActivityAsUser variants still call intent.resolveTypeIfNeeded internally. Per index search, Telecomm CallIntentProcessor.maybeRedirectToIntentForwarderForPrivate (which receives its intent as a parameter) and CallsManager.maybeRedirectToIntentForwarder (which builds its own intent) still use the old overloads. The material does not show whether they are affected.",
      "The later start of newIntent in the target user (startActivityAsCaller) is not touched by the diff. The material does not show whether the system server derives the type again at that point.",
      "CrossProfileIntentsChecker (both copies) still resolves the type from the intent at the call site. It was only adapted to the new signature, not hardened.",
      "The getCallingPackage to getLaunchedFromPackage change appears only in the frameworks/base copy. The IntentResolver module copy shown has no equivalent hunk."
     ],
     "behavior_changes": [
      "IntentForwarderActivity now resolves the MIME type once, in onCreate. Before, canForwardInner resolved it for the root intent and again for the selector, and ApplicationPackageManager.resolveActivityAsUser resolved it once more.",
      "canForwardTo now evaluates selector intents with the parent intent's resolved type instead of their own.",
      "On the path that launches the resolver with EXTRA_SELECTED_PROFILE, the selector now also goes through sanitizeIntent.",
      "EXTRA_SKIP_USER_CONFIRMATION is now honored based on getLaunchedFromPackage() instead of getCallingPackage() (frameworks/base copy only). getCallingPackage() returns null unless the activity was started for a result, while getLaunchedFromPackage() reports the package that launched the activity (general platform knowledge).",
      "New hidden PackageManager.resolveActivityAsUser(Intent, String, int|ResolveInfoFlags, int) overloads. A PackageManager subclass that does not override the ResolveInfoFlags variant throws UnsupportedOperationException if it is called.",
      "After 323651e81585, Injector also has getExecutorService() and isDeviceProvisioned(). The default implementations keep the previous logic."
     ],
     "preconditions": [
      "A cross-profile intent filter forwards the intent to IntentForwarderActivity (FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE). This needs a device with a managed or parent/private profile relationship.",
      "The type is obtained through Intent.resolveTypeIfNeeded. The material does not detail how much of the type an attacker can control.",
      "Per index search, Telecomm (CallIntentProcessor, CallsManager) resolves intents that it expects to land on IntentForwarderActivity, and the resolver's CrossProfileIntentsChecker calls canForward."
     ],
     "per_major": {
      "14": "Same fix: platform/frameworks/base 042b52a8551f in android-security-14.0.0_r24. It makes the same PackageManager, ApplicationPackageManager, IntentForwarderActivity and AbstractMultiProfilePagerAdapter changes, and its diff context shows no isPrivateProfile branch. No IntentResolver module commit for 14 appears in the material. 6/8 lines of the patched region are present at android-security-14.0.0_r29; the material does not say why.",
      "15": "Same fix in two repos: frameworks/base bf9e58d7edb5 and IntentResolver dcb671e00d68 in android-security-15.0.0_r12, plus the test/injector follow-up frameworks/base 323651e81585 in android-security-15.0.0_r17. 6/8 lines of the patched region are present at r17; the material does not say why.",
      "16": "Same fix: IntentResolver 4e6cf5285d0b in android-security-16.0.0_r3, identical to the 15 module commit. The material does not show the matching frameworks/base commit for 16. 8/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "Resolved by change_id in android-security-17.0.0_r1. The material shows only the test/injector follow-up frameworks/base 323c8b968a88, which also removes the IntentForwarderActivityTest exclude filter in core/tests/coretests/Android.bp. 8/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Apply the frameworks/base commit first. The IntentResolver module's InjectorImpl calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId), a hidden PackageManager overload that the base commit introduces. The canForward signature change also means every caller has to be updated (AbstractMultiProfilePagerAdapter / CrossProfileIntentsChecker and any OEM callers).",
      "Injector implementations, including test injectors, need the new resolveActivityAsUser overload. The security commit's IntentForwarderActivityTest assertions are inverted (assertNull on a captured Intent that must be non-null). Only the later 323651e81585/323c8b968a88 commit corrects them, so pick that commit too if you run the test."
     ],
     "confidence": "high",
     "unknowns": [
      "The per-major status implies that a frameworks/base commit for Android 16 and the full Android 17 fix commit exist, but the material does not show them.",
      "The material does not show whether an IntentResolver module fix was applied for Android 14.",
      "The material does not state which of the three bug ids (403565650, 407763772, 397216638) maps to which sub-change.",
      "The diff and context do not show whether the later startActivityAsCaller path derives the MIME type again in the system server.",
      "The material does not say why only 6/8 lines of the patched region are present at the latest 14 and 15 tags.",
      "The material does not show the body of sanitizeIntent (what fields it strips)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48565": {
     "cve": "CVE-2025-48565",
     "summary": "IntentForwarderActivity now resolves the incoming intent's MIME type once and passes that fixed string through both the canForwardTo check and the target-user resolveActivityAsUser call (via new hidden PackageManager.resolveActivityAsUser(Intent, String, ...) overloads). It also uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-consent check, and sanitizes the intent selector (package/component/nested selector) everywhere sanitizeIntent is applied.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity, android.content.pm.PackageManager / android.app.ApplicationPackageManager)",
     "weakness": "Time-of-check/time-of-use on intent MIME type resolution (the type was derived again from the untrusted intent after the cross-profile forwarding check), plus incomplete intent sanitization (selector not sanitized on all paths) and a wrong caller identity source",
     "root_cause": "In IntentForwarderActivity.onCreate the forwarding decision and the later resolution each derived the intent type on their own. canForwardInner() called intent.resolveTypeIfNeeded(contentResolver) and passed the result to IPackageManager.canForwardTo(), while ApplicationPackageManager.resolveActivityAsUser() called intent.resolveTypeIfNeeded() again before mPM.resolveIntent() for the target user. The commit message states that letting the intent declare its own type is a vulnerability if the type changes after the canForward check, so the activity resolved in the target profile could be chosen with a type that canForwardTo never validated. In addition, the privileged EXTRA_SKIP_USER_CONFIRMATION path used getCallingPackage() rather than getLaunchedFromPackage() to identify the launcher. Finally, the intent selector was not sanitized (package/component cleared) on the launchResolverActivityWithCorrectTab / launchChooserActivityWithCorrectTab paths.",
     "fix_analysis": [
      "Commit bf9e58d7edb5 (android-security-15.0.0_r12), core/java/android/content/pm/PackageManager.java: adds hidden overloads resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The javadoc says resolvedType is given explicitly when the type in the intent cannot be trusted, and null falls back to Intent#resolveTypeIfNeeded. The base-class ResolveInfoFlags variant throws UnsupportedOperationException.",
      "Same commit, core/java/android/app/ApplicationPackageManager.java: resolveActivity/resolveActivityAsUser now all go through resolveActivityAsUser(Intent, String, ResolveInfoFlags, int), which passes the caller-supplied resolvedType to mPM.resolveIntent() and only calls intent.resolveTypeIfNeeded() when resolvedType is null.",
      "Same commit, core/java/com/android/internal/app/IntentForwarderActivity.java onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once and passes it to canForward(...) and to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). The canForward() and canForwardInner() signatures change from ContentResolver to String resolvedType, and canForwardInner no longer calls resolveTypeIfNeeded. Injector/InjectorImpl gain a resolveActivityAsUser(Intent, String, int, int) that calls PackageManager.resolveActivityAsUser(intent, resolvedType, flags, userId).",
      "Same commit, IntentForwarderActivity.java: the privileged skip-user-consent check now reads callingPackage from getLaunchedFromPackage() instead of getCallingPackage(). launchResolverActivityWithCorrectTab also sanitizes intentReceived.getSelector() if present.",
      "Same commit, core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents: adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver).",
      "Commit 323651e81585 (android-security-15.0.0_r17): touches production code only for testability. IntentForwarderActivity gets Injector.getExecutorService() and Injector.isDeviceProvisioned() indirections (same behavior in InjectorImpl). In the test, the backwards assertNull assertions from the first commit are corrected, HIDE_OVERLAY_WINDOWS is added to the test manifest, and the test is re-enabled (per the commit message).",
      "Commit 577f0333c2c3 (android-security-15.0.0_r17), IntentForwarderActivity.java sanitizeIntent: now also clears the selector's package, component and nested selector (selector.setSelector(null)) in place. The separate selector sanitization in launchResolverActivityWithCorrectTab and in canForward is removed because sanitizeIntent now covers it. This extends selector sanitization to every sanitizeIntent call site, including launchChooserActivityWithCorrectTab (both intentReceived and the EXTRA_INTENT innerIntent)."
     ],
     "completeness": [
      "The single type resolution covers the onCreate path: canForwardTo for the root intent and for the selector, and the target-user resolveActivityAsUser, all use the same resolvedType string. The selector check reuses the root intent's resolvedType rather than resolving the selector's own data.",
      "launchResolverActivityWithCorrectTab still calls mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, callingUserId) without a resolvedType, so that resolution derives the type from the intent again. In the r17 code its result only selects which user hosts the resolver (callingUserId vs targetUserId).",
      "Other callers of PackageManager.resolveActivityAsUser without a resolvedType keep the old behavior (type derived from the intent); the fix only changes callers that opt in to the new overload.",
      "After 577f0333c2c3, sanitizeIntent clears package/component on the intent and on its selector and drops a nested selector; it does not touch other intent fields (data, type, extras, flags).",
      "The Android 14/15/16 latest tags report 6/8 lines of the first commit's patched region present, which is consistent with 577f0333c2c3 having removed the explicit selector-sanitization lines (shown for 15 r17). Android 17 r1 shows 8/8; why it differs is not shown in the material."
     ],
     "behavior_changes": [
      "New hidden PackageManager API surface: resolveActivityAsUser(Intent, String, int, int) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int). The base-class ResolveInfoFlags variant throws UnsupportedOperationException unless overridden, so PackageManager subclasses/mocks that do not override it will throw if these overloads are called.",
      "The IntentForwarderActivity.canForward static signature changed (ContentResolver parameter replaced by String resolvedType), and AbstractMultiProfilePagerAdapter was updated to match. The Injector interface gained resolveActivityAsUser(Intent, String, int, int), getExecutorService() and isDeviceProvisioned().",
      "Target-user resolution now uses the type resolved at forwarding-check time; a type that would differ on a second resolveTypeIfNeeded call no longer influences resolution.",
      "The EXTRA_SKIP_USER_CONFIRMATION privileged check now uses getLaunchedFromPackage() instead of getCallingPackage().",
      "Forwarded intents and chooser/resolver inner intents have their selector's package/component removed and any nested selector dropped."
     ],
     "preconditions": [
      "The device has a profile pair (managed profile or profile parent; a private-profile branch exists on 15+) with cross-profile intent filters such that the caller's intent resolves to IntentForwarderActivity (FORWARD_INTENT_TO_PARENT / FORWARD_INTENT_TO_MANAGED_PROFILE).",
      "The intent reaches IntentForwarderActivity.onCreate. The type aspect concerns an intent whose type changes after the canForward check (per the commit message); the selector aspect concerns a selector carrying a package/component.",
      "Per index search, Telecom (CallIntentProcessor.maybeRedirectToIntentForwarderForPrivate, CallsManager.maybeRedirectToIntentForwarder) resolves and starts intents that go to the intent forwarder, and AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker calls the static IntentForwarderActivity.canForward directly."
     ],
     "per_major": {
      "14": "adapted fix: commit 042b52a8551f in android-security-14.0.0_r24 carries the same PackageManager/ApplicationPackageManager API addition and IntentForwarderActivity changes, adapted to 14's code (no private-profile branch in onCreate, no singleTabOnly in launchResolverActivityWithCorrectTab). Latest tag r29 shows 6/8 lines of the region, consistent with the selector-sanitization consolidation having landed too, though that commit is not shown for 14.",
      "15": "same fix: bf9e58d7edb5 in android-security-15.0.0_r12, followed in android-security-15.0.0_r17 by 323651e81585 (test/injector) and 577f0333c2c3 (selector sanitization consolidated into sanitizeIntent).",
      "16": "same fix: 28579dff4305 in android-security-16.0.0_r3 matches the 15 commit in content (private-profile branch present). Latest tag r8 shows 6/8 lines, consistent with the later selector consolidation, which is not shown explicitly.",
      "17": "resolved by change_id at android-security-17.0.0_r1 via 323c8b968a88 (the test/injector commit, which also re-enables IntentForwarderActivityTest in core/tests/coretests/Android.bp). The patched region of the main API/type fix is present in that tag (8/8 lines). Whether the sanitizeIntent selector consolidation (577f0333c2c3) is in 17 r1 is not shown."
     },
     "backport_notes": [
      "Apply in order: bf9e58d7edb5 first (the PackageManager/ApplicationPackageManager overloads are required by IntentForwarderActivity and AbstractMultiProfilePagerAdapter in the same commit, and any out-of-tree PackageManager subclass or canForward caller must be updated to the new signatures). Then apply 323651e81585 (not security-relevant itself, but 577f0333c2c3 is based on its result) and 577f0333c2c3. On branches without the private-profile branch or singleTabOnly, follow the Android 14 variant 042b52a8551f.",
      "Per index search, packages/modules/IntentResolver has its own IntentForwarderActivity copy, which these frameworks/base commits do not touch; check whether it is used on the device."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the specific component/type combination used in the reported issue, the exact condition under which the type changes between check and use, or the exact privilege gained; it only shows that the bulletin rates it High EoP.",
      "Whether the selector-sanitization consolidation commit (577f0333c2c3) is present in the Android 14, 16 and 17 latest tags is not shown explicitly (only inferred from the 6/8 vs 8/8 line counts).",
      "Why getCallingPackage() was insufficient in the skip-consent path is not explained beyond 'Check the launch package rather than calling package'.",
      "Whether the IntentResolver module's IntentForwarderActivity copy is reachable or fixed separately is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48566": {
     "cve": "CVE-2025-48566",
     "summary": "IntentForwarderActivity now resolves the incoming intent's MIME type once and passes that string both to the canForwardTo check and to a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload, so the type used for the cross-profile permission check is the same one used for the actual target resolution; it also uses getLaunchedFromPackage() for the privileged skip-confirmation check and sanitizes the received intent's selector before launching the resolver.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity) and PackageManager/ApplicationPackageManager hidden API",
     "weakness": "TOCTOU / inconsistent re-evaluation of attacker-influenced input (intent type resolved separately for the security check and for the use), plus incomplete intent sanitization (selector) and wrong caller identity source",
     "root_cause": "In IntentForwarderActivity.onCreate the forwardability check and the target resolution each derived the intent's type independently. canForwardInner called intent.resolveTypeIfNeeded(contentResolver) and passed the result to IPackageManager.canForwardTo; afterwards Injector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which called intent.resolveTypeIfNeeded again before mPM.resolveIntent. Per the commit message, an intent whose type changes after the canForward check could therefore pass the cross-profile check with one type and be resolved and forwarded with another. Two related issues fixed in the same change: the EXTRA_SKIP_USER_CONFIRMATION privileged-caller check used getCallingPackage() instead of getLaunchedFromPackage(), and in the path that launches the resolver with intentReceived only the top-level intent was sanitized while intentReceived.getSelector() was not (canForward sanitized the selector only on its private copy forwardIntent).",
     "fix_analysis": [
      "Commit bf9e58d7edb5 (android-security-15.0.0_r12), core/java/android/content/pm/PackageManager.java: adds two @hide overloads resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId); the int-flags one delegates to the ResolveInfoFlags one, which throws UnsupportedOperationException by default. Javadoc states resolvedType is 'specified explicitly when the data type contained in the intent cannot be trusted'.",
      "core/java/android/app/ApplicationPackageManager.java: implements the new overloads; all pre-existing resolveActivity/resolveActivityAsUser overloads now delegate with resolvedType == null, and the final implementation calls mPM.resolveIntent with resolvedType if non-null, otherwise intent.resolveTypeIfNeeded(mContext.getContentResolver()).",
      "core/java/com/android/internal/app/IntentForwarderActivity.java onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...) and then to mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId), so the check and the resolution use the same type string.",
      "IntentForwarderActivity.canForward / canForwardInner: parameter ContentResolver replaced by String resolvedType; canForwardInner no longer calls intent.resolveTypeIfNeeded and passes the supplied resolvedType to packageManager.canForwardTo for both the root intent and its selector. ContentResolver import removed.",
      "IntentForwarderActivity (skip-confirmation path): String callingPackage = getCallingPackage() changed to getLaunchedFromPackage() for the privilegedCallerAskedToSkipUserConsent computation.",
      "IntentForwarderActivity (resolver launch path, around sanitizeIntent(intentReceived)): adds 'if (intentReceived.getSelector() != null) sanitizeIntent(intentReceived.getSelector())' so the selector of the intent handed to the resolver is sanitized too.",
      "IntentForwarderActivity.Injector interface and the activity's Injector implementation: adds resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) returning CompletableFuture.supplyAsync(() -> getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId)); the old overload is kept.",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents: adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver).",
      "core/tests/coretests/.../IntentForwarderActivityTest.java: TestInjector implements the new overload; a test now verifies canForwardTo is called twice (root intent then selector) and that both are sanitized (getPackage() null). Two assertions were written backwards (assertNull on the captured intents).",
      "Commit 323651e81585 (android-security-15.0.0_r17, Flag: EXEMPT TEST_ONLY): fixes the backwards assertions (assertNotNull), removes a 30 s awaitTermination in the test wrapper, injects ExecutorService and isDeviceProvisioned through Injector (the production Injector implementation keeps Executors.newSingleThreadExecutor() and the Settings.Global.DEVICE_PROVISIONED read), adds HIDE_OVERLAY_WINDOWS to the test manifest. No change to production security logic."
     ],
     "completeness": [
      "Covers the IntentForwarderActivity.onCreate path fully: a single resolvedType is used for canForwardTo (root and selector) and for the subsequent resolveActivityAsUser, removing the second independent type resolution.",
      "The selector check in canForward now receives the root intent's resolvedType rather than a type resolved from the selector itself; the selector's own data type is no longer separately resolved anywhere in this class.",
      "The selector of intentReceived is now sanitized in the resolver-launch path; previously only canForward's private copy had its selector sanitized.",
      "The new PackageManager overloads default to throwing UnsupportedOperationException; only ApplicationPackageManager implements them in this diff. Any other PackageManager subclass returned by Injector.getPackageManager() would throw.",
      "AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker still derives the type from the intent at call time (signature adaptation only); no second resolution step is shown there, so no equivalent gap is evident from the material.",
      "The copy of canForward/canForwardInner in packages/modules/IntentResolver (per index search) still takes a ContentResolver and resolves the type inside canForwardInner; it is not touched by this frameworks/base change."
     ],
     "behavior_changes": [
      "New hidden API: PackageManager.resolveActivityAsUser(Intent, String resolvedType, int|ResolveInfoFlags, int userId); existing overloads unchanged in behavior (null resolvedType falls back to intent.resolveTypeIfNeeded).",
      "IntentForwarderActivity.canForward static signature changed from (..., IPackageManager, ContentResolver) to (..., IPackageManager, String resolvedType); callers in frameworks/base must pass a type string.",
      "The selector's canForwardTo check now uses the root intent's resolved type instead of the selector's own resolved type.",
      "Privileged skip-user-confirmation decision is based on getLaunchedFromPackage() instead of getCallingPackage().",
      "The selector of the intent forwarded to the resolver UI is sanitized (same sanitizeIntent as the top-level intent).",
      "Second commit: executor and device-provisioned lookup are obtained via Injector; production behavior unchanged."
     ],
     "preconditions": [
      "An intent reaches IntentForwarderActivity (FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE), i.e. a device with a managed or other profile where cross-profile intent filters route the intent to the forwarder; Telecomm's CallsManager.maybeRedirectToIntentForwarder and CallIntentProcessor.maybeRedirectToIntentForwarderForPrivate are examples of such routing (per index search).",
      "The intent's type is not fixed in the intent but derived via Intent.resolveTypeIfNeeded, so that the value can differ between the canForward check and the later resolution (as described in the commit message).",
      "For the skip-confirmation issue: an intent carrying EXTRA_SKIP_USER_CONFIRMATION where the caller identity differs between getCallingPackage() and getLaunchedFromPackage()."
     ],
     "per_major": {
      "14": "Same fix, lightly adapted: 042b52a8551f in android-security-14.0.0_r24 (no private-profile branch, resolver path ends in startActivityAsCaller(intentReceived, ...)); test diff keeps the old assertEquals line and the backwards assertNull assertions. At android-security-14.0.0_r29 6/8 lines of the patched region are present.",
      "15": "Same fix: bf9e58d7edb5 in android-security-15.0.0_r12, followed by test-only commit 323651e81585 in android-security-15.0.0_r17 that corrects the backwards test assertions and moves executor/provisioning behind Injector. 6/8 lines of the patched region present at r17.",
      "16": "Same fix: 28579dff4305 in android-security-16.0.0_r3, identical in content to the 15 commit including the backwards test assertions. 6/8 lines present at android-security-16.0.0_r8.",
      "17": "Resolved by change_id in android-security-17.0.0_r1 (8/8 lines present); the material shows only the follow-up test-fix commit 323c8b968a88 for 17, which additionally removes IntentForwarderActivityTest from exclude_filters in core/tests/coretests/Android.bp."
     },
     "backport_notes": [
      "Apply Change-Id I8453f25ed5ddd938718f4d2be4983c881778f281 first (it changes the canForward signature, so AbstractMultiProfilePagerAdapter must be updated in the same step), then optionally Iade781e0758c477ca888bd1541e0c53e2dee3761 which is test-only but fixes assertions that cannot pass as written. Forks with their own PackageManager subclasses or additional canForward callers need to add the new overload / pass a resolved type; per the diff the new PackageManager overloads are concrete (the ResolveInfoFlags one throws UnsupportedOperationException by default), so other subclasses compile unchanged but throw if the new overload is called on them.",
      "Trees that use the IntentResolver mainline module's own IntentForwarderActivity (packages/modules/IntentResolver, per index search) still have the ContentResolver-based canForward/canForwardInner; this frameworks/base commit does not touch that copy."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact mechanism by which the intent type changes between the check and the resolution is only described generically in the commit message; the material does not show it.",
      "Which 2 of the 8 patched-region lines are missing at 14 r29, 15 r17 and 16 r8, and why; whether the test-only commit 323651e81585 (or its equivalent) was also applied to Android 14 and 16 is not shown.",
      "Whether the IntentResolver module's IntentForwarderActivity.onCreate has the same double-resolution pattern; only its canForward/canForwardInner are shown by the index.",
      "Which of the three bug ids (403565650, 407763772, 397216638) maps to which of the three changes bundled in the commit."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0010": {
     "cve": "CVE-2026-0010",
     "summary": "Replaces an sprintf(\"%lu\", (unsigned long)fd) into a fixed 16-byte stack buffer with String8::format(\"%d\", fd) when BnDrmManagerService::onTransact stores the \"FileDescriptorKey\" value of an incoming DRM info request in drmserver.",
     "component": "platform/frameworks/av, drm/common (IDrmManagerService binder server side, drmserver)",
     "weakness": "Likely stack-based buffer overflow (sprintf into a fixed 16-byte buffer, using a cast and format that can produce more digits than the buffer holds)",
     "root_cause": "In drm/common/IDrmManagerService.cpp, BnDrmManagerService::onTransact, in the transaction handler that builds drmInfoRequest (likely ACQUIRE_DRM_INFO, inferred from the context), handled a parcel key equal to \"FileDescriptorKey\" by calling data.readFileDescriptor() and formatting the int result with sprintf(buffer, \"%lu\", (unsigned long)fd) into char buffer[16]. Inferred from the diff: if fd were negative, casting it to unsigned long on a 64-bit build gives a value of up to 20 decimal digits. Those digits plus the NUL terminator would exceed the 16-byte stack buffer. The material does not show when readFileDescriptor() returns a negative value.",
     "fix_analysis": [
      "drm/common/IDrmManagerService.cpp:BnDrmManagerService::onTransact: removes the local char buffer[16] and the sprintf call.",
      "Same function: the fd read via data.readFileDescriptor() is now converted with String8::format(\"%d\", fd), which prints it as a signed int into a dynamically sized String8. The result is stored with drmInfoRequest->put(key, ...) as before.",
      "The surrounding logic is unchanged: the key comparison, and the else branch that reads a string value and maps \"NULL\" to an empty string."
     ],
     "completeness": [
      "The fix removes the fixed-size buffer rather than adding a bounds check, so the formatted fd no longer goes into a stack buffer of limited size.",
      "The fd value itself is not validated: whatever readFileDescriptor() returns is still stored as a string in the DrmInfoRequest.",
      "Only the \"FileDescriptorKey\" branch of this one handler is touched. The material does not show whether other sprintf-into-fixed-buffer sites exist elsewhere in the file. The PROCESS_DRM_INFO case shown in the context reads its values with readString8 and has no such pattern."
     ],
     "behavior_changes": [
      "For a non-negative fd the stored string is the same decimal value as before.",
      "For a negative fd, if one can occur, the stored value changes from a large unsigned decimal to a signed decimal (e.g. \"-1\").",
      "No new error codes or rejections: the transaction still proceeds."
     ],
     "preconditions": [
      "The caller can reach the drmserver IDrmManagerService binder interface and send the transaction that builds drmInfoRequest in BnDrmManagerService::onTransact (the exact transaction code is inferred, not shown).",
      "The transaction's key/value list contains the key \"FileDescriptorKey\".",
      "Inferred from the diff: the overflow needs readFileDescriptor() to return a negative value and a 64-bit build, so that the unsigned long cast produces more than 15 digits."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/av 8bf16f1ae904 in android-security-14.0.0_r25. The diff is identical to the Android 15 commit; only the file index hashes differ.",
      "15": "same fix: platform/frameworks/av c8713a065232 in android-security-15.0.0_r13, cherry-picked from internal changes 358b3457 and 7c5d19a1 (Change-Id I6efecac362757c10b2308d020440eac5edc5b873).",
      "16": "same fix: platform/frameworks/av ebdbf918dd87 in android-security-16.0.0_r4, identical diff.",
      "17": "not affected (per the bulletin, which does not list Android 17). android-security-17.0.0_r1 has 5/8 lines of the patched region present, the same count as in the latest 14/15/16 tags."
     },
     "backport_notes": [
      "Single self-contained hunk with no dependencies. The Android 14 and 16 commits have the same diff as the 15 one, so it should apply to any tree that still has the char buffer[16]/sprintf sequence in the \"FileDescriptorKey\" branch. String8::format is part of libutils (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show the case label enclosing line 1001. ACQUIRE_DRM_INFO is inferred from the order of the code and the drmInfoRequest variable.",
      "When data.readFileDescriptor() can return a negative value is not shown.",
      "Which callers (SELinux domains, permissions) can transact with drmserver is not shown.",
      "Why two internal cherry-pick sources are listed for a one-hunk change is not shown.",
      "Which 3 of the 8 patched-region lines are missing in each latest tag is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0054": {
     "cve": "CVE-2026-0054",
     "summary": "Adds an onTransact-level caller check to SystemUI's WalletContextualLocationsService binder so that only privileged apps holding the android.app.role.SYSTEM_UI_INTELLIGENCE role can invoke IWalletContextualLocationsService methods; all other callers get a SecurityException.",
     "component": "platform/frameworks/base, SystemUI wallet (packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt)",
     "weakness": "missing permission / caller authorization check on a binder interface",
     "root_cause": "WalletContextualLocationsService (a LifecycleService in SystemUI that sits between QuickAccessWalletService and ContextualCardManager in PCC) returned an IWalletContextualLocationsService.Stub. Its methods addWalletCardsUpdatedListener and onWalletContextualLocationsStateUpdated passed calls straight to internal handlers without checking the calling identity. So any process that could bind to the service could register an IWalletCardsUpdatedListener or push store-location state into the controller. The bulletin classifies the result as information disclosure (ID), High.",
     "fix_analysis": [
      "WalletContextualLocationsService.kt: the anonymous IWalletContextualLocationsService.Stub now overrides onTransact(code, data, reply, flags). Before handing off to super.onTransact it calls isCallerAllowed() and throws SecurityException(\"Caller is not allowed.\") if that returns false. Because the gate is in onTransact, one check covers every AIDL method on the stub.",
      "WalletContextualLocationsService.kt: the new private isCallerAllowed() first reads Binder.getCallingUid(), then clears the calling identity (restored in finally) and looks up packageManager.getPackagesForUid(uid). It returns false if there are no packages. It then fetches RoleManager.getRoleHolders(\"android.app.role.SYSTEM_UI_INTELLIGENCE\") (an empty list if RoleManager is null). It returns true only if some package of the caller UID is in the role holder list and also has ApplicationInfo.isPrivilegedApp() true. NameNotFoundException counts as false.",
      "The companion object gains a ROLE_SYSTEM_UI_INTELLIGENCE constant, and imports for RoleManager, PackageManager, Binder and Parcel are added. The commit message calls this a quick fix and says a dedicated permission should replace it later. STS tests are in a separate change (I4c59ae71f66035d2aeeb9b9e4dd13d7911e5fae5), not in this commit."
     ],
     "completeness": [
      "Covers all transactions on the IWalletContextualLocationsService stub, because the check is in onTransact rather than in individual methods.",
      "Authorization is based on the role plus privileged-app status, not on a manifest permission. The commit itself calls this an interim measure and says a new permission should replace it. Any privileged app that holds SYSTEM_UI_INTELLIGENCE is accepted.",
      "The check runs at runtime inside the service process. The diff does not change the manifest, so whether the service is exported and who can bind are unchanged. Callers that are not allowed can still bind, but their transactions are rejected.",
      "This commit touches no other binder surfaces or sibling wallet services. The material does not show whether any exist."
     ],
     "behavior_changes": [
      "Any transaction from a caller that is not a privileged app holding android.app.role.SYSTEM_UI_INTELLIGENCE now throws SecurityException in onTransact instead of being processed.",
      "Each incoming transaction now does PackageManager and RoleManager lookups under cleared calling identity.",
      "If RoleManager is unavailable, the role holder list is treated as empty and all callers are rejected."
     ],
     "preconditions": [
      "The caller must be able to bind to WalletContextualLocationsService in SystemUI and get its IWalletContextualLocationsService binder. The material does not show the export or bind conditions.",
      "Before the fix, addWalletCardsUpdatedListener and onWalletContextualLocationsStateUpdated had no caller identity, role or privilege requirement."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base a83edee34b0d in android-security-14.0.0_r28. Same onTransact/isCallerAllowed logic, with the binder stub block re-indented and restructured to fit the 14 file. 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base 3717e022bc2a in android-security-15.0.0_r16, +46 lines in WalletContextualLocationsService.kt. 8/8 patched lines present at android-security-15.0.0_r17.",
      "16": "same fix: platform/frameworks/base a8f3c0ccb472 in android-security-16.0.0_r7. Identical logic; only the import context differs (DeadObjectException is already imported). 8/8 patched lines present at android-security-16.0.0_r8.",
      "17": "not affected (the bulletin does not list it). The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Each branch has one self-contained commit. The only adaptation was re-formatting the binder stub block on Android 14. STS tests are in a separate change, I4c59ae71f66035d2aeeb9b9e4dd13d7911e5fae5, and the fix does not depend on them."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether WalletContextualLocationsService is exported in the SystemUI manifest, and what bind restrictions (if any) existed before the fix.",
      "Exactly what information is disclosed. The bulletin only gives the type ID, and the material does not show what IWalletCardsUpdatedListener callbacks contain.",
      "Whether the AIDL methods are oneway, which affects how the SecurityException reaches the caller.",
      "Whether a dedicated permission has since replaced this role-based check on any branch."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0065": {
     "cve": "CVE-2026-0065",
     "summary": "The \"process bound by foreground uid\" background activity launch (BAL) exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned (non-PiP) window, via a new ActivityTaskManagerService.hasActiveVisibleNotPinnedWindow predicate, while FGS-start checks keep using the original any-visible-window predicate.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager background activity launch (BAL) controls",
     "weakness": "Insufficient authorization check (BAL exemption accepted a pinned/PiP window as \"foreground\")",
     "root_cause": "BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed grants BAL_ALLOW_VISIBLE_WINDOW when isBoundByForegroundUid() finds any UID in mBalOptInBoundClientUids (clients that bound the process with Context.BIND_ALLOW_ACTIVITY_STARTS) for which mUidHasActiveVisibleWindowPredicate is true. That predicate was ActivityTaskManagerService.hasActiveVisibleWindow, which returns true for any visible non-toast window, including an activity in pinned (picture-in-picture) windowing mode. A PiP window of the binding client was therefore enough to allow a background activity start. This did not match regular BAL behavior, where the process's own pinned task is not treated as foreground (see testForegroundTaskBlockedIfPinned). On Android 15 the appSwitchState check on this exemption was also gated behind Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid.",
     "fix_analysis": [
      "Commit 3d4a174f1def, services/core/java/com/android/server/wm/ActivityTaskManagerService.java: adds hasActiveVisibleNotPinnedWindow(int uid). It returns true if mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity(uid) or mActiveUids.hasNonAppVisibleWindow(uid) is true. The only difference from hasActiveVisibleWindow is that it calls hasVisibleNotPinnedActivity instead of hasVisibleActivity.",
      "Same commit, services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java: adds a second constructor parameter and the field mUidHasActiveVisibleNotPinnedWindowPredicate. isBoundByForegroundUid now takes an IntPredicate parameter and tests each UID in mBalOptInBoundClientUids against it. areBackgroundActivityStartsAllowed passes mUidHasActiveVisibleWindowPredicate when isCheckingForFgsStart is true and mUidHasActiveVisibleNotPinnedWindowPredicate otherwise. The appSwitchState != APP_SWITCH_DISALLOW condition now always applies: the Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid gate and the com.android.window.flags.Flags import are removed.",
      "Same commit, services/core/java/com/android/server/wm/WindowProcessController.java constructor: passes atm::hasActiveVisibleNotPinnedWindow as the new second argument to BackgroundLaunchProcessController.",
      "Same commit, services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java: adds the mHasActiveNotPinnedVisibleWindow set, updates the constructor calls and renames mInPinnedWindoMode to mInPinnedWindowMode. It splits testBoundByForeground into testBoundByForegroundFgs (client in mHasActiveVisibleWindow) and testBoundByForeground (client in mHasActiveNotPinnedVisibleWindow). Both expect BAL_ALLOW_VISIBLE_WINDOW.",
      "Commit 18d845a1ee4b (test-only), BackgroundLaunchProcessControllerTests.testBoundByForegroundFgs: sets mIsCheckingForFgsStart = true so the test actually runs the FGS path, which uses the any-visible-window predicate. It changes no production code."
     ],
     "completeness": [
      "The fix covers only the bound-by-foreground-UID exemption in BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed. The pinned-window distinction is applied only through the predicate passed to isBoundByForegroundUid.",
      "FGS-start checks (isCheckingForFgsStart true, reached via WindowProcessController.areBackgroundFgsStartsAllowed per index search) intentionally keep the original hasActiveVisibleWindow predicate. A client with only a PiP window therefore still allows FGS starts, by design.",
      "hasActiveVisibleNotPinnedWindow still falls back to mActiveUids.hasNonAppVisibleWindow(uid), so non-app visible windows of the client UID count as foreground exactly as before. Only app activity visibility is narrowed.",
      "ActivityTaskManagerService.hasActiveVisibleWindow itself is unchanged. Any other consumers of it are outside the material and are not changed by this fix.",
      "The second commit only corrects a test and does not change security behavior."
     ],
     "behavior_changes": [
      "A process whose only BAL justification is a BIND_ALLOW_ACTIVITY_STARTS client UID whose only visible activity is in pinned (PiP) mode can no longer start activities from the background under the 'process bound by foreground uid' verdict.",
      "On Android 15, the appSwitchState != APP_SWITCH_DISALLOW requirement for this exemption is now always enforced instead of depending on Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid. On Android 14 this appSwitchState requirement is new for the exemption.",
      "The BackgroundLaunchProcessController constructor signature changes (one extra IntPredicate), which affects any out-of-tree callers or tests."
     ],
     "preconditions": [
      "The target process must have been bound by a client UID with Context.BIND_ALLOW_ACTIVITY_STARTS (recorded via addBoundClientUid into mBalOptInBoundClientUids).",
      "That client UID's only visible activity must be in pinned (PiP) windowing mode (hasActiveVisibleWindow true, hasVisibleNotPinnedActivity false), with no other qualifying visible window.",
      "The check must be a BAL (activity start) check, not an FGS start check (isCheckingForFgsStart false), and appSwitchState must not be APP_SWITCH_DISALLOW.",
      "No earlier exemption in areBackgroundActivityStartsAllowed applies (otherwise the verdict is decided before this branch)."
     ],
     "per_major": {
      "14": "Adapted fix (407d7b083010 in android-security-14.0.0_r28). It has the same ATMS.hasActiveVisibleNotPinnedWindow, predicate selection and WindowProcessController wiring. It also adds the appSwitchState != APP_SWITCH_DISALLOW condition to the exemption, which the pre-fix code did not have, and keeps the older logStartAllowedAndReturnCode return style. The shown diff has no test changes. Patched region 8/8 present at android-security-14.0.0_r29.",
      "15": "Fix as analysed (3d4a174f1def in android-security-15.0.0_r16), plus the test-only follow-up 18d845a1ee4b in android-security-15.0.0_r17. It removes the Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid gate. Patched region 8/8 present at android-security-15.0.0_r17.",
      "16": "Adapted fix (66c209d1f65f in android-security-16.0.0_r7). The predicate is chosen from checkConfiguration.isCheckingForFgsStart inside the existing checkConfiguration.checkVisibility && appSwitchState condition. The verdict is BAL_ALLOW_BOUND_BY_FOREGROUND when balImprovedMetrics() is true, else BAL_ALLOW_VISIBLE_WINDOW. Tests use BalCheckConfiguration and add mFgsCheckConfiguration. Patched region 8/8 present at android-security-16.0.0_r8.",
      "17": "Not affected (Android 17 is not listed by the bulletin). Patched region 8/8 present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply 3d4a174f1def first. 18d845a1ee4b is a test-only follow-up that depends on the testBoundByForegroundFgs test added by the first commit. The fix relies on VisibleActivityProcessTracker.hasVisibleNotPinnedActivity(int) already existing (the diff does not add it), so check that it is present in the target tree.",
      "The condition around the exemption differs per branch (A14: no appSwitchState check; A15: flag-gated appSwitchState check; A16: BalCheckConfiguration with checkVisibility), so the areBackgroundActivityStartsAllowed hunk must be adapted by hand. The ATMS and WindowProcessController hunks have the same content across branches."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of VisibleActivityProcessTracker.hasVisibleNotPinnedActivity (not in the material), including how it treats a UID that has both pinned and non-pinned visible activities.",
      "Whether Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid was enabled on Android 15 devices before the fix (the fix makes the check unconditional either way).",
      "Which caller path or scenario was used to report the issue; the bulletin only states High severity EoP."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28581": {
     "cve": "CVE-2026-28581",
     "summary": "Telecom's CallIntentProcessor.fixInitiatingUserIfNecessary now decides whether a managed (work) profile has a dialer by asking RoleManager for an enabled ROLE_DIALER holder in that profile, instead of checking whether any activity handles ACTION_DIAL, so calls from a work profile without a real dialer are correctly re-attributed to the parent user.",
     "component": "platform/packages/services/Telecomm, CallIntentProcessor (outgoing call intent processing, work profile handling)",
     "weakness": "Incorrect capability check (logic error): presence of a work-profile dialer was inferred from ACTION_DIAL intent resolution, which could return a false positive, leading to wrong initiating-user attribution of the call",
     "root_cause": "In src/com/android/server/telecom/CallIntentProcessor.java, fixInitiatingUserIfNecessary() used DefaultDialerManager.getInstalledDialerApplications(context, initiatingUser.getIdentifier()).size() == 0 to detect that a managed profile has no dialer, in which case the call is treated as initiated from the parent user. That helper only checks whether something resolves the ACTION_DIAL intent. Per the commit message, a regression in PackageManager#queryIntentActivitiesAsUser made the \"android\" package (system server) appear as an ACTION_DIAL handler in the work profile even when nothing there handles it, so noDialerInstalled was false and the call was not redirected to the parent profile. The first replacement (RoleManager.getRoleHolders(ROLE_DIALER).size() > 0) was itself insufficient on Android 14. Per the second commit message, getQualifiedDefaultHolderPackageName resolved the role's default holder against the system user, which falsely indicated that the default system dialer role was active in the work profile; the fix responds by checking that the package is enabled in the work profile.",
     "fix_analysis": [
      "Commit 34079b659664 (15.0.0_r16), CallIntentProcessor.java: adds private static doesUserHaveDialerRoleHolder(Context, UserHandle), which does context.createContextAsUser(user, 0), gets RoleManager from that user context and returns roleManager.getRoleHolders(RoleManager.ROLE_DIALER).size() > 0.",
      "Same commit, CallIntentProcessor.fixInitiatingUserIfNecessary: replaces the DefaultDialerManager.getInstalledDialerApplications(...).size() == 0 check with noDialerInstalled = !doesUserHaveDialerRoleHolder(context, initiatingUser). The rest of the branch (resolving the parent user via UserManager and rewriting the initiating user) is unchanged.",
      "Commit ec63a79133e6 (15.0.0_r17), CallIntentProcessor.doesUserHaveDialerRoleHolder: hardens the role check. It returns false if getRoleHolders returns null or an empty list. Otherwise it takes roleHolders.get(0), calls userContext.getPackageManager().getApplicationInfo(dialerPackage, 0) in the work profile's context and returns info.enabled; a PackageManager.NameNotFoundException yields false. It also adds imports for ApplicationInfo and java.util.List."
     ],
     "completeness": [
      "Only the fixInitiatingUserIfNecessary path in CallIntentProcessor is changed. These commits do not touch the DefaultDialerManager.getInstalledDialerApplications helper or the underlying PackageManager#queryIntentActivitiesAsUser regression described in the commit message. DefaultDialerManager is still imported in the file at r17, but the material shows no remaining call site.",
      "The second commit only validates roleHolders.get(0); any additional holders are ignored. ROLE_DIALER is an exclusive single-holder role (general platform knowledge), so this is consistent with the role's semantics.",
      "The check confirms that the role holder package exists and is enabled in the work profile. It does not verify that the package actually resolves ACTION_DIAL or still meets the dialer role requirements; it relies on RoleManager for that.",
      "The check is now a positive role check: a work-profile app that handles ACTION_DIAL but does not hold the dialer role no longer counts as a dialer, so such calls are redirected to the parent user."
     ],
     "behavior_changes": [
      "Calls initiated from a managed profile that has no ROLE_DIALER holder, or whose holder package is missing or disabled in that profile, are now re-attributed to the parent user and handled by the personal profile dialer (the commit's test notes confirm this trampoline).",
      "Work profiles that have an ACTION_DIAL handler without holding the dialer role are now treated as having no dialer; previously the call stayed attributed to the work profile.",
      "During call intent processing, Telecom now calls RoleManager.getRoleHolders and PackageManager.getApplicationInfo for the work profile through a context created with createContextAsUser."
     ],
     "preconditions": [
      "The device has a managed (work) profile, and a call intent reaches CallIntentProcessor with KEY_INITIATING_USER set to that managed profile (UserUtil.isManagedProfile returns true). Per the class comment, UserCallIntentProcessor acts as the per-user trampoline into CallIntentProcessor.",
      "Per the commit messages: the work profile has no actual dialer (or, for the second commit, the default dialer package is not enabled in the profile) while the old check still reports an ACTION_DIAL handler.",
      "The material does not show the precise trigger of the information disclosure."
     ],
     "per_major": {
      "14": "adapted fix: commit 13272bca51ee in android-security-14.0.0_r28 carries the first change (RoleManager-based doesUserHaveDialerRoleHolder), adapted to the 14 code shape (fixInitiatingUserIfNecessary without FeatureFlags, UserUtil.isManagedProfile(context, initiatingUser), UserManager.get(context)). The material does not show the second hardening commit for 14. Per-major facts report 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "same fix in two commits: 34079b659664 in android-security-15.0.0_r16 (RoleManager check) and ec63a79133e6 in android-security-15.0.0_r17 (enabled-package cross check). 8/8 lines present at r17.",
      "16": "same fix: commit 510bcce16c24 in android-security-16.0.0_r7 carries the first change with the same code as 15. The material does not show the second commit for 16. Per-major facts report 8/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "same fix: commit c1104b6c1bbe in android-security-17.0.0_r1 carries the second (hardening) change. Its diff context shows doesUserHaveDialerRoleHolder already present in the 17 base, so both parts are in r1 (8/8 lines present)."
     },
     "backport_notes": [
      "Apply both commits in order: the second (Change-Id I5f004f94...) edits doesUserHaveDialerRoleHolder, which the first (Change-Id I1c10a6e0...) introduces. Per the second commit message, the first alone is insufficient on Android 14. On 14-style trees, adapt the surrounding fixInitiatingUserIfNecessary signature (no FeatureFlags, UserManager.get(context)) as the 14 commit did.",
      "The material includes no 'Fix in fork' comparison, so fork status could not be checked."
     ],
     "confidence": "high",
     "unknowns": [
      "What information is disclosed and to whom: the bulletin rates it ID/High, but the commits describe only wrong profile attribution of work-profile calls.",
      "Whether Android 14 and 16 received the second hardening commit. Only the first commit is shown for those branches; per-major facts say the patched region is present in the following security tag.",
      "Whether the PackageManager#queryIntentActivitiesAsUser regression referenced in the commit message was fixed separately in frameworks/base.",
      "Which permissions the new cross-user RoleManager and PackageManager calls require, and whether every fork's Telecom configuration grants them."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28582": {
     "cve": "CVE-2026-28582",
     "summary": "ConfirmDeviceCredentialActivity.onCreate now finishes immediately when it receives KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through any component other than the com.android.settings.ConfirmRemoteDeviceCredentialActivity alias. As a result, the permission checks tied to that alias can no longer be skipped by launching the underlying activity directly.",
     "component": "platform/packages/apps/Settings, password/ConfirmDeviceCredentialActivity (remote device credential confirmation)",
     "weakness": "Missing permission check: an access-controlled entry point (activity alias) could be bypassed by launching the underlying activity component with the same action",
     "root_cause": "ConfirmDeviceCredentialActivity.onCreate chose the remote validation path based only on intent.getAction() equalling KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL. The permission checks for that action are tied to the alias com.android.settings.ConfirmRemoteDeviceCredentialActivity (per the fix comment \"Remote validation must use the alias to avoid bypassing permission checks\"), not enforced in the activity code. A caller that targeted the ConfirmDeviceCredentialActivity class directly with this action could therefore reach the remote validation flow without going through the alias. The bulletin classifies the result as information disclosure (ID), severity High.",
     "fix_analysis": [
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: adds the constant CONFIRM_REMOTE_DEVICE_CREDENTIAL_ACTIVITY_ALIAS = \"com.android.settings.ConfirmRemoteDeviceCredentialActivity\".",
      "ConfirmDeviceCredentialActivity.onCreate: right after remoteValidation is computed, if it is true the code reads intent.getComponent(). If the component is null or its getClassName() does not equal the alias, it logs \"Caller bypassing alias for action: ...\" with Log.w, calls finish() and returns. This happens before mTaskOverlay is set and, on 15/16/17, before prepareRepairMode is computed.",
      "tests/robotests/.../ConfirmDeviceCredentialActivityTest.java (new): onCreate_remoteValidationWithAlias_doesNotFinish checks that the activity is not DESTROYED when launched via the alias component. onCreate_remoteValidationWithoutAlias_finishes checks that it is DESTROYED when launched via ConfirmDeviceCredentialActivity.class directly.",
      "tests/robotests/testutils/.../ShadowLockPatternUtils.java: adds a getCredentialTypeForUser shadow that returns CREDENTIAL_TYPE_PIN when the user is marked secure and CREDENTIAL_TYPE_NONE otherwise."
     ],
     "completeness": [
      "The check covers only the remote validation action. The FRP path (ACTION_CONFIRM_FRP_CREDENTIAL) and the normal confirm path are unchanged.",
      "The guard sits in onCreate, which the InternalActivity subclass inherits in the code shown, so it also applies there because that class name differs from the alias.",
      "Only componentName.getClassName() is compared, not the package. The check does not perform the permission check itself, so protection still depends on the alias's manifest permission, which the material does not show.",
      "A null component also leads to finish(), so an intent for this action without an explicit component is rejected."
     ],
     "behavior_changes": [
      "Launching ConfirmDeviceCredentialActivity (or any component whose class name is not the alias) with ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL now calls finish() and returns right away from onCreate (no setResult call in the diff), and logs a Log.w warning with tag ConfirmDeviceCredentialActivity.",
      "Launches through the com.android.settings.ConfirmRemoteDeviceCredentialActivity alias continue past the new check unchanged."
     ],
     "preconditions": [
      "A caller sends an intent with action KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL to a Settings component other than the ConfirmRemoteDeviceCredentialActivity alias, for example ConfirmDeviceCredentialActivity.",
      "Whether the direct components are exported or otherwise reachable by third-party apps depends on the Settings manifest, which is not in the material."
     ],
     "per_major": {
      "14": "Adapted fix: commit 4b69be85e55d in android-security-14.0.0_r29 ([RESTRICT AUTOMERGE]) adds the same constant and onCreate guard (7/7 lines present). The diff shown has no robolectric test or ShadowLockPatternUtils change. The surrounding code differs (non-final booleans, no custom-logo constants).",
      "15": "Same fix: commit 839f92783965 in android-security-15.0.0_r17 ([RESTRICT AUTOMERGE]), with the source guard, a new test and the shadow update; 8/8 lines present.",
      "16": "Same fix: commit 3ee7e15a126f in android-security-16.0.0_r8 ([RESTRICT AUTOMERGE]), same content as 15 at a different line offset; 8/8 lines present.",
      "17": "Same fix: commit 642944e4c4e6 in android-security-17.0.0_r1 (no RESTRICT AUTOMERGE prefix), with the same source guard, test and shadow update; 8/8 lines present."
     },
     "backport_notes": [
      "One self-contained commit per branch. The only required source change is the constant plus the guard block placed right after the remoteValidation boolean in ConfirmDeviceCredentialActivity.onCreate. The test and shadow changes are not part of the Android 14 diff.",
      "The check is an exact string compare against the class name com.android.settings.ConfirmRemoteDeviceCredentialActivity, so a fork that renamed that alias needs to change the constant to match."
     ],
     "confidence": "high",
     "unknowns": [
      "The Settings manifest is not in the material: the alias declaration, its permission attribute, and whether the direct activity components are exported. So the exact permission being bypassed is unknown.",
      "What information the remote validation flow discloses is not shown; the bulletin only gives type ID and severity High.",
      "Whether comparing only the class name (not the package) fully separates the alias from other launch paths is not shown.",
      "Whether framework-side changes went with this fix is not shown; only the Settings commits are provided."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28583": {
     "cve": "CVE-2026-28583",
     "summary": "validate_camera_metadata_structure in system/media camera_metadata.c now converts entry_capacity (an entry count) to bytes with sizeof(camera_metadata_buffer_entry_t), with an overflow guard, before checking that the entry region ends at or before data_start.",
     "component": "platform/system/media, camera/src/camera_metadata.c (libcamera_metadata, camera_metadata_t structure validation used by cameraserver)",
     "weakness": "Incorrect bounds check (unit mismatch: entry count treated as byte size) leading to out-of-bounds write",
     "root_cause": "In validate_camera_metadata_structure, the entry region end was computed as header->entries_start + header->entry_capacity and compared against header->data_start. entry_capacity is the number of camera_metadata_buffer_entry_t slots, not a byte count, so the computed end underestimated the real size of the entry region by a factor of sizeof(camera_metadata_buffer_entry_t). A crafted camera_metadata_t header whose real entry region extends past data_start therefore passed validation; per the commit message this led to an out-of-bounds write that eventually crashed cameraserver.",
     "fix_analysis": [
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: adds a pre-check rejecting header->entry_capacity > (metadata_size_t)~0 / sizeof(camera_metadata_buffer_entry_t) with CAMERA_METADATA_VALIDATION_ERROR, so the byte conversion cannot wrap in the 32-bit metadata_size_t.",
      "Computes metadata_size_t entries_capacity_bytes = sizeof(camera_metadata_buffer_entry_t) * header->entry_capacity and uses it (instead of the raw entry_capacity) in entries_end = header->entries_start + entries_capacity_bytes.",
      "Splits the old combined check into two: entries_end < header->entries_start (addition overflow, new ALOGE) and entries_end > header->data_start (region exceeds data start, existing ALOGE now printing entries_end); both return CAMERA_METADATA_VALIDATION_ERROR.",
      "camera/src/camera_metadata.c:struct camera_metadata: comment-only clarification that entry_capacity is a number of entries and data_capacity is a number of bytes."
     ],
     "completeness": [
      "Covers the entry_capacity vs data_start relationship in validate_camera_metadata_structure, including both multiplication and addition overflow.",
      "Only the entry-capacity check is changed; the data_capacity checks and the other header checks in the same function are not shown in the diff and are unchanged, so their correctness is not demonstrated by this material (data_capacity is documented as bytes, so the same unit mismatch does not apply by design).",
      "The fix is purely in the validator; code paths that consume camera_metadata_t without acting on validate_camera_metadata_structure's result (for example CameraMetadata::acquire only logs on validation failure, per index search) are not changed."
     ],
     "behavior_changes": [
      "Stricter validation: buffers where entries_start + entry_capacity * sizeof(camera_metadata_buffer_entry_t) exceeds data_start, or where entry_capacity is large enough to overflow the 32-bit byte computation, now fail with CAMERA_METADATA_VALIDATION_ERROR instead of passing.",
      "Two new ALOGE messages ('Entry capacity (...) is too large' and 'Entry start (...) + capacity bytes (...) overflows'); the existing 'should be <= data start' message now prints the byte-based entries_end.",
      "Metadata from HALs or clients that previously passed with inconsistent headers will now be rejected (cloneFromAidl / convertFromHidl return false and log 'Malformed camera metadata received from HAL', per index search)."
     ],
     "preconditions": [
      "A camera_metadata_t buffer with a crafted header (entry_capacity inconsistent with entries_start/data_start) reaches validate_camera_metadata_structure and passed validation before the fix.",
      "Per index search, the validator is called from frameworks/av/services/camera/libcameraservice/aidl/AidlUtils.cpp cloneFromAidl and hidl/Utils.cpp convertFromHidl (metadata received from the camera HAL), and from frameworks/av/camera/CameraMetadata.cpp CameraMetadata::acquire; the exact entry point used for the EoP is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/system/media a9dc2a964e84 in android-security-14.0.0_r29, identical diff (same blob indices e0c1cadb..0935461a), 8/8 patched lines present.",
      "15": "same fix: platform/system/media 9b4fe9dce95b in android-security-15.0.0_r17, 8/8 patched lines present.",
      "16": "same fix: platform/system/media 312249da098c in android-security-16.0.0_r8, identical hunk content on a different file base (1f09d5b1..95116f6c), 8/8 patched lines present.",
      "17": "not affected (not listed by the bulletin); the patched region is already present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "Single self-contained commit touching one function in camera/src/camera_metadata.c; the same hunk applied without adaptation on 14, 15 and 16, so it should apply cleanly to any branch whose validate_camera_metadata_structure still computes entries_end = entries_start + entry_capacity.",
      "libcamera_metadata is a platform library built from system/media rather than a mainline module, but it is also vendor-available, so vendor processes such as camera HALs may link their own copy; check which copies a fork rebuilds (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which caller/IPC path delivers the crafted metadata in the reported issue (bulletin rates it EoP; the commit only mentions a cameraserver crash).",
      "Which subsequent operation performs the out-of-bounds write (the diff only changes the validator).",
      "Contents of the rest of validate_camera_metadata_structure (data_capacity and size checks) are not included in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28593": {
     "cve": "CVE-2026-28593",
     "summary": "The Live TV app's \"Send Feedback\" settings action now builds an explicit intent targeting com.google.android.feedback/com.google.android.feedback.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent, so the intent can no longer resolve to an arbitrary app that registered for that action.",
     "component": "platform/packages/apps/TV, Live TV settings side panel (SettingsFragment)",
     "weakness": "Implicit intent used for a sensitive operation (unsafe implicit intent launch)",
     "root_cause": "In src/com/android/tv/ui/sidepanel/SettingsFragment.java, the onSelected callback of the ActionItem for R.string.settings_send_feedback built `new Intent(Intent.ACTION_APP_ERROR)` with no package or component, and then built an ApplicationErrorReport (packageName, processName, time) for it. Because the intent was implicit, resolution was not limited to a specific app. The commit message describes the risk as \"opening malicious apps\". The diff does not show how the report is attached or how the intent is launched.",
     "fix_analysis": [
      "SettingsFragment.java: adds `import android.content.ComponentName` and two constants, FEEDBACK_PACKAGE = \"com.google.android.feedback\" and FEEDBACK_CLASS = \"com.google.android.feedback.FeedbackActivity\".",
      "SettingsFragment.java:onSelected (anonymous ActionItem for settings_send_feedback): replaces `new Intent(Intent.ACTION_APP_ERROR)` with `new Intent().setComponent(new ComponentName(FEEDBACK_PACKAGE, FEEDBACK_CLASS))`. This makes the intent explicit, so it targets only the named Google Feedback activity. The diff does not change the ApplicationErrorReport construction lines it shows as context."
     ],
     "completeness": [
      "Covers the single implicit ACTION_APP_ERROR intent in SettingsFragment's Send Feedback action. The diff touches only this one call site.",
      "The action string is dropped entirely (no setAction), so the intent is purely component-based. The material does not show whether the target activity needs an action to be set. It also does not show whether the code handles the case where the hard-coded package is absent (no ActivityNotFoundException handling is visible in the diff).",
      "No other intent launches in the TV app appear in the material, so the code provided does not show whether sibling implicit intents exist."
     ],
     "behavior_changes": [
      "Send Feedback now targets only com.google.android.feedback.FeedbackActivity. On devices without that package, or with a different feedback handler, the intent no longer resolves to an alternative ACTION_APP_ERROR handler. The diff does not show what happens when resolution fails.",
      "Third-party apps with an ACTION_APP_ERROR intent filter can no longer be selected as the target of this intent."
     ],
     "preconditions": [
      "The Live TV app is installed, and the user opens the Settings side panel and selects the Send Feedback item (local user interaction).",
      "Before the fix: an app on the device declares an intent filter for Intent.ACTION_APP_ERROR, so that implicit resolution could select it.",
      "Per index search, no other caller of this onSelected was found. It is the UI ActionItem callback."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/TV 2e9c27acedc2 in android-security-14.0.0_r29, identical diff (same blob indices 762a190c..1ac5d81e); 8/8 patched lines present.",
      "15": "same fix: platform/packages/apps/TV 7e516e62a48d in android-security-15.0.0_r17, cherry-picked from internal changes 22867ae71e25 / 6690b3faa83a; 8/8 patched lines present.",
      "16": "same fix: platform/packages/apps/TV d688b27298d4 in android-security-16.0.0_r8, identical diff; 8/8 patched lines present.",
      "17": "same fix: platform/packages/apps/TV 724154d5db03 in android-security-17.0.0_r1, identical diff; 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained Java change with no visible dependencies. The pre- and post-fix blob hashes of the file are the same on 14 through 17, so the patch should apply cleanly. The fix hard-codes com.google.android.feedback, so forks that ship a non-Google feedback handler need to substitute their own explicit component."
     ],
     "confidence": "high",
     "unknowns": [
      "How the rest of the onSelected method attaches the report and starts the intent (startActivity or something else), and whether a resolution failure is caught; this is not in the diff.",
      "Which ApplicationErrorReport fields beyond packageName/processName/time are populated, and therefore what data could reach an implicit receiver.",
      "Why the bulletin rates this High EoP; the material only says the intent could open malicious apps."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28596": {
     "cve": "CVE-2026-28596",
     "summary": "Adds OutOfMemoryError to the multi-catch around the game mode config XML meta-data parsing in GameManagerService, so an oversized XML resource no longer raises an uncaught Error that crashes the process.",
     "component": "platform/frameworks/base, services/core GameManagerService (game mode config meta-data parsing)",
     "weakness": "Uncaught exception / uncontrolled resource consumption: an OutOfMemoryError thrown while parsing app-supplied XML was not handled, so the process crashed (DoS)",
     "root_cause": "In services/core/java/com/android/server/app/GameManagerService.java, the method that reads an application's METADATA_GAME_MODE_CONFIG XML meta-data only caught NameNotFoundException, XmlPullParserException and IOException. That method gets the app Resources via packageManager.getResourcesForApplication, wraps the parser with Xml.asAttributeSet, calls parser.next() until the first START_TAG and reads the GameModeConfig styleable attributes. Per the commit message, an XML resource that is too big (>1 GB) makes this parsing fail with OutOfMemoryError. That is an Error, not an Exception, so it escaped the catch block and the process crashed.",
     "fix_analysis": [
      "services/core/java/com/android/server/app/GameManagerService.java, the METADATA_GAME_MODE_CONFIG parsing method (returns xmlFound; in the same class as getOrAddDefaultGameModeConfiguration): the catch clause is widened from `NameNotFoundException | XmlPullParserException | IOException` to also include `OutOfMemoryError`.",
      "The existing handler body is reused unchanged. On failure it sets mPerfModeOverridden=false, mBatteryModeOverridden=false, mAllowDownscale=true, mAllowAngle=true and mAllowFpsOverride=true, then logs 'Error while parsing XML meta-data for ' + METADATA_GAME_MODE_CONFIG via Slog.e. The method then returns xmlFound."
     ],
     "completeness": [
      "Covers exactly one try/catch in GameManagerService.java: the block that parses the GameModeConfig meta-data XML. No other code paths are changed.",
      "The fix handles the OutOfMemoryError after it is thrown. It does not add an up-front size limit or validation on the XML resource.",
      "The material does not show any other XML or resource parsing sites in GameManagerService or elsewhere that read app-supplied resources, and this change does not touch them.",
      "The commit message lists two Cherrypick-From sources, but the material contains only a single 2-line diff."
     ],
     "behavior_changes": [
      "An OutOfMemoryError raised while parsing the game mode config XML is now caught. The game mode override flags are reset to defaults, an error is logged, and the method returns normally instead of the process crashing.",
      "If the OOM happens after xmlFound is set to true (inside the parsing branch), the method still returns true, with default flag values. This matches the existing exception path."
     ],
     "preconditions": [
      "An installed application declares the METADATA_GAME_MODE_CONFIG meta-data pointing at an XML resource, which GameManagerService parses using the Resources obtained via packageManager.getResourcesForApplication for that package.",
      "Per the commit message, the XML must be large enough (>1 GB) that parsing it throws OutOfMemoryError.",
      "The material does not show which event makes GameManagerService parse the package's config."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 12567c8df8f3 in android-security-14.0.0_r29, the same 2-line change at a different line offset (@@ -610).",
      "15": "same fix: platform/frameworks/base 47898903e7c3 in android-security-15.0.0_r17 (@@ -619), the commit analysed here.",
      "16": "same fix: platform/frameworks/base 581fd9950f41 in android-security-16.0.0_r8, the same change (@@ -633).",
      "17": "not affected per the bulletin. 4/5 lines of the patched region are present at android-security-17.0.0_r1, the same count as on the fixed branches. The material does not confirm that the OutOfMemoryError catch is there."
     },
     "backport_notes": [
      "Single self-contained 2-line change with no visible dependencies. It applies where the same `catch (NameNotFoundException | XmlPullParserException | IOException ex)` block exists in GameManagerService.java (only the line offset differs across 14/15/16)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the exact name of the method that contains the patched try/catch.",
      "The material does not show which callers trigger the parsing, or whether the declaring app can trigger it on demand.",
      "The material does not show whether the second Cherrypick-From commit contained any change beyond this diff.",
      "The material does not show whether android-security-17.0.0_r1 contains the OutOfMemoryError catch.",
      "The material does not show where in the try block the OOM is actually thrown (for example, while the parser is obtained, before line 589)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28600": {
     "cve": "CVE-2026-28600",
     "summary": "PaymentDefaultDialog.onCreate no longer reads a UserHandle from the Intent.EXTRA_USER extra; the user id passed to buildDialog is now always UserHandle.myUserId().",
     "component": "platform/packages/apps/Settings, NFC payment default dialog (src/com/android/settings/nfc/PaymentDefaultDialog.java)",
     "weakness": "Trust of a caller-controlled user identifier (Intent.EXTRA_USER) without validation in the privileged Settings app",
     "root_cause": "PaymentDefaultDialog.onCreate took a UserHandle from the launching intent's Intent.EXTRA_USER extra. When one was present, it passed userHandle.getIdentifier() as the userId to buildDialog(component, category, userId). onCreate did not check that this user matched the user running the dialog. onClick(BUTTON_POSITIVE) calls mBackend.setDefaultPaymentApp(mNewDefault.componentName, mNewDefault.userId); mNewDefault is presumably filled in buildDialog from that userId, but the material does not show this.",
     "fix_analysis": [
      "src/com/android/settings/nfc/PaymentDefaultDialog.java:onCreate: the fix deletes the getParcelableExtra(Intent.EXTRA_USER) read and the null-check branch that chose between UserHandle.myUserId() and userHandle.getIdentifier(). userId is now always set to UserHandle.myUserId().",
      "As a result, buildDialog(component, category, userId) always gets the user the Settings activity runs in. The intent can no longer choose the user id at this point.",
      "The commit title says dialog building logic is removed from onCreate. The actual diff keeps buildDialog() in onCreate and only removes the EXTRA_USER handling. EXTRA_SERVICE_COMPONENT and EXTRA_CATEGORY are still read from the intent."
     ],
     "completeness": [
      "The fix covers the place in onCreate where the user id supplied by the intent entered the flow. Whether mNewDefault.userId (used in onClick) always comes from this value depends on buildDialog code that the material does not show.",
      "Component (EXTRA_SERVICE_COMPONENT) and category (EXTRA_CATEGORY) are still taken from the intent. The shown context does not reveal how buildDialog validates them beyond the null check.",
      "The diff does not change the activity declaration or PaymentBackend.setDefaultPaymentApp itself. The fix is purely in the dialog's input handling."
     ],
     "behavior_changes": [
      "Intent.EXTRA_USER passed to PaymentDefaultDialog is now ignored. Any caller that relied on it to target a different user (for example a profile) will now target the user running Settings.",
      "No new error paths in onCreate. The dialog is built as before, with the user id fixed to UserHandle.myUserId()."
     ],
     "preconditions": [
      "Ability to start the Settings PaymentDefaultDialog activity with an intent carrying CardEmulation.EXTRA_SERVICE_COMPONENT, CardEmulation.EXTRA_CATEGORY and an Intent.EXTRA_USER for a different user. Whether the activity is exported or protected by a permission is not shown in the material.",
      "The user must press the positive button, since setDefaultPaymentApp is only called in onClick(BUTTON_POSITIVE).",
      "PaymentBackend construction must not throw a NullPointerException, which makes the activity call finish()."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14.",
      "15": "Same fix: commit 1d0237d77a38 in android-security-15.0.0_r17 (cherry-pick of c2f680bf6093, Change-Id I9224a3aec3ecb2c8bbf3fe3ee966afaf782cb9c4).",
      "16": "Same fix: commit bf85ca6c87fc in android-security-16.0.0_r8. Identical diff against the same file blob (d333b3da6ac -> d556b6df929).",
      "17": "Same fix: commit d1db7e8d9994 in android-security-17.0.0_r1. Identical diff against the same file blob."
     },
     "backport_notes": [
      "One commit with one hunk, and the same pre-image (index d333b3da6ac) on 15, 16 and 17. It should apply cleanly wherever PaymentDefaultDialog.onCreate still reads Intent.EXTRA_USER. Settings is a platform app, not a mainline module, so the fix ships with the platform build (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "How PaymentDefaultDialog is declared in the Settings manifest (exported state, intent filter, permission), and which system component normally launches it.",
      "How buildDialog fills mNewDefault, and what validation buildDialog and PaymentBackend.setDefaultPaymentApp apply to the component, category and userId beyond the null check visible in the context.",
      "Why the commit title refers to removing dialog building logic when the diff only removes the EXTRA_USER handling; whether a related change exists elsewhere is not shown.",
      "What causes the NullPointerException in the PaymentBackend constructor."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28602": {
     "cve": "CVE-2026-28602",
     "summary": "ClipboardService now reads and writes the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS secure setting explicitly for the given userId (getIntForUser/putIntForUser), so the clipboard access notification decision follows the setting of the user whose clipboard was accessed.",
     "component": "platform/frameworks/base, system_server ClipboardService (services/core/java/com/android/server/clipboard/ClipboardService.java)",
     "weakness": "Wrong user context when reading a per-user setting (user-id confusion affecting a clipboard access notification)",
     "root_cause": "In ClipboardService the code path that decides whether to show the clipboard access notification called Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, default) without passing the method's userId, so the setting was read for the service context's own user rather than for the user whose clipboard was being accessed. On multi-user or profile configurations where the two users' values differ, the notification decision could therefore not match the accessed user's setting. The companion setter setClipboardAccessNotificationsEnabledForUser used Settings.Secure.putInt on a resolver obtained from createContextAsUser(UserHandle.of(userId)), relying on the resolver's implicit user rather than an explicit userId.",
     "fix_analysis": [
      "ClipboardService.java, access-notification method around line 1441 (hunk does not show the method name; it has a userId in scope and checks clipboard.primaryClip == null first): Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, mShowAccessNotifications ? 1 : 0) is replaced by Settings.Secure.getIntForUser(..., userId), so the decision uses the setting of the user whose clipboard was accessed.",
      "ClipboardService.java:setClipboardAccessNotificationsEnabledForUser: Settings.Secure.putInt(resolver, CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, enable ? 1 : 0) is replaced by Settings.Secure.putIntForUser(resolver, ..., userId), making the target user explicit instead of relying on the per-user resolver created via createContextAsUser.",
      "The default value argument (mShowAccessNotifications) and the MANAGE_CLIPBOARD_ACCESS_NOTIFICATION permission check in the setter are unchanged; only the user used for the Settings read/write changes."
     ],
     "completeness": [
      "Both Settings accesses to CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS that lacked an explicit user are changed; the third accessor, areClipboardAccessNotificationsEnabledForUser, already used getIntForUser(..., userId) at this tag and is untouched.",
      "The fix is a single-file change (two hunks, 5 insertions, 4 deletions) with no new test (Test: Manual)."
     ],
     "behavior_changes": [
      "Clipboard access notifications are now shown or suppressed according to the accessed user's own CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS value instead of the value of the service context's user.",
      "setClipboardAccessNotificationsEnabledForUser writes the setting explicitly for the given userId; no API or error-code changes are visible in the diff."
     ],
     "preconditions": [
      "Multi-user or profile configuration where the clipboard being accessed belongs to a user other than the service context's user.",
      "That user's CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS value differs from the affected user's.",
      "clipboard.primaryClip is non-null (the method returns early otherwise); per the code comment, no notification is shown when the accessing app is the current clip owner."
     ],
     "per_major": {
      "14": "same fix: DO NOT MERGE commit 6f90eac7f9e5 in android-security-14.0.0_r29, same two hunks (around lines 516 and 1364); resolver reports 7/8 lines of patched region present.",
      "15": "same fix: DO NOT MERGE commit 993d9100be4c (Cherrypick-From c6de6ab75ea3) in android-security-15.0.0_r17, hunks around lines 560 and 1441; resolver reports 7/8 lines present.",
      "16": "same fix: DO NOT MERGE commit 80b1b1fe63b5 in android-security-16.0.0_r8, hunks around lines 553 and 1438; resolver reports 7/8 lines present.",
      "17": "same Settings change in commit 4b3bf7fb29ec in android-security-17.0.0_r1 (title without DO NOT MERGE); surrounding context differs: the notification method returns boolean (return false instead of return) and is preceded by a shouldSuppressAccessNotificationForUidLocked(uid) check; resolver reports 7/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit; it requires a userId in scope in the notification method (present on all four branches per the diffs) and Settings.Secure.getIntForUser/putIntForUser, which all four branch diffs use."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the notification method containing the second hunk (not shown in the hunk header or context).",
      "Which user the service context's resolver maps to, and so the exact direction of the mismatch before the fix.",
      "Why the resolver reports 7/8 lines of the patched region present on every branch.",
      "Whether the source commit c6de6ab75ea3 or the Android 17 commit included test changes."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28603": {
     "cve": "CVE-2026-28603",
     "summary": "In Settings' AppRestrictionsFragment, assertSafeToStartCustomActivity now removes all four URI grant flags from its intent copy and clears its data and clipData. Settings then starts the custom restrictions activity with startActivityForResult.",
     "component": "platform/packages/apps/Settings, users / AppRestrictionsFragment",
     "weakness": "Intent forwarding with URI permission grants. Settings starts a supplied intent that may carry FLAG_GRANT_*_URI_PERMISSION flags plus a data or clipData URI, and the launch runs under Settings' identity.",
     "root_cause": "RestrictionsResultReceiver.assertSafeToStartCustomActivity in src/com/android/settings/users/AppRestrictionsFragment.java checked only where the supplied intent resolved. It copied the intent, resolved it with mPackageManager.resolveActivity, checked that activityInfo.packageName equals the expected packageName, then pinned the component and package. It left the intent's flags, data URI and clipData untouched. AppRestrictionsFragment.this.startActivityForResult(vettedIntent, ...) runs inside Settings. So any FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION flags, combined with a content URI in data or clipData, could make Settings hand URI grants to the target activity in the configured app's package. The material does not show where the intent comes from; that the configured app supplies it is inferred from the class and method names.",
     "fix_analysis": [
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.assertSafeToStartCustomActivity: after setComponent/setPackage on vettedIntent, the method calls vettedIntent.removeFlags(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION | FLAG_GRANT_PREFIX_URI_PERMISSION), then setData(null) and setClipData(null). It then returns the copy that is passed to startActivityForResult.",
      "tests/robotests/src/com/android/settings/users/AppRestrictionsFragmentTest.java (new file): the test assertSafeToStartCustomActivity_stripsSensitiveData builds an intent with all four grant flags, a content:// data URI and a clipData entry. It mocks resolveActivity to return the expected package, calls assertSafeToStartCustomActivity through reflection, and asserts that the flags are cleared and that data and clipData are null."
     ],
     "completeness": [
      "Within assertSafeToStartCustomActivity, all four URI grant flag bits are removed and both data and clipData are set to null on the returned intent.",
      "The existing package check (resolveActivity plus packageName equality, then setComponent/setPackage) is unchanged. The fix only adds lines.",
      "Intent extras and non-grant flags are left as they are.",
      "The commit changes only this method and adds one test. It touches no other startActivity call site in Settings."
     ],
     "behavior_changes": [
      "The custom restrictions activity launched from AppRestrictionsFragment no longer receives the data URI or clipData from the supplied intent, and Settings no longer forwards the grant flags. Code that read getData()/getClipData() on this intent will now get null.",
      "No new exception or error path. The intent is still started."
     ],
     "preconditions": [
      "A custom restrictions activity intent reaches RestrictionsResultReceiver and is passed to assertSafeToStartCustomActivity (inferred from the code context).",
      "The intent resolves to an activity in the expected package (the existing package check), and it carries URI grant flags together with a data or clipData URI."
     ],
     "per_major": {
      "14": "same fix: commit 0f9f6238cf25 in android-security-14.0.0_r29, identical hunk at a different offset (@@ -687), 4/4 patched lines present.",
      "15": "same fix: commit 856f8ccca12e in android-security-15.0.0_r17, hunk @@ -683, 4/4 patched lines present.",
      "16": "same fix: commit 3bf7abe6e14b in android-security-16.0.0_r8, hunk @@ -683 (same file blob as 15), 4/4 patched lines present.",
      "17": "same fix: commit a3cbe8fbf286 in android-security-17.0.0_r1, identical hunk at offset @@ -726, 4/4 patched lines present."
     },
     "backport_notes": [
      "This is a single self-contained commit. The six added lines go right after the setComponent/setPackage lines in assertSafeToStartCustomActivity. The Robolectric test sets mPackageManager through ReflectionHelpers and calls new RestrictionsResultReceiver(\"com.example.app\", null, false). Adjust the test if the constructor differs in a fork."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show where the intent handled by RestrictionsResultReceiver comes from, only that Settings checks it and starts it.",
      "Which user or profile setup (for example, restricted profiles) and which UI flow reach this code is not shown.",
      "Whether extras such as EXTRA_STREAM or EXTRA_OUTPUT could still lead to a URI grant through the platform's extra-to-clipData migration when the activity is started is not covered by the material.",
      "The material does not show which content provider or data the bulletin considered reachable through Settings' URI grants. The test uses a placeholder content://com.android.settings.files URI.",
      "Whether the referenced internal cherry-picks (adde2e48, bf5e5cf1) contain anything beyond this hunk is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28606": {
     "cve": "CVE-2026-28606",
     "summary": "The Bluetooth AdapterService now removes the per-device CallerInfo entry from mBondAttemptCallerInfo whenever a bond state transition lands in any state other than BOND_BONDING, so caller information from a finished or failed bonding attempt is no longer kept.",
     "component": "platform/packages/modules/Bluetooth, btservice/AdapterService (bond state handling)",
     "weakness": "Stale state retention: the caller record for a bonding attempt is not cleared after the attempt concludes (improper cleanup of per-device state)",
     "root_cause": "AdapterService keeps a map, mBondAttemptCallerInfo, keyed by device address and holding a CallerInfo for a device's bonding attempt. The bond state transition handler in AdapterService.java receives toState and resets the phonebook and SIM access permissions on the relevant transitions, but it never removed that entry. So once bonding reached BOND_BONDED or BOND_NONE, the CallerInfo stayed in the map even though it described an attempt that had already finished. The material does not show which code reads mBondAttemptCallerInfo, so how the stale record was used is not visible.",
     "fix_analysis": [
      "android/app/src/com/android/bluetooth/btservice/AdapterService.java: adds a static import of BluetoothDevice.BOND_BONDING.",
      "android/app/src/com/android/bluetooth/btservice/AdapterService.java, bond state transition handler (hunk after the setPhonebookAccessPermission/setSimAccessPermission reset block): adds `if (toState != BOND_BONDING) { CallerInfo callerInfo = mBondAttemptCallerInfo.remove(device.getAddress()); if (callerInfo != null) Log.d(...) }`. The entry is now dropped on every transition to a state other than BOND_BONDING.",
      "The Android 15 (9b196f44770c) and Android 16 (07542331e7c4) commits apply this unconditionally. Their commit message names the aconfig flag com.android.bluetooth.flags.remove_bond_caller_info, but the diff has no Flags check.",
      "Android 17 (1b0d684402b2, 'Unflag remove_bond_caller_info'): changes the earlier flagged form `if (Flags.removeBondCallerInfo() && toState != BOND_BONDING)` to `if (toState != BOND_BONDING)` and deletes the remove_bond_caller_info flag definition from flags/pairing.aconfig, so the cleanup is always active."
     ],
     "completeness": [
      "Covers: cleanup of mBondAttemptCallerInfo on any bond state change whose target state is not BOND_BONDING (i.e. BOND_BONDED and BOND_NONE), for the device whose state changed.",
      "Cleanup happens only in the bond state callback. The material does not show whether the map is also cleared when the adapter is turned off or the service is torn down while a device is still in BOND_BONDING (unverified).",
      "The code that inserts into mBondAttemptCallerInfo and the code that reads it are not in the diff or context, so it cannot be judged whether the readers also validate the record.",
      "The removal does nothing and nothing is logged when no entry exists for the device."
     ],
     "behavior_changes": [
      "mBondAttemptCallerInfo entries now last only through the BOND_BONDING phase. After a device's bond completes or fails, no CallerInfo from that attempt remains.",
      "A new debug log 'Removed bond caller info for device: <device>' is emitted when an entry is removed.",
      "On Android 17 the behaviour is no longer gated by the remove_bond_caller_info aconfig flag, and that flag is removed from flags/pairing.aconfig."
     ],
     "preconditions": [
      "A CallerInfo must have been stored in mBondAttemptCallerInfo for the device address (the insertion path is not shown in the material).",
      "The bond state of that device must then move to BOND_BONDED or BOND_NONE. Before the fix, the entry was kept after that point. How it was used afterwards is not shown."
     ],
     "per_major": {
      "14": "not affected per the bulletin; android-security-14.0.0_r29 contains 5/8 lines of the patched region. The material does not show which lines those are or whether mBondAttemptCallerInfo exists there.",
      "15": "same fix, unconditional (no Flags check despite the Flag: line in the commit message), commit 9b196f44770c in android-security-15.0.0_r17, 8/8 lines present",
      "16": "same fix, unconditional, commit 07542331e7c4 in android-security-16.0.0_r8, 8/8 lines present",
      "17": "adapted: the removal block was already present behind Flags.removeBondCallerInfo(). Commit 1b0d684402b2 in android-security-17.0.0_r1 drops the flag check and deletes the remove_bond_caller_info flag from flags/pairing.aconfig. 6/8 lines of the reference region are present (the reason for the difference is not shown)."
     },
     "backport_notes": [
      "The fix is a self-contained change to AdapterService.java: one static import plus a 7-line block (a blank line and the removal block) in the bond state transition handler, 8 insertions in total. It needs mBondAttemptCallerInfo and the CallerInfo type to already exist in the target tree. If the target carries the flagged form (as Android 17 did), apply the unflag change and remove the aconfig flag definition instead of re-adding the block."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which code reads mBondAttemptCallerInfo, and what decision the stale CallerInfo influenced; the EoP mechanism is not visible in the material.",
      "Where and under what conditions entries are inserted into mBondAttemptCallerInfo.",
      "The exact name of the bond state handler method (the hunk header shows only the class).",
      "Whether the map is cleared on adapter shutdown while a device remains in BOND_BONDING.",
      "Why Android 14 is treated as not affected, and which of the patched-region lines exist there.",
      "Why only 6/8 lines of the reference region match at android-security-17.0.0_r1."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28607": {
     "cve": "CVE-2026-28607",
     "summary": "Adds caller permission checks (REQUEST_INSTALL_PACKAGES requested or INSTALL_PACKAGES granted) plus the hide-non-system-overlays window flag to UnarchiveErrorActivity.onCreate, factors the UID-to-package and requested-permission helpers into PackageUtil, and on Android 17 moves the PIA v2 UnarchiveLaunch forwarding behind those checks.",
     "component": "platform/frameworks/base, packages/PackageInstaller (app archiving / unarchive UI)",
     "weakness": "missing permission check on an activity entry point (CWE-862 style missing authorization)",
     "root_cause": "UnarchiveErrorActivity.onCreate read intent extras (EXTRA_UNARCHIVE_STATUS, EXTRA_REQUIRED_BYTES; the class also defines installer package name/title extras) from any launcher. It did not verify that the calling UID held INSTALL_PACKAGES or requested REQUEST_INSTALL_PACKAGES, and it did not set SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS on its window. The sibling UnarchiveActivity already performed these caller checks, so the error dialog path was the unguarded one. On Android 17 (shown in the 17 diff), UnarchiveActivity also forwarded the incoming intent to the v2 UnarchiveLaunch activity (when PackageUtil.isVersionTwoEnabled) before running its own caller checks, so the v2 path was reached without the caller being validated.",
     "fix_analysis": [
      "PackageUtil.java: adds two static helpers, moved out of UnarchiveActivity so both activities share them. getPackageNameForUid(PackageManager, int) returns pm.getPackagesForUid(uid)[0], or null with a log message when no package matches. getRequestedPermissions(PackageManager, String) returns requestedPermissions from getPackageInfo(pkg, GET_PERMISSIONS), or an empty array on any Exception.",
      "UnarchiveActivity.java:onCreate: switches to the PackageUtil helpers and a single PackageManager local. On 15 and 16 the existing check logic is unchanged: INVALID_UID aborts, a null package aborts, and neither REQUEST_INSTALL_PACKAGES requested nor INSTALL_PACKAGES granted gives RESULT_FIRST_USER + finish. The private getRequestedPermissions and getPackageNameForUid methods are deleted.",
      "UnarchiveErrorActivity.java:onCreate: before reading any extras, the method now does the following, mirroring the UnarchiveActivity gate. It calls getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS). It gets callingUid via getLaunchedFromUid() and aborts with RESULT_FIRST_USER if that is INVALID_UID. It aborts if PackageUtil.getPackageNameForUid returns null. It aborts if the caller neither lists REQUEST_INSTALL_PACKAGES in its requested permissions nor passes getBaseContext().checkPermission(INSTALL_PACKAGES, 0, callingUid).",
      "Android 17 only, UnarchiveActivity.java:onCreate: the PackageUtil.isVersionTwoEnabled block that builds the piaV2 intent (EXTRA_CALLING_PKG_NAME/EXTRA_CALLING_PKG_UID, setClass UnarchiveLaunch, FLAG_ACTIVITY_FORWARD_RESULT) moves from right after super.onCreate to after the permission checks. The unused androidx NonNull import is dropped. In UnarchiveErrorActivity on 17, the existing isVersionTwoEnabled forwarding block now sits after the newly inserted checks."
     ],
     "completeness": [
      "Covers both v1 unarchive activities (UnarchiveActivity, UnarchiveErrorActivity) with the same caller gate. On 17 it also makes sure the v2 UnarchiveLaunch forwarding in both activities happens only after the gate.",
      "The REQUEST_INSTALL_PACKAGES branch only checks that the calling package declares the permission (requestedPermissions), not that the app op is granted. The in-code comment in UnarchiveActivity says the AppOpsManager is not checked because the requester is not the source of the installation. The INSTALL_PACKAGES branch is a real checkPermission against the UID.",
      "For shared-UID callers, PackageUtil.getPackageNameForUid uses only packagesForUid[0], so the requested-permission check is evaluated against the first package of the UID (same as the existing UnarchiveActivity behavior).",
      "The rest of UnarchiveErrorActivity.onCreate (extras parsing and dialog handling) is not changed and not shown. Any validation of those extras is outside this fix.",
      "Whether the v2 UnarchiveLaunch activity performs its own caller validation is not shown in the material."
     ],
     "behavior_changes": [
      "UnarchiveErrorActivity now logs an error and finishes immediately with Activity.RESULT_FIRST_USER when the launching UID cannot be resolved, maps to no package, or neither requests REQUEST_INSTALL_PACKAGES nor holds INSTALL_PACKAGES.",
      "The UnarchiveErrorActivity window now sets SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, which hides non-system overlays while it is shown.",
      "PackageUtil.getRequestedPermissions catches Exception (broader than the previous NameNotFoundException), and getPackageNameForUid now logs when no package matches the UID.",
      "On Android 17, UnarchiveActivity forwards to the v2 UnarchiveLaunch only after the permission gate passes, so launches by callers that fail the gate are no longer forwarded to UnarchiveLaunch."
     ],
     "preconditions": [
      "A caller must be able to start UnarchiveErrorActivity (or, on 17, UnarchiveActivity) in the PackageInstaller app with crafted extras. The material does not show whether these activities are exported in the manifest.",
      "The bulletin lists Android 15, 16, 16-qpr2, 17 and 17-next as affected.",
      "The Android 17 v2 reordering matters only when PackageUtil.isVersionTwoEnabled is true (the commit message names the flag android.content.pm.use_pia_v2)."
     ],
     "per_major": {
      "14": "not affected per the bulletin (Android 14 not listed). The per-major facts show 7/8 lines of the patched region present at android-security-14.0.0_r29, so some form of this code exists there, but no fix is recorded for 14.",
      "15": "same fix: a4b48c54049c in android-security-15.0.0_r17 (cherry-pick, Change-Id I8f1265acae162cd752e76259f4ffaf39ab76b7e5). Adds the gate to UnarchiveErrorActivity and moves the helpers into PackageUtil. The 15 diff and code context show no v2 UnarchiveLaunch forwarding in these activities. 8/8 patched lines present.",
      "16": "same fix: 2e158f2d9a81 in android-security-16.0.0_r8. The activity changes are textually identical to 15 (same before/after blob hashes); only the PackageUtil hunk offset differs. No v2 forwarding shown in these activities. 8/8 patched lines present.",
      "17": "adapted fix: 743a34f05af0 in android-security-17.0.0_r1. Same PackageUtil helpers and the same UnarchiveErrorActivity overlay flag and caller gate (the LOG_TAG field is not added in this diff). In addition, UnarchiveActivity's PackageUtil.isVersionTwoEnabled forwarding to UnarchiveLaunch moves after the permission checks, and the androidx NonNull import is removed. The existing v2 block in UnarchiveErrorActivity now follows the new gate. 8/8 patched lines present."
     },
     "backport_notes": [
      "Single commit. The PackageUtil helpers must land together with the activity changes, because UnarchiveActivity's private copies are deleted. On branches without the PIA v2 UnarchiveLaunch forwarding (15, 16 as shown), the 17 reordering hunk does not apply. On forks that carry v2, make sure the isVersionTwoEnabled block sits after the permission gate in both activities."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether UnarchiveErrorActivity is exported or otherwise reachable by third-party apps (manifest not in the material).",
      "What a caller could achieve by launching UnarchiveErrorActivity with crafted extras before the fix (the extras handling body, including any use of the installer extras or a PendingIntent, is not shown).",
      "Whether UnarchiveLaunch (v2) has independent caller checks.",
      "Why Android 14 is not listed even though the per-major facts show most of the patched region present there."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28611": {
     "cve": "CVE-2026-28611",
     "summary": "Adds a caller-UID-to-package-name verification (new NfcPermissions.checkPackage backed by AppOpsManager.checkPackage) at the start of the NfcAdapterService binder methods enable, disable and setObserveMode, so a caller can no longer pass an arbitrary package name into NFC authorization decisions.",
     "component": "platform/packages/apps/Nfc (Android 15) / platform/packages/modules/Nfc (Android 16), NfcService binder interface (INfcAdapter.Stub)",
     "weakness": "Missing validation of caller-supplied identity (package name not checked against calling UID) used in permission decisions",
     "root_cause": "NfcAdapterService.enable(String pkg), disable(boolean, String pkg) and setObserveMode(boolean, String packageName) in src/com/android/nfc/NfcService.java take a package name from the binder caller and use it directly in authorization. enable and disable call isDeviceOrProfileOwner(Binder.getCallingUid(), pkg), which delegates to NfcPermissions.isDeviceOwner/isProfileOwner; isProfileOwner calls DevicePolicyManager.isProfileOwnerApp(packageName) (per the shown context). enable also compares pkg against R.array.nfc_allow_list. setObserveMode passes packageName to mCardEmulationManager.isPreferredServicePackageNameForUser and to wallet role comparisons (per index excerpt). Nothing checked that the supplied package actually belongs to the calling UID, so the string was trusted as the caller's identity.",
     "fix_analysis": [
      "src/com/android/nfc/NfcPermissions.java: adds an AppOpsManager field, set in the constructor via mContext.getSystemService(AppOpsManager.class), and a new method checkPackage(int uid, String pkgName). The method throws SecurityException when pkgName is null and otherwise calls mAppOpsManager.checkPackage(uid, pkgName).",
      "src/com/android/nfc/NfcService.java: NfcAdapterService.enable(String pkg) now calls mNfcPermissions.checkPackage(Binder.getCallingUid(), pkg) as its first statement, before isDeviceOrProfileOwner and the admin permission check.",
      "src/com/android/nfc/NfcService.java: NfcAdapterService.disable(boolean saveState, String pkg) gets the same checkPackage call as its first statement.",
      "src/com/android/nfc/NfcService.java: NfcAdapterService.setObserveMode(boolean enable, String packageName) gets the same checkPackage call as its first statement, before the isNfcEnabled check.",
      "tests/unit/src/com/android/nfc/NfcServiceTest.java: setUp mocks mPackageManager.getPackageUid(PKG_NAME, 0) to return Binder.getCallingUid(), and testSetObserveMode_nfcDisabled now passes PKG_NAME instead of null.",
      "Android 16 commit ebbca6f8125e (packages/modules/Nfc): removes the aconfig flag check_passed_in_package and makes the checkPackage calls unconditional in enable, disable, setObserveMode, updateDiscoveryTechnology, setReaderMode and indicateDataMigration in NfcNci/src/com/android/nfc/NfcService.java. The test gains an AppOpsManager mock."
     ],
     "completeness": [
      "On Android 15 the check covers exactly three binder methods: enable, disable and setObserveMode. The Android 16 version of the change covers six methods (adding updateDiscoveryTechnology, setReaderMode and indicateDataMigration). The Android 15 material does not show whether those three extra methods exist with a package-name parameter in the 15 tree.",
      "The check comes before any other logic in each method, so it also applies to privileged callers (SYSTEM_UID, NFC_UID, ROOT_UID). The material shows no bypass for them.",
      "A null package in setObserveMode, which before was only logged for non-privileged callers (per the index excerpt), now causes a SecurityException.",
      "The fix relies on AppOpsManager.checkPackage semantics, so the correctness of the UID-to-package binding itself is left to the platform."
     ],
     "behavior_changes": [
      "enable, disable and setObserveMode now throw SecurityException, before any other processing, when the supplied package name is null or does not belong to the calling UID.",
      "setObserveMode with a null package now throws SecurityException before the isNfcEnabled check. The unit test that passed null with NFC disabled (and expected false) now passes PKG_NAME.",
      "NfcPermissions now gets AppOpsManager from the Context in its constructor, and checkPackage depends on it. The diff adds no null check."
     ],
     "preconditions": [
      "The caller can reach the INfcAdapter binder interface of the NFC service. The public NfcAdapter API routes to these methods; for example, NfcAdapter.setObserveModeEnabled calls sService.setObserveMode(enabled, mContext.getPackageName()) (per index search).",
      "Before the fix, the caller chose the pkg/packageName argument, and the service used it for device/profile-owner, allowlist or preferred-service decisions."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. The resolver reports 7/8 lines of the patched region present at android-security-14.0.0_r29. The material does not show the 14 code, so it cannot confirm whether the checkPackage calls are there.",
      "15": "Same fix: platform/packages/apps/Nfc d914feee6a9b in android-security-15.0.0_r17 adds unconditional checkPackage calls in enable, disable and setObserveMode (8/8 lines present). The AOSP android-15.0.0_r36 tree shows the same calls guarded by Flags.checkPassedInPackage() (per index search), so the security branch carries the unflagged form.",
      "16": "Adapted fix: platform/packages/modules/Nfc ebbca6f8125e in android-security-16.0.0_r8 is an aconfig flag cleanup. It makes the existing, previously flag-guarded checkPackage calls unconditional in six methods (enable, disable, setObserveMode, updateDiscoveryTechnology, setReaderMode, indicateDataMigration) in NfcNci/src/com/android/nfc/NfcService.java. 5/8 lines of the patched region present.",
      "17": "Not affected: not listed by the bulletin. The project is not present in this major's security tags."
     },
     "backport_notes": [
      "Backport the unflagged Android 15 form (direct mNfcPermissions.checkPackage calls) rather than the Flags.checkPassedInPackage()-guarded form seen in the 15 release tree. Also port the NfcPermissions constructor change so mAppOpsManager is initialized, and update any unit tests that pass a null package name.",
      "If the target tree has updateDiscoveryTechnology, setReaderMode or indicateDataMigration taking a package name, consider mirroring the Android 16 change, which also guards those methods."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether updateDiscoveryTechnology, setReaderMode and indicateDataMigration exist with a package-name parameter in the Android 15 tree and remain unchecked there.",
      "The exact privilege gained with a spoofed package name. The bulletin says EoP, High; the context shows device/profile-owner, allowlist and preferred-service decisions keyed on the string.",
      "Why Android 14 is reported as not affected while 7/8 lines of the patched region are present at android-security-14.0.0_r29."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28613": {
     "cve": "CVE-2026-28613",
     "summary": "In LiveTv's ChannelImpl.initAppLinkTypeAndIntent, the fix refuses to build a channel app-link Intent when the resolved target activity is exported and declares a permission, addressing intent redirection to permission-protected activities (per the commit title).",
     "component": "platform/packages/apps/TV (LiveTv), channel app link handling (src/com/android/tv/data/ChannelImpl.java)",
     "weakness": "Insufficient validation of an externally supplied Intent target (intent redirection); missing check that the target activity is permission protected",
     "root_cause": "ChannelImpl.initAppLinkTypeAndIntent parses mAppLinkIntentUri with Intent.parseUri(..., URI_INTENT_SCHEME), resolves it with intent.resolveActivityInfo(pm, 0), and stores it as mAppLinkIntent, which getAppLinkIntent() returns. The only validation was isProtectedActivity, which rejected targets inside LiveTv's own package (CommonConstants.BASE_PACKAGE and its sub-packages). An exported activity in any other package that is guarded by an android:permission was accepted as an app link target. The commit title describes this as intent redirection to permission-protected activities. The launch site is not shown in the material.",
     "fix_analysis": [
      "src/com/android/tv/data/ChannelImpl.java:initAppLinkTypeAndIntent: after resolving activityInfo, the fix computes isPermissionProtected = activityInfo.exported && (activityInfo.permission != null).",
      "If isPermissionProtected is true, the method logs 'Attempt to add app link to permission protected activity' and returns early. That leaves the values set at the top of the method (mAppLinkType = APP_LINK_TYPE_NONE, mAppLinkIntent = null), so no Intent is stored for that channel.",
      "The existing isProtectedActivity (own-package) check is kept and still runs first. The new check is a second, independent rejection.",
      "A code comment documents two cases that are deliberately not restricted: chooser intents (Intent.parseUri cannot carry a wrapped Intent.EXTRA_INTENT) and ACTION_SEND (stated as unable to obtain FLAG_GRANT_* here). The fix is a single commit in one file, 18 insertions and 1 deletion. The deletion is a whitespace-only reformat of an existing Log.w line."
     ],
     "completeness": [
      "Covers: any app link whose resolved activity is exported and has a non-null ActivityInfo.permission, on top of the existing block on LiveTv's own package.",
      "The check keys only on the activity's manifest android:permission attribute. Activities that are exported without a permission attribute but enforce access in other ways (for example runtime checks inside the activity) are still accepted. This is a design limit visible in the code, not necessarily a flaw.",
      "The parsed Intent is stored as-is: the fix does not strip flags or extras from the parsed URI. The commit's comment says chooser wrapping and ACTION_SEND grant flags cannot be reached through parseUri, but the fix adds no code for those cases.",
      "Resolution runs once, through resolveActivityInfo(pm, 0) at init time, and the result is cached in mAppLinkType. The check only reflects the activity resolved at that moment.",
      "The fix diff is identical on all four branches (same blob transition 5be1179d..62c06cc2)."
     ],
     "behavior_changes": [
      "Channels whose app link URI resolves to an exported, permission-protected activity now get no app link from this block (APP_LINK_TYPE_NONE, null Intent).",
      "A new Log.w message 'Attempt to add app link to permission protected activity: <uri>' is emitted in that case.",
      "No API, resource or manifest changes (the diff touches a single Java file)."
     ],
     "preconditions": [
      "A channel object with non-empty mAppLinkText and mAppLinkIntentUri reaches ChannelImpl.getAppLinkType(context) or getAppLinkIntent(context), which call initAppLinkTypeAndIntent on first use.",
      "The URI must parse via Intent.parseUri(URI_INTENT_SCHEME) and resolve to an ActivityInfo on the device.",
      "The material does not show where mAppLinkIntentUri comes from. Channel metadata is normally written by TV input apps into the TV provider and read by LiveTv (general platform knowledge)."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/TV e64e4b90fc77 in android-security-14.0.0_r29, identical diff (blob 5be1179d..62c06cc2); resolved by bug_id.",
      "15": "same fix: platform/packages/apps/TV 481a7a588acd in android-security-15.0.0_r17, identical diff; resolved by bug_id.",
      "16": "same fix: platform/packages/apps/TV 86fb106012ec in android-security-16.0.0_r8, identical diff; resolved by bug_id.",
      "17": "same fix: platform/packages/apps/TV a10682aac138 in android-security-17.0.0_r1, identical diff; resolved by bug_id."
     },
     "backport_notes": [
      "Single self-contained commit touching only ChannelImpl.initAppLinkTypeAndIntent. The same blob transition (5be1179d -> 62c06cc2) applies on 14 through 17, so it should cherry-pick cleanly onto trees that already have the isProtectedActivity check. Forks that changed or lack that check should insert the isPermissionProtected test after activityInfo is resolved and before mAppLinkIntent is assigned."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show where mAppLinkIntentUri is populated or which LiveTv code path launches mAppLinkIntent, so the attacker-controlled entry point is not confirmed.",
      "Why the per-major metric reports 7/8 lines present (which line differs) is not shown; the code at android-security-15.0.0_r17 shows the full check.",
      "Whether initAppLinkTypeAndIntent has fallback logic after the shown block (beyond line 710) is not shown.",
      "The two Cherrypick-From internal commits are not visible; whether they contained anything beyond this diff is unknown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28614": {
     "cve": "CVE-2026-28614",
     "summary": "Adds an isCallerValid() gate to SystemUI's SlicePermissionActivity.onCreate. The slice permission dialog is now shown only when the activity was actually launched by the slice provider's package, SystemUI itself, or the \"android\" package. Before, the activity trusted the package name supplied in the intent extra.",
     "component": "platform/frameworks/base, SystemUI (packages/SystemUI, SlicePermissionActivity, slice permission grant UI)",
     "weakness": "Missing caller validation (confused deputy): the identity of the requesting app was taken from an intent extra rather than from the real launcher",
     "root_cause": "SlicePermissionActivity.onCreate read mCallingPkg from the intent extra SliceProvider.EXTRA_PKG and took mProviderPkg from the resolved authority of EXTRA_BIND_URI. Its only check on these was verifyCallingPkg(), which compares the optional \"provider_pkg\" extra against the resolved provider. Nothing verified who actually started the activity. As a result, the permission dialog could be shown for a launch by any package with extras of its choosing, and the labels the user saw came from the possibly spoofed EXTRA_PKG.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java:onCreate: a new check `if (!isCallerValid()) { finish(); return; }` is inserted after mProviderPkg is resolved and verifyCallingPkg() runs, and before the app labels are loaded and the AlertDialog is built. An untrusted launcher therefore never reaches the dialog.",
      "packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java:isCallerValid (new): gets the real launcher from getLaunchedFromPackage() and falls back to getCallingPackage() when that is null. It returns true only if the result is non-null and equals mProviderPkg, getPackageName() (SystemUI) or \"android\". Otherwise it logs 'Direct launch blocked. Expected provider ... or system, but got ...' and returns false.",
      "packages/SystemUI/tests/src/com/android/systemui/SlicePermissionActivityTest.kt (new): unit tests for the provider package, com.android.systemui, android and the getCallingPackage fallback (none of these may finish), and for an arbitrary spoof package (must finish)."
     ],
     "completeness": [
      "The gate comes before the dialog is built in onCreate. The onClick grant code and any other entry points are not in the material.",
      "The check validates only the launcher's identity. It does not tie mCallingPkg (EXTRA_PKG) to the launcher, so a permitted launcher (provider package, SystemUI or android) can still name any package in EXTRA_PKG.",
      "If both getLaunchedFromPackage() and getCallingPackage() return null, the activity finishes (fail closed).",
      "verifyCallingPkg() is left unchanged. The material does not show its body beyond the early return.",
      "The check sits inside the existing try block, after pm.resolveContentProvider(...). What happens when the provider does not resolve depends on the pre-existing catch, which is not shown."
     ],
     "behavior_changes": [
      "SlicePermissionActivity now finishes immediately, without showing a dialog, when launched by any package other than the slice provider's package, com.android.systemui or android. It also logs an error line 'Direct launch blocked...'.",
      "Launches from the provider package, SystemUI or the system (android) package behave as before.",
      "Adds a new SystemUI test class, SlicePermissionActivityTest."
     ],
     "preconditions": [
      "An app other than the slice provider, SystemUI or android must be able to start SlicePermissionActivity and pass the pre-existing checks in onCreate: action SliceManager.ACTION_REQUEST_SLICE_PERMISSION, an EXTRA_BIND_URI whose content type resolves to SliceProvider.SLICE_TYPE, and an EXTRA_PKG of its choosing.",
      "The material does not show whether the activity is exported or otherwise reachable from third-party apps."
     ],
     "per_major": {
      "14": "same fix: commit c6b5038a9e23 in android-security-14.0.0_r29. The code change (isCallerValid, the gate and the test) is identical; only the line offsets differ (hunks at 66 and 110). 8/8 patched lines present.",
      "15": "same fix: commit 971f1d196295 in android-security-15.0.0_r17 (cherry-pick of 7f44449562bc). 8/8 patched lines present.",
      "16": "same fix: commit dba6ec2c05ff in android-security-16.0.0_r8. The diff is identical to the Android 15 change, with the same blob hashes (67012cb0584f..5a26c8474cc3). 8/8 patched lines present.",
      "17": "same fix: commit 7e25b93a1faa in android-security-17.0.0_r1. The diff is identical to the Android 15 change. 8/8 patched lines present."
     },
     "backport_notes": [
      "The fix is a single commit with no dependencies on other commits. The Java hunk applies to any SlicePermissionActivity that still has the verifyCallingPkg() call site. The Kotlin test depends on SysuiTestCase and the SystemUI test harness and can be dropped if the harness differs."
     ],
     "confidence": "high",
     "unknowns": [
      "The manifest declaration of SlicePermissionActivity (exported state, required permission) is not in the material.",
      "The onClick grant path and the rest of verifyCallingPkg() are not shown, so the grant call that was previously reachable cannot be seen.",
      "The material does not say whether exploitation requires the user to accept the dialog."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28617": {
     "cve": "CVE-2026-28617",
     "summary": "The fix makes WifiNetworkSuggestionsManager clear the vendor data (setVendorData(Collections.emptyList())) on every suggested WifiConfiguration before the suggestion is processed, so app-supplied PersistableBundle vendor data no longer enters the suggestion path that the commit message ties to unbounded XmlUtil (de)serialization.",
     "component": "platform/packages/modules/Wifi, WifiNetworkSuggestionsManager (network suggestion handling)",
     "weakness": "Missing input sanitization leading to unbounded nesting depth in persisted data (DoS via PersistableBundle depth bomb, per commit message)",
     "root_cause": "Per the commit message, XmlUtil.writeOuiKeyedDataToXml / readPersistableBundleFromBytes serialize and deserialize PersistableBundle data without limiting nesting depth. WifiNetworkSuggestionsManager accepted the vendor data in a suggestion's WifiConfiguration as-is, even though (per the commit message) setting it on a suggestion was never allowed. Deeply nested vendor data could therefore reach the unbounded (de)serialization. The commit message calls the result a 'persistent DoS'.",
     "fix_analysis": [
      "service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java: the change is in the suggestion add path, in the method that holds the 'bad wifi suggestion from app' check returning STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED. The hunk header does not show the method name. After that existing check and before convertLegacyFieldsToSecurityParamsIfNeeded, a new loop calls wns.wifiConfiguration.setVendorData(Collections.emptyList()) on every incoming WifiNetworkSuggestion.",
      "The loop is gated on SdkLevel.isAtLeastV().",
      "The commit adds import java.util.Collections and changes no other file. XmlUtil is not modified in this commit, even though the commit message names it."
     ],
     "completeness": [
      "Covers the network suggestion entry point: all vendor data on suggested WifiConfigurations is dropped (on SDK V and later) before further processing.",
      "Not covered by this commit: XmlUtil.writeOuiKeyedDataToXml / readPersistableBundleFromBytes are unchanged, so this diff does not harden any other path that uses those helpers. The material does not show whether a separate depth-bounding change exists.",
      "Only the add path is sanitized. The diff contains no migration code, so suggestions persisted with vendor data before the fix are not scrubbed by this change."
     ],
     "behavior_changes": [
      "On SDK V and later, any vendor data in a WifiNetworkSuggestion's WifiConfiguration is silently discarded at add time. The caller gets no error.",
      "The diff adds no new status codes, timeouts or log messages."
     ],
     "preconditions": [
      "The caller is an app that can submit WifiNetworkSuggestions to WifiNetworkSuggestionsManager.",
      "The new clearing code runs only when SdkLevel.isAtLeastV() is true.",
      "The suggestion must pass the existing validity check before the new loop; otherwise STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED is returned first."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14. All 8 lines of the patched region are reported present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 5dab8604e9c0 in android-security-15.0.0_r17, 8/8 lines present.",
      "16": "Same fix: commit 4d8225a6d03d in android-security-16.0.0_r8. The hunk is identical; only the import context differs (java.util.BitSet present). 8/8 lines present.",
      "17": "Same fix: commit 9b1148745f6b in android-security-17.0.0_r1, identical hunk at a shifted line offset, 8/8 lines present."
     },
     "backport_notes": [
      "Single-hunk, self-contained change. The target tree needs WifiConfiguration.setVendorData and com.android.modules.utils.build.SdkLevel. The commit message names XmlUtil as the vulnerable code but this commit leaves it unchanged, so check whether the target branch needs any XmlUtil hardening of its own."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether a separate commit adds depth bounding to XmlUtil.writeOuiKeyedDataToXml / readPersistableBundleFromBytes, and which tags it landed in.",
      "What the two Cherrypick-From sources correspond to.",
      "The exact name of the method containing the hunk (the hunk header does not show it).",
      "The exact failure mode of the DoS and whether it survives restarts or affects wifi service startup, beyond the commit message's 'persistent DoS' wording.",
      "Whether WifiConfiguration vendor data exists on SDK levels below V."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28626": {
     "cve": "CVE-2026-28626",
     "summary": "SetupPassthroughActivity in the LiveTv app now uses getLaunchedFromPackage() (on SDK_INT >= 34) to check that the package that launched it is CommonConstants.BASE_PACKAGE before it forwards the embedded setup Intent. If the check fails, it logs a warning and finishes without forwarding.",
     "component": "platform/packages/apps/TV (LiveTv), SetupPassthroughActivity",
     "weakness": "Intent redirection due to insufficient verification of the launching package",
     "root_cause": "SetupPassthroughActivity takes a nested setup Intent (setupIntent) from its incoming Intent, rewrites its extras (InputSetupActionUtils.removeSetupIntent, then setupIntent.putExtras) and forwards it. Before the fix, the only caller-related code in this block was a getCallingActivity() package comparison against CommonConstants.BASE_PACKAGE. The launching package was not checked via getLaunchedFromPackage() before forwarding. The material does not show what the existing comparison does when it fails, or why it was not enough on its own.",
     "fix_analysis": [
      "src/com/android/tv/SetupPassthroughActivity.java (setupIntent forwarding block inside the try): when Build.VERSION.SDK_INT >= 34, call getLaunchedFromPackage() and require a non-null value equal to CommonConstants.BASE_PACKAGE. Otherwise log 'Calling package ... is not trusted. Not forwarding intent.', call finish() and return.",
      "The new check is placed before the existing getCallingActivity() comparison, which is unchanged. Both checks now run in sequence.",
      "Adds import android.os.Build for the SDK gate."
     ],
     "completeness": [
      "Covers the setup Intent forwarding path in SetupPassthroughActivity shown in the diff by adding a launching-package check.",
      "The new check is gated on SDK_INT >= 34. On a runtime below API 34 only the original getCallingActivity() code applies. The patched branches (14 to 17) are API 34 or higher, so the gate is active there.",
      "Only an exact package-name match is done. The diff adds no signature or UID check. The code after the getCallingActivity() check is not shown in the material and is not changed by the diff."
     ],
     "behavior_changes": [
      "On API 34+, if SetupPassthroughActivity is launched by any package other than CommonConstants.BASE_PACKAGE, it now finishes immediately with a warning log and does not forward the setup Intent.",
      "A launch where getLaunchedFromPackage() returns null is also rejected."
     ],
     "preconditions": [
      "The sender must be able to reach SetupPassthroughActivity with an Intent whose extras carry a setup Intent handled by InputSetupActionUtils. The diff does not show whether the activity is exported in the manifest."
     ],
     "per_major": {
      "14": "same fix: commit 738d2cc1f03e in android-security-14.0.0_r29 with the same diff, resolved by change_id, 8/8 patched lines present.",
      "15": "same fix: commit f23429d7cad6 in android-security-15.0.0_r17, resolved by change_id, 8/8 patched lines present.",
      "16": "same fix: commit 856a260c2504 in android-security-16.0.0_r8 with the same diff, resolved by change_id, 8/8 patched lines present.",
      "17": "same fix: commit 25c98c910675 in android-security-17.0.0_r1 with the same diff, resolved by change_id, 8/8 patched lines present."
     },
     "backport_notes": [
      "Self-contained change to a single file, with the same blob index 2a4a556f..87a66a0e on all four branches, so it applies cleanly wherever SetupPassthroughActivity.java matches. Activity.getLaunchedFromPackage() is a public API from API 34, which matches the SDK_INT >= 34 gate. A backport to a platform below 34 would need a different source for the caller's identity (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The code after the getCallingActivity() check (what the untrusted branch does and how setupIntent is started) is not in the material.",
      "Why the existing getCallingActivity() check was not enough to stop the redirection.",
      "Whether SetupPassthroughActivity is exported in the manifest and which intent filters it declares.",
      "The content of the two internal commits referenced by Cherrypick-From, and whether they differ from the AOSP commit."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28631": {
     "cve": "CVE-2026-28631",
     "summary": "IntentForwarderActivity now calls getWindow().setHideOverlayWindows(true) when it shows the mini resolver consent dialog, so overlay windows from other apps are hidden while the cross-profile redirection prompt is displayed; a companion commit refactors the activity's Injector so the test suite can run against it.",
     "component": "platform/frameworks/base, core/java/com/android/internal/app/IntentForwarderActivity (cross-profile intent forwarding mini resolver)",
     "weakness": "Missing overlay-window protection on a security-relevant consent UI (tapjacking exposure)",
     "root_cause": "In IntentForwarderActivity.buildMiniResolver (Android 15), the activity set the R.layout.miniresolver content view and wired the open and cancel buttons (the open button calls startActivityAsCaller with the forwarded launchIntent), but never asked for overlay windows to be hidden. Other apps' overlay windows could therefore cover the window while the user was asked to consent to redirecting an intent into another profile.",
     "fix_analysis": [
      "Commit 8e66b48894f7 (Set hide overlays on mini resolver), core/java/com/android/internal/app/IntentForwarderActivity.java:buildMiniResolver: adds getWindow().setHideOverlayWindows(true) immediately after setContentView(layoutId). This one line is the only production change that addresses the vulnerability.",
      "Commit 323651e81585 (Fix & speed up IntentForwarderActivityTest), linked by the same bug 461500706, in IntentForwarderActivity.java: onCreate now gets mExecutorService from mInjector.getExecutorService() instead of calling Executors.newSingleThreadExecutor() directly. isDeviceProvisioned() now delegates to mInjector.isDeviceProvisioned(). The Injector interface and the activity's default injector implementation gain getExecutorService() and isDeviceProvisioned(), with the previous behavior as the default. Production behavior is unchanged; this only adds a test seam.",
      "Same commit, core/tests/coretests/AndroidManifest.xml: adds <uses-permission android:name=\"android.permission.HIDE_OVERLAY_WINDOWS\"/>. Per the commit message, IntentForwarderActivity crashes in the test when it calls setHideOverlayWindows() without that permission.",
      "Same commit, core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java: removes the 30 second awaitTermination from the wrapper's onCreate. It injects a synchronous AbstractExecutorService and a stubbed isDeviceProvisioned() instead of writing Settings.Global.DEVICE_PROVISIONED. It also replaces two backwards assertNull checks with assertNotNull. On Android 16 and 17 the commit also removes the test from exclude_filters in core/tests/coretests/Android.bp, which re-enables it."
     ],
     "completeness": [
      "The overlay protection is applied only to the window built in buildMiniResolver (the R.layout.miniresolver consent dialog). The material shows one call site, the consent path for redirection into the main profile at lines 303-315. It does not show whether other mini-resolver renderings exist outside this method.",
      "Other paths of IntentForwarderActivity are not changed by the fix. The material does not show whether any of them shows UI that would need the same protection.",
      "The second commit changes no security-relevant logic: the default injector implementation keeps the same executor and device-provisioned behavior as before."
     ],
     "behavior_changes": [
      "While the mini resolver is displayed, the activity window has the hide-overlay-windows flag set, so non-system application overlay windows are hidden for that time (general platform knowledge about setHideOverlayWindows semantics).",
      "setHideOverlayWindows needs android.permission.HIDE_OVERLAY_WINDOWS. The coretests manifest had to be granted it, because per the commit message the activity otherwise crashes in the test.",
      "IntentForwarderActivity.mExecutorService and isDeviceProvisioned() now come from the Injector. Any Injector implementation, for example in tests that override createInjector, must implement the two new interface methods."
     ],
     "preconditions": [
      "An intent reaches IntentForwarderActivity under the ForwardIntentToParent or ForwardIntentToManagedProfile class name and is allowed to be forwarded to another profile.",
      "The flow must reach the mini resolver consent UI (buildMiniResolver), for example the path at lines 303-315 that asks the user to consent to redirection into the main profile.",
      "Another app able to draw overlay windows would have to be present for the missing protection to matter. The material does not describe any attack scenario."
     ],
     "per_major": {
      "14": "Fix applied, with different surrounding code: commit cb471de52128 in android-security-14.0.0_r29 adds getWindow().setHideOverlayWindows(true) after setContentView(layoutId) at line ~240, just after the consent Log statement. The enclosing method is not shown. Only the one-line commit is shown for 14 (4/4 patched lines present).",
      "15": "same fix: commit 8e66b48894f7 in android-security-15.0.0_r17 adds the line in buildMiniResolver; test refactor commit 323651e81585 is also present (4/4 patched lines present).",
      "16": "Resolved in android-security-16.0.0_r8 via commit 71d6dc109528, the test refactor (8/8 patched lines present). It also removes the test from Android.bp exclude_filters. The one-line setHideOverlayWindows commit is not shown for 16.",
      "17": "Resolved in android-security-17.0.0_r1 via commit 323c8b968a88, the same test refactor as on 16, matched by change_id (8/8 lines present). The setHideOverlayWindows commit itself is not shown for 17."
     },
     "backport_notes": [
      "Apply the one-line 'Set hide overlays on mini resolver' change first; it is the actual security fix. Put it right after setContentView(R.layout.miniresolver) in whichever method shows the mini resolver. The test commit is optional. If you take it, apply it afterwards: it needs HIDE_OVERLAY_WINDOWS added to core/tests/coretests/AndroidManifest.xml and getExecutorService()/isDeviceProvisioned() in every Injector implementation.",
      "The material includes no comparison against a fork, so the state of the patched files in a given fork is unknown."
     ],
     "confidence": "medium",
     "unknowns": [
      "The bulletin and commits do not describe the concrete attack scenario or which user action on the mini resolver is the EoP target.",
      "Whether the setHideOverlayWindows commit is on Android 16 and 17 is not shown in the per-major diffs.",
      "Whether buildMiniResolver has other call sites, and what the enclosing method is on Android 14, is not shown.",
      "Whether other consent-style windows reachable from cross-profile forwarding (for example the full ResolverActivity) already hide overlay windows is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28633": {
     "cve": "CVE-2026-28633",
     "summary": "In safe mode, VoiceInteractionManagerService no longer parses the XML recognition-service metadata of non-system (3p) recognizers in RecognitionServiceInfo.getAvailableServices, and no longer parses the metadata of the already-configured recognizer. This guards safe-mode boot against oversized metadata from a malicious app.",
     "component": "platform/frameworks/base, services/voiceinteraction (VoiceInteractionManagerService / RecognitionServiceInfo)",
     "weakness": "Uncontrolled resource consumption. Per the commit message and code comments, system code parses large app-supplied recognition-service XML metadata, and that can cause OOM and crash the system (DoS), including during safe-mode boot.",
     "root_cause": "RecognitionServiceInfo.getAvailableServices() iterates every resolved RecognitionService and calls parseInfo() on each one. VoiceInteractionManagerService also calls RecognitionServiceInfo.parseInfo() on the currently configured recognizer (curRecognizer branch, lines 636-671 in context). Neither call was skipped in safe mode. Per the commit message, a malicious app can ship a very large recognition-service metadata XML whose parsing can crash the system. Because the parsing also ran in safe mode, safe mode did not protect against it, even though 3p apps are not launchable in safe mode anyway.",
     "fix_analysis": [
      "RecognitionServiceInfo.java:getAvailableServices gains a boolean isSafeMode parameter. Inside the ResolveInfo loop, when isSafeMode is true and the service's applicationInfo.flags lacks ApplicationInfo.FLAG_SYSTEM, parseInfo() is skipped. A RecognitionServiceInfo(serviceInfo, selectableAsDefault=true, parseError=\"\") is added directly and the loop continues. System (preinstalled) recognizers are still parsed in safe mode.",
      "VoiceInteractionManagerService.java (curRecognizer != null branch): the condition guarding RecognitionServiceInfo.parseInfo(mContext.getPackageManager(), recognizerInfo) changes from `recognizerInfo != null` to `recognizerInfo != null && !mSafeMode`. As a result, the configured recognizer's metadata is never parsed in safe mode. This branch does not distinguish system from 3p recognizers.",
      "VoiceInteractionManagerService.java (recognizer discovery path near the 'no available voice recognition services found' log): the call site becomes RecognitionServiceInfo.getAvailableServices(mContext, userHandle, mSafeMode), passing the existing mSafeMode field through."
     ],
     "completeness": [
      "The fix covers safe mode only. In normal boot, getAvailableServices() and the curRecognizer path still call parseInfo() on 3p metadata. The commit message states the intent: avoid parsing 3p metadata in safe mode.",
      "RecognitionServiceInfo.parseInfo() itself is not changed by the diff.",
      "In getAvailableServices, the skip applies only to apps without FLAG_SYSTEM, so system recognizers are still parsed in safe mode. In the curRecognizer path, parsing is skipped for any recognizer in safe mode. The two guards are therefore not symmetric.",
      "Only the two call sites inside services/voiceinteraction are covered. A TODO in the file mentions using this class in com.android.settings.applications.assist.VoiceInputHelper. Whether that Settings code parses the same metadata is not shown."
     ],
     "behavior_changes": [
      "In safe mode, non-system recognition services are listed with selectableAsDefault=true and an empty parse error, and their metadata is not read. Per the commit comment, they still will not be auto-selected because only preinstalled recognizers are eligible for auto selection.",
      "In safe mode, the isSelectableAsDefault() check on the currently configured recognizer is skipped. An existing default is left in place instead of being unset and replaced by a new search.",
      "Signature change: RecognitionServiceInfo.getAvailableServices(Context, int) becomes getAvailableServices(Context, int, boolean isSafeMode). Any other caller must be updated.",
      "Normal-mode (non-safe-mode) behavior does not change."
     ],
     "preconditions": [
      "An installed non-system app provides a recognition service whose metadata is a large XML resource (per commit message).",
      "VoiceInteractionManagerService reaches the curRecognizer parse path or the getAvailableServices path while the device boots in safe mode (mSafeMode). Per the code comments, this caused boot issues."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 20ecd72dd4c8 in android-security-14.0.0_r29. The RecognitionServiceInfo.java change is identical. The VoiceInteractionManagerService.java hunks sit at different line offsets (old lines 567 and 831). 8/8 patched lines present.",
      "15": "same fix: platform/frameworks/base df4d44965279 in android-security-15.0.0_r17 (cherry-pick of a81b51b6a6e0), with VoiceInteractionManagerService.java hunks at old lines 648 and 912. 8/8 patched lines present.",
      "16": "same fix: platform/frameworks/base a660d0c46482 in android-security-16.0.0_r8, identical content, hunks at old lines 648 and 912. 8/8 patched lines present.",
      "17": "same fix: platform/frameworks/base 4c5500a0d604 in android-security-17.0.0_r1, identical content, hunks at old lines 818 and 1101. 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit. It depends only on the existing mSafeMode field in VoiceInteractionManagerService and on ApplicationInfo.FLAG_SYSTEM. The RecognitionServiceInfo.java hunk applies identically on all four majors (same blob 05d4b9305d30 -> 376ba571f790). Only the VoiceInteractionManagerService.java line offsets differ. Any out-of-tree caller of getAvailableServices(Context, int) must be updated for the new boolean parameter."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the body of RecognitionServiceInfo.parseInfo(), so it is unknown which parsing step consumes the memory and whether any size limit exists.",
      "The names of the VoiceInteractionManagerService methods that contain the two patched call sites are not shown.",
      "Whether the recognizer parsing paths are gated by mEnableService or another device configuration is not shown.",
      "Whether the Settings-side code referenced in the file's TODO (VoiceInputHelper) parses the same metadata is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28634": {
     "cve": "CVE-2026-28634",
     "summary": "Adds a per-user subscription association check to PhoneInterfaceManager.handleUssdRequest so a caller can only send USSD requests on a subscription associated with its own user, throwing SecurityException otherwise.",
     "component": "platform/packages/services/Telephony, PhoneInterfaceManager (ITelephony binder service, USSD handling)",
     "weakness": "Missing authorization check (missing per-user subscription association check on a caller-supplied subId)",
     "root_cause": "PhoneInterfaceManager.handleUssdRequest(int subId, String ussdRequest, ResultReceiver wrappedCallback) accepted a caller-supplied subId. After the existing entry checks (enforceCallPermission() on Android 14; enforceTelephonyFeatureWithException(..., FEATURE_TELEPHONY_RADIO_ACCESS, \"handleUssdRequest\") on 15+), it called Binder.clearCallingIdentity() and went on to validate and dispatch the USSD request. Nothing verified that the calling user was associated with that subscription, so a caller running as one user could issue USSD requests on a subscription that is not associated with its user.",
     "fix_analysis": [
      "src/com/android/phone/PhoneInterfaceManager.java:handleUssdRequest: before Binder.clearCallingIdentity(), the fix calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mApp, subId, Binder.getCallingUserHandle()) and throws SecurityException(\"The subscription <subId> is not associated with the calling user\") when it returns false. Because the check runs while the caller's identity is still in place, Binder.getCallingUserHandle() reflects the actual caller.",
      "tests/src/com/android/phone/PhoneInterfaceManagerTest.java: adds two tests. testHandleUssdRequest_SubscriptionNotAssociatedWithUser_ThrowsSecurityException mocks SubscriptionManagerService.isSubscriptionAssociatedWithUser to return false and expects SecurityException. testHandleUssdRequest_ValidSubscription_Success mocks it to return true and expects the call to get past the check. Test setUp is extended to install the mocked SubscriptionManagerService as sInstance via replaceInstance and to stub getActiveSubIdList. On Android 15 setUp also stubs getHalVersion, getContext, getDataNetworkController and getDefaultPhone on the mPhone mock."
     ],
     "completeness": [
      "Only handleUssdRequest is changed. The check runs once, at the entry of this method, before the identity-cleared block that validates and dispatches the USSD request.",
      "The check runs on the raw caller-supplied subId before the existing SubscriptionManager.isValidSubscriptionId(subId) validation. The material does not show how TelephonyPermissions.checkSubscriptionAssociatedWithUser treats invalid or default subIds.",
      "Existing checks (enforceCallPermission on 14, feature enforcement on 15+) are kept unchanged. The fix only adds code.",
      "This commit touches no other subId-taking ITelephony methods in PhoneInterfaceManager. The material does not show whether they already have equivalent checks."
     ],
     "behavior_changes": [
      "handleUssdRequest now throws SecurityException (message: \"The subscription <subId> is not associated with the calling user\") for callers whose user is not associated with the given subscription. Before the fix, the request proceeded.",
      "The exception is raised before Binder.clearCallingIdentity(), so rejected callers get no identity clearing and no USSD dispatch.",
      "Callers whose user is associated with the subscription see no change at this check."
     ],
     "preconditions": [
      "The caller reaches ITelephony.handleUssdRequest in the phone process and passes the existing entry checks. On Android 14 that is enforceCallPermission(). On 15+ the shown context has enforceTelephonyFeatureWithException for FEATURE_TELEPHONY_RADIO_ACCESS, which per the index-provided function body is skipped unless the feature-mapping flag, compat change and vendor API level conditions hold.",
      "A configuration in which the caller's user is not associated with the target subId."
     ],
     "per_major": {
      "14": "same fix: commit 2a177c06ec48 in android-security-14.0.0_r29. The identical 5-line check is inserted after enforceCallPermission() in handleUssdRequest. The test file changes are larger (adds AppOpsManager, AudioManager and SatelliteController mocks, SetFlagsRule, and UiccController/PinStorage fields), but the two new tests are the same.",
      "15": "same fix: commit 83d27ac3f32f in android-security-15.0.0_r17. The check is inserted after enforceTelephonyFeatureWithException(..., FEATURE_TELEPHONY_RADIO_ACCESS, ...). Test setUp adds mPhone and DataNetworkController stubs in addition to the SubscriptionManagerService replacement.",
      "16": "same fix: commit 03cc6ee76027 in android-security-16.0.0_r8. The production change is identical. The test diff removes a local Phone mPhone mock and adds the same setup and tests.",
      "17": "same fix: commit 5ef227b0a2f7 in android-security-17.0.0_r1. The production change is identical. The test diff reshuffles existing mock fields (removing the DataNetworkController mock) and adds the same setup and tests."
     },
     "backport_notes": [
      "The production change is a single self-contained 5-line insertion near the top of handleUssdRequest, placed before Binder.clearCallingIdentity(). It requires TelephonyPermissions.checkSubscriptionAssociatedWithUser (called with a Context, an int subId and a UserHandle) to be available on the target branch. The test changes differ per branch only in mock scaffolding and can be adapted separately."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of TelephonyPermissions.checkSubscriptionAssociatedWithUser is not in the material, including how it handles invalid subIds, system callers, and devices without multiple users. Which project contains it is also not shown.",
      "The provided context does not show whether handleUssdRequest on Android 15/16/17 still calls enforceCallPermission() before the feature check. Only the Android 14 diff shows it.",
      "The two internal Cherrypick-From commits referenced in the commit message are not provided, so whether they contain anything beyond this change is unknown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28638": {
     "cve": "CVE-2026-28638",
     "summary": "MediaProvider's XMP redaction now treats an XML element as sensitive when any of its attributes (not only its tag name) is an EXIF tag in the redacted set, and IsoInterface's flattened box list now includes the 'XMP_' (BOX_XMP) box, so location data stored as XMP attributes in ISO media files is redacted for apps lacking ACCESS_MEDIA_LOCATION.",
     "component": "platform/packages/providers/MediaProvider, media metadata redaction (util/IsoInterface.java, util/XmpDataParser.java; util/XmpInterface.java on Android 14)",
     "weakness": "Incomplete sanitization / redaction bypass (location metadata not redacted when encoded as XMP attributes or stored in an unindexed container box)",
     "root_cause": "Two flaws, from the diff. (1) In XmpDataParser the redaction check was `else if (NS_EXIF.equals(ns) && RedactionUtils.getsRedactedExifTags().contains(name))`. It only matched START_TAG elements whose own name is a redacted EXIF tag, so XMP properties carried as attributes (e.g. exif:GPSLatitude=\"...\" on an rdf:Description element) were never examined. The comment in the redaction-range loop already said \"attributes or tags, so we're willing to look for both\", but the code only looked at tags. (2) In IsoInterface.parseNextBox on the 16/17 base, flattening is filtered by a switch over box types and BOX_XMP was missing from it. An 'XMP_' box was therefore never added to mFlattened, so the BOX_XMP lookups found nothing and XMP in that box was not redacted.",
     "fix_analysis": [
      "XmpDataParser.java: a new private isSensitiveTagOrAttribute() returns true if the current element's namespace is NS_EXIF and its name is in RedactionUtils.getsRedactedExifTags(), or if any attribute i (getAttributeNamespace(i)/getAttributeName(i)) meets the same condition.",
      "XmpDataParser.java, the parse loop that fills the XmpInterface builder: the `else if (NS_EXIF ...)` branch is replaced by a separate `if (isSensitiveTagOrAttribute())` after the else-if chain. Elements that also match an earlier branch (e.g. rdf:Description, xmpMM fields) are therefore checked too. The existing redaction block (start = offset, then the do/while loop) now runs for these elements.",
      "XmpDataParser.java, redaction-range loop (returns redactedRanges): the tag-only check is replaced with isSensitiveTagOrAttribute() in the same way.",
      "IsoInterface.java (Android 15 commit): parsing is restructured so parseNextBox builds a filtered flattened list directly (mRoots, Box.children and the ArrayDeque flatten pass are removed). A switch adds BOX_META, BOX_HDLR, BOX_XYZ, BOX_XMP, BOX_LOCI, BOX_GPS, BOX_GPS0 and BOX_UUID (only when the uuid equals the new constant XMP_UUID) to the list. A new allocateBuffer(type,size) caps box data at MAX_XMP_SIZE_BYTES (1 MiB) and catches OutOfMemoryError. UUID box data is now read only for XMP_UUID. getBoxRanges(UUID)/getBoxBytes(UUID) are replaced by getBoxRangesForXmpUuid()/getBoxBytesForXmpUuid(), and XmpDataParser.XmpData.extractXmpData is updated to call them.",
      "Tests: adds tests/res/raw/gps_test_xmp.mp4 and XmpDataParserTest.testRedactXmp_RemovesExifGpsAttributes, which asserts that getRedactedXmp() no longer contains exif:GPSLatitude or exif:GPSLongitude."
     ],
     "completeness": [
      "Covers EXIF-namespace sensitive fields written either as element names or as attributes of any element, in both the builder parse loop and the redaction-range loop.",
      "Only attributes in NS_EXIF (http://ns.adobe.com/exif/1.0/) are matched. The fix adds no other namespaces, and the set of names is still RedactionUtils.getsRedactedExifTags().",
      "On 15/16/17, XmpData.extractXmpData still uses only the first XMP UUID box, and falls back to the first BOX_XMP only when no UUID box exists (the post-fix XmpDataParser blob 3e48d1d91 is the same on all three). The Android 14 variant instead processes every UUID and XMP_ box via the new getBoxBytesList/getBoxRangesList.",
      "On Android 15, the restructured IsoInterface drops an XMP box whose payload exceeds MAX_XMP_SIZE_BYTES: allocateBuffer returns null and parseNextBox returns false before the switch adds the box. Such a box is not in mFlattened, and this commit does not parse or range-redact its content."
     ],
     "behavior_changes": [
      "The redacted range now starts at elements that carry a sensitive EXIF attribute, so other content of those elements (e.g. other rdf:Description attributes) can also be redacted. The exact end of the range depends on the existing do/while loop, which is not shown.",
      "Android 15 only: IsoInterface.mFlattened now holds only location/XMP-related box types, so getBoxRanges/getBoxBytes for other box types return empty/null. Payloads of non-XMP UUID boxes are no longer read into memory.",
      "Android 15 only: an XMP (XMP_ or XMP UUID) box larger than 1 MiB is skipped with a warning and parseNextBox returns false, which also stops parsing of the remaining sibling boxes at that level.",
      "Android 15 only: public API change in IsoInterface: getBoxRanges(UUID) and getBoxBytes(UUID) are replaced by getBoxRangesForXmpUuid() and getBoxBytesForXmpUuid()."
     ],
     "preconditions": [
      "A media file in ISO base media format (e.g. MP4) with XMP metadata in an 'XMP_' box or XMP UUID box, where EXIF location fields are written as XML attributes rather than child elements.",
      "The file is read through MediaProvider's redaction path by a caller without ACCESS_MEDIA_LOCATION (per the commit message). The exact MediaProvider entry point that calls XmpDataParser/IsoInterface is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix (15168c2e6f84, android-security-14.0.0_r29): changes XmpInterface rather than XmpDataParser. XmpInterface.process() gains an attribute scan that tracks nested START/END tags by depth and then continues, and fromContainer() processes every XMP UUID and XMP_ box via the new IsoInterface.getBoxBytesList/getBoxRangesList. The diff does not change box flattening. The test is added to XmpInterfaceTest.",
      "15": "adapted fix (cb04f3531ff2, android-security-15.0.0_r17): the XmpDataParser attribute check is identical to 16/17, plus a larger IsoInterface rewrite that adds the filtered flattened list (including BOX_XMP), the XMP_UUID constant, the 1 MiB cap via allocateBuffer, and renamed XMP UUID getters.",
      "16": "fix (4bdbc2fa9f53, android-security-16.0.0_r8): adds `case BOX_XMP:` to the existing flatten switch in IsoInterface.parseNextBox, removes unused ArrayDeque/Queue imports, and makes the XmpDataParser isSensitiveTagOrAttribute change.",
      "17": "fix (11009f3800f9, android-security-17.0.0_r1): identical to the Android 16 diff."
     },
     "backport_notes": [
      "The core change is the attribute check in XmpDataParser (or XmpInterface on trees like 14). The IsoInterface `case BOX_XMP:` addition is needed only on trees that already have the filtered flatten switch. Code that flattens every box (mRoots + ArrayDeque, as in Android 15 before this commit) already indexes XMP_ boxes. The 15 commit also restructures flattening and renames getBoxBytes(UUID)/getBoxRanges(UUID), so any other callers of those methods must be updated when taking that version."
     ],
     "confidence": "high",
     "unknowns": [
      "How the existing do/while in XmpDataParser ends (whether it stops at the first END_TAG or tracks depth) is not shown. The Android 14 variant adds explicit depth tracking.",
      "Whether the ExifInterface (image) XMP path goes through the same XmpDataParser loops and so also gets the attribute check is not shown.",
      "The MediaProvider call site that applies these redaction ranges when serving files to apps without ACCESS_MEDIA_LOCATION is not in the material.",
      "How Android 14's IsoInterface flattens boxes (and so whether XMP_ boxes were indexed there) is not shown.",
      "The code context at android-security-15.0.0_r17 contains a test, testContainer_IsoRedactionRanges_OversizedXmp, that references R.raw.gps_test_xmp_big, which this commit does not add. A further IsoInterface change may exist in that tag, but its content is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28642": {
     "cve": "CVE-2026-28642",
     "summary": "ActivityStarter now clears the ActivityRecord's mLaunchTaskBehind flag when the background activity launch (BAL) verdict blocks the start, so a launch that BAL blocks no longer keeps the launch-behind flag.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager (ActivityStarter, background activity launch control)",
     "weakness": "Missing enforcement of an authorization decision on a side path (the BAL block verdict was not applied to the launch-task-behind flag), leading to a bypass of the background activity launch restriction",
     "root_cause": "In ActivityStarter, the ActivityRecord r is built with ActivityRecord.Builder (including .setActivityOptions(checkedOptions)) and can carry mLaunchTaskBehind = true. The BAL verdict (balVerdict, or balCode on Android 14) is already available at this point and is used later, for example for resumeAppSwitches, but the launch-behind flag was not reconciled with it. So when BAL blocked the launch, the record could still carry mLaunchTaskBehind. The fix comment says the flag is reset 'to avoid making it visible if the activity launch should be blocked', and the commit message says there is no need to perform the launch-behind animation if BAL blocks.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java (ActivityRecord creation region, right after the ActivityRecord.Builder(...).build() call and before mLastStartActivityRecord = r): adds `if (r.mLaunchTaskBehind && balVerdict.blocks())`, which logs Slog.w (\"Disallowed launching behind for a background launch\") and sets r.mLaunchTaskBehind = false.",
      "On Android 14 the same logic uses the older BAL API: `balCode == BAL_BLOCK` instead of `balVerdict.blocks()`."
     ],
     "completeness": [
      "Covers only the single point in ActivityStarter where the ActivityRecord is created. The flag is cleared only when the BAL verdict is a block.",
      "The fix does not change how mLaunchTaskBehind is derived from ActivityOptions, and it does not touch any other consumer of the flag. It relies on the BAL verdict already being computed correctly earlier in the same method.",
      "The diff touches no other code paths that set or honor mLaunchTaskBehind. The material does not show whether other paths could bypass this check."
     ],
     "behavior_changes": [
      "When BAL blocks a start and the caller requested launch-task-behind, the request is now processed with mLaunchTaskBehind = false and a warning is logged (Slog.w with TAG, \"Disallowed launching behind for a background launch\").",
      "No new error code or exception. Launches that BAL allows are unaffected."
     ],
     "preconditions": [
      "An activity start reaches ActivityStarter's record-creation path and the resulting ActivityRecord has r.mLaunchTaskBehind set to true.",
      "The BAL check for that start yields a block verdict (balVerdict.blocks(), or balCode == BAL_BLOCK on Android 14)."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base d94f8b27bc84 in android-security-14.0.0_r29. Same 7-line insertion at the same location, but using the older `balCode == BAL_BLOCK` check. Patched region fully present (3/3 lines).",
      "15": "same fix: platform/frameworks/base d98cb1490fc3 in android-security-15.0.0_r17, using `balVerdict.blocks()`. Patched region fully present (3/3 lines).",
      "16": "same fix: platform/frameworks/base 2b8a07bf7437 in android-security-16.0.0_r8. Identical hunk using `balVerdict.blocks()`, at a different line offset. Patched region fully present (3/3 lines).",
      "17": "not affected (not listed by the bulletin). The patched region is present (3/3 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single-hunk change. Place it directly after the ActivityRecord.Builder(...).build() that creates `r` and before `mLastStartActivityRecord = r`. On trees without the BalVerdict object, use the integer BAL code comparison (`balCode == BAL_BLOCK`) as Android 14 does. Make sure the BAL result variable is already computed at that point in your fork."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the downstream code that acts on mLaunchTaskBehind, so how the launch-behind path makes an activity visible is inferred only from the commit message and the code comment.",
      "The material does not show how mLaunchTaskBehind is populated from checkedOptions in ActivityRecord.Builder.",
      "The material does not name the enclosing method of the patched region.",
      "The diff includes no test (the commit says 'locally verified')."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28644": {
     "cve": "CVE-2026-28644",
     "summary": "ActivityTaskManagerService.startNextMatchingActivity() no longer always forwards the original caller's launchedFromUid/launchedFromPackage/launchedFromFeatureId to the next resolved activity. It now forwards them only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID. Otherwise the calling identity is reset to the intermediary's own UID and package.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManagerService (activity start, startNextMatchingActivity)",
     "weakness": "Caller identity spoofing / confused deputy: a trusted caller's UID and package are always passed on through an untrusted intermediary, so permission checks on the target activity can be evaluated against the wrong identity.",
     "root_cause": "In services/core/java/com/android/server/wm/ActivityTaskManagerService.java, startNextMatchingActivity() built the ActivityStarter with setCallingUid(r.launchedFromUid), setCallingPackage(r.launchedFromPackage) and setCallingFeatureId(r.launchedFromFeatureId) for every intermediary activity r, whoever r was. Only realCallingUid/realCallingPid had been changed earlier to use the actual Binder caller (the BAL fix for b/230492947 and b/337726734 mentioned in the surrounding comment). As a result, an app whose activity was launched by another app could call Activity.startNextMatchingActivity(), and the next matching activity would start with the original launcher's UID and package as the calling identity. Per the commit message, this let an intermediary trampoline an implicit intent from a privileged app to a victim app and bypass permission checks such as REQUEST_INSTALL_PACKAGES.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java:startNextMatchingActivity(): the builder chain is split so the ActivityStarter is held in a local `starter` instead of being executed inline. The three identity setters (setCallingUid/setCallingPackage/setCallingFeatureId) are removed from the chain. setCallingPid(-1), setRealCallingPid(origCallingPid), setRealCallingUid(origCallingUid), setActivityOptions(options) and setUserId(userId) are unchanged.",
      "Same function: a new gate computes `intermediaryUid = r.getUid()`. If `!UserHandle.isSameApp(intermediaryUid, r.launchedFromUid) && UserHandle.getAppId(intermediaryUid) != Process.SYSTEM_UID`, the starter gets setCallingUid(intermediaryUid) and setCallingPackage(r.packageName), and no feature id is set. Otherwise the previous behaviour is kept: setCallingUid(r.launchedFromUid), setCallingPackage(r.launchedFromPackage), setCallingFeatureId(r.launchedFromFeatureId). Then `starter.execute()` is called and the result is handled as before.",
      "core/java/android/app/Activity.java:startNextMatchingActivity(Intent, Bundle) javadoc: says the propagated caller identity reflects the intermediary unless it shares the original caller's UID, and that receivers should validate the immediate caller. The code checks app ID, not exact UID. No code change in this file.",
      "services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java: adds three tests (differentAppId_propagatesIntermediary, sameAppId_propagatesOriginalCaller, systemUid_propagatesOriginalCaller). They use a spied ActivityStarter and a mocked IPackageManager.queryIntentActivities to check which setCallingUid/setCallingPackage values are applied.",
      "services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java:ActivityBuilder: adds setIntent(Intent), and build() uses the supplied intent instead of always constructing a component-only Intent. This is test-only support for the new tests."
     ],
     "completeness": [
      "Covers the single identity-forwarding site in ActivityTaskManagerService.startNextMatchingActivity(). The realCallingUid/realCallingPid path already used the Binder caller before this change and is untouched.",
      "The trust decision is based only on UID: UserHandle.isSameApp (same app ID, any user) or app ID == Process.SYSTEM_UID. Privileged intermediaries that do not run as SYSTEM_UID are treated as untrusted and now have their identity squashed. Any component running under SYSTEM_UID is trusted without further checks.",
      "In the squash branch setCallingFeatureId is not called, so no feature id is propagated. The fix does not try to derive the intermediary's feature id.",
      "r.launchedFromUid is used as stored on the intermediary ActivityRecord. The fix does not validate that field, only whether it should be forwarded.",
      "The material shows no other activity-start paths that forward launchedFromUid/launchedFromPackage, so the diff cannot show whether similar forwarding exists elsewhere."
     ],
     "behavior_changes": [
      "When an activity calls startNextMatchingActivity() and its app ID differs from its launcher's and it is not SYSTEM_UID, the next activity is started with the intermediary's UID and package as the calling identity, not the original launcher's.",
      "In that case, permission and other caller checks during the start are evaluated against the intermediary's UID. Starts that previously succeeded because of the original caller's identity may now fail.",
      "No feature id is propagated in the squashed case (setCallingFeatureId is not called).",
      "Intermediaries with the same app ID and SYSTEM_UID intermediaries (the commit message cites ChooserActivity) keep the previous behaviour.",
      "Public API javadoc for Activity.startNextMatchingActivity() is updated. No signature change."
     ],
     "preconditions": [
      "An app's activity is started by another app through an intent that the intermediary's activity matches, so the intermediary ActivityRecord r carries that caller's launchedFromUid/launchedFromPackage.",
      "The intermediary calls Activity.startNextMatchingActivity(intent[, options]) from its own process (r.app with a live thread, valid activity token). This reaches IActivityTaskManager.startNextMatchingActivity in ActivityTaskManagerService.",
      "Another activity matching the intent must resolve after the current one. The new test models this as a queryIntentActivities result listing the current activity and then a target. The changed path matters only when the intermediary's app ID differs from the original caller's and is not SYSTEM_UID.",
      "The target activity relies on the caller identity supplied by the activity start (for example, permission enforcement against callingUid/callingPackage) for its security decision."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base bb7413247455 in android-security-14.0.0_r29, with the same logic and tests. The builder chain on 14 has no setUserId(userId) call, so the hunk ends at setActivityOptions(options). 8/8 patched lines present at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/base ba9c05f37043 in android-security-15.0.0_r17 (the commit analysed here). 8/8 patched lines present.",
      "16": "same fix: platform/frameworks/base e95170c6378e in android-security-16.0.0_r8, identical ATMS hunk. The test file differs only in surrounding imports. 8/8 patched lines present.",
      "17": "not affected (not listed by the bulletin). 8/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The ATMS change is self-contained: it needs only UserHandle, Process and ActivityStarter in scope and the existing r.getUid()/r.packageName/r.launchedFromUid fields. On trees without setUserId(userId) in the chain (as on 14), end the chain at setActivityOptions(options) as Google did. The tests also need the ActivityBuilder.setIntent() addition in WindowTestsBase.",
      "No 'Fix in fork' section was provided in the material, so no per-file fork verdict can be given."
     ],
     "confidence": "high",
     "unknowns": [
      "Which concrete privileged original caller and victim target were involved in the reported issue, beyond the REQUEST_INSTALL_PACKAGES example in the commit message.",
      "Whether any system components not running as SYSTEM_UID depended on identity forwarding through startNextMatchingActivity and are affected by the stricter rule.",
      "The content of the two internal cherry-picked commits (995f95376eb3, a24b74da128d) beyond what this AOSP commit shows.",
      "Why Android 17 is not listed as affected. The material only shows that the patched region is present at android-security-17.0.0_r1."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28650": {
     "cve": "CVE-2026-28650",
     "summary": "WindowState.setHiddenWhileSuspended now classifies the window by getBaseType() instead of mAttrs.type, so sub-windows whose top parent is a system alert window or toast are hidden when the owning app is suspended.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/WindowState.java)",
     "weakness": "Incorrect type check (sub-window type used instead of base window type), leading to a bypass of the suspended-app overlay hiding policy",
     "root_cause": "To decide whether a window must be hidden while its package is suspended, setHiddenWhileSuspended checked isSystemAlertWindowType(mAttrs.type) and mAttrs.type == TYPE_TOAST. For a sub-window (for example TYPE_APPLICATION_PANEL), isSystemAlertWindowType does not match mAttrs.type. The method therefore took the early return and never called hide()/show() for a sub-window whose top parent is a system alert window (such as TYPE_APPLICATION_OVERLAY) when the app was suspended (per the commit message). The sibling method setForceHideNonSystemOverlayWindowIfNeeded already used getBaseType() for the same decision (15.0.0_r17 context); setHiddenWhileSuspended did not.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowState.java:setHiddenWhileSuspended: computes final int baseType = getBaseType() and uses it in the early-return condition (!isSystemAlertWindowType(baseType) && baseType != TYPE_TOAST) in place of mAttrs.type. Per the commit message, the base type reflects the top parent window, so a sub-window attached to an overlay or toast no longer returns early. It goes on to the mHiddenWhileSuspended bookkeeping and the hide()/show() calls.",
      "services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java: adds testSubWindowHiddenWhenSuspended. The test creates a TYPE_APPLICATION_OVERLAY parent with a TYPE_APPLICATION_PANEL child and verifies that setHiddenWhileSuspended makes the child receive hide(true, true) and then show(true, true)."
     ],
     "completeness": [
      "Covers only setHiddenWhileSuspended. The mOwnerCanAddInternalSystemWindow early return is unchanged, so windows with that flag set are still exempt as before.",
      "The sibling path setForceHideNonSystemOverlayWindowIfNeeded already uses getBaseType() (15.0.0_r17 context, lines 3140-3161). After this change both hide-policy methods classify windows by base type.",
      "Per index search, the caller RootWindowContainer.updateHiddenWhileSuspendedState calls setHiddenWhileSuspended through forAllWindows for windows whose getOwningPackage() is in the suspended set. No caller-side change was made.",
      "The material does not show whether a sub-window of a hidden parent was actually displayed on screen before the fix. The fix makes the child explicitly hidden either way."
     ],
     "behavior_changes": [
      "Sub-windows whose top parent is a system alert window type or TYPE_TOAST, and whose mOwnerCanAddInternalSystemWindow is false, are now hidden with hide(true, true) when their package is suspended and shown again with show(true, true) when it is unsuspended. mHiddenWhileSuspended is now tracked on those sub-windows.",
      "No API, error code, or permission change. The only change is to the visibility policy for overlay sub-windows of suspended apps."
     ],
     "preconditions": [
      "A package is suspended, so RootWindowContainer.updateHiddenWhileSuspendedState calls setHiddenWhileSuspended(true) on windows owned by that package (per index search).",
      "The suspended app owns a window whose top parent is a system alert window type (for example TYPE_APPLICATION_OVERLAY) or TYPE_TOAST, with a sub-window (for example TYPE_APPLICATION_PANEL) attached to it.",
      "mOwnerCanAddInternalSystemWindow is false for the window."
     ],
     "per_major": {
      "14": "same fix: commit ce479a9a57bd in android-security-14.0.0_r29, identical WindowState.java hunk; the test uses the createWindow(parent, TYPE_APPLICATION_PANEL, ...) helper.",
      "15": "same fix: commit 8130b6057817 in android-security-15.0.0_r17, identical WindowState.java hunk; the test uses the createWindow helper. The code context at the tag shows the patched method in place.",
      "16": "same fix: commit b61562e1abe6 in android-security-16.0.0_r8, identical WindowState.java hunk; the test is adapted to newWindowBuilder(...).setParent(parent).build().",
      "17": "same fix: commit 236258a3e304 in android-security-17.0.0_r1, identical WindowState.java hunk; the test is adapted to newWindowBuilder and placed before the FLAG_APP_LOCK_CORE test."
     },
     "backport_notes": [
      "A single two-line production change. Google applied the identical getBaseType() hunk on 14, 15, 16 and 17. The 15.0.0_r17 context shows getBaseType() already used by the adjacent setForceHideNonSystemOverlayWindowIfNeeded. Only the test helper differs: createWindow(parent, type, name) on 14/15 versus newWindowBuilder(name, type).setParent(parent).build() on 16/17."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the implementation of getBaseType() or isSystemAlertWindowType(), only their use.",
      "The material does not describe the security impact beyond the bulletin's High/EoP rating.",
      "The material does not show whether hiding the parent window already affected the child's on-screen visibility before the fix.",
      "The resolver reports 6/7 patched-region lines present on 14, 15 and 16 versus 7/7 on 17. The material does not explain this, although the fix commits are present in each tag."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28655": {
     "cve": "CVE-2026-28655",
     "summary": "RemoteViews stops enabling Parcel.allowSquashing() during writeToParcel and drops the ReadWriteHelper trick that forced eager Bundle unparceling in ReflectionAction; on Android 15 this is paired with a re-landed change that serializes a single ApplicationInfoCache list at the root and per-node int ids instead of a full ApplicationInfo per nested RemoteViews.",
     "component": "platform/frameworks/base, core/java/android/widget/RemoteViews.java (RemoteViews parceling, ApplicationInfoCache, ReflectionAction)",
     "weakness": "Parcel serialization/deserialization inconsistency: RemoteViews.writeToParcel enabled Parcel squashing for the whole hierarchy, which alters how nested Bundle contents are written, and the reader compensated with a custom ReadWriteHelper to force immediate Bundle unparcel. The exact flaw class (what mismatch results) is not stated in the material.",
     "root_cause": "RemoteViews.writeToParcel wrapped the entire write in dest.allowSquashing()/dest.restoreAllowSquashing(prev); per the fix commit message squashing was added for application info. Per the same message, squashing \"doesn't work\" for this purpose (an ApplicationInfoCache had been added as well) and was therefore redundant, but per the removed code comment it still changed how Bundle contents inside ReflectionAction (BUNDLE type) were written. To cope, ReflectionAction's parcel constructor set a private ALTERNATIVE_DEFAULT Parcel.ReadWriteHelper around in.readBundle() specifically to defeat lazy Bundle initialization (TODO b/212731590). This squashing state plus the ReadWriteHelper workaround is the mechanism the fix removes; the precise way it is misused is not shown.",
     "fix_analysis": [
      "Android 15, commit 1 (04faeb30ca94, Revert^2, applied first; commit 2's base index 0b23c63ad1ae is this commit's result): RemoteViews.ApplicationInfoCache is rewritten from Map<Pair<String,Integer>,ApplicationInfo> to a List<ApplicationInfo> with getOrPut(ApplicationInfo)->int, findId(ApplicationInfo)->int (matches on packageName and uid), updateAndGetId(ApplicationInfo)->int, get(int id) (returns null if id<0 or id>=size), a Parcel constructor using parcel.createTypedArrayList(ApplicationInfo.CREATOR), and writeToParcel(dest, flags) using writeTypedList. A new field mApplicationId (default -1) is added to RemoteViews.",
      "Android 15, commit 1, RemoteViews.writeToParcel(Parcel,int,SparseArray<Intent>): in all three modes (MODE_NORMAL, MODE_HAS_SIZED_REMOTEVIEWS, landscape/portrait) the root now writes mApplicationInfoCache.writeToParcel(dest, flags) after the bitmap cache and before the collection cache; in MODE_NORMAL dest.writeTypedObject(mApplication, flags) is replaced by dest.writeInt(mApplicationId).",
      "Android 15, commit 1, RemoteViews(Parcel, HierarchyRootData, ApplicationInfo, int) constructor: for the root, mApplicationInfoCache = new ApplicationInfoCache(parcel) is read after mClassCookies and before mCollectionCache; in MODE_NORMAL mApplicationId = parcel.readInt() and mApplication = mApplicationInfoCache.get(mApplicationId) replace parcel.readTypedObject(ApplicationInfo.CREATOR). Other constructors (ApplicationInfo/layoutId, landscape/portrait, sized, copy) and the sized/landscape parcel branches set mApplicationId via updateAndGetId(mApplication). configureDescendantsAsChildren and the root-reset method (which recreates BitmapCache and ApplicationInfoCache when mIsRoot) use getOrPut then get(id). updateAppInfo uses get(findId(info)) and updateAndGetId(info). getPackageUserKey is removed.",
      "Commit 2 (cddac11f862e on 15; ae484e3d7a20 on 14; 6fe346abe6fb on 16; b78e05338801 on 17), RemoteViews.writeToParcel: removes boolean prevSquashingAllowed = dest.allowSquashing() at the start and dest.restoreAllowSquashing(prevSquashingAllowed) at the end, so RemoteViews no longer enables Parcel squashing for anything it writes.",
      "Commit 2, RemoteViews.ReflectionAction(Parcel) BUNDLE case: removes the hasReadWriteHelper()/setReadWriteHelper(ALTERNATIVE_DEFAULT)/setReadWriteHelper(null) sequence and reads the Bundle with a plain in.readBundle(); the static ALTERNATIVE_DEFAULT Parcel.ReadWriteHelper field is deleted."
     ],
     "completeness": [
      "Squashing removal is at the top and bottom of RemoteViews.writeToParcel, so all three serialization modes (normal, sized, landscape/portrait) and all nested writes within that call are covered; nested RemoteViews written via mLandscape.writeToParcel / sized views go through the same method.",
      "The ReadWriteHelper workaround existed only in the ReflectionAction BUNDLE case; the diff touches no other Bundle read sites in RemoteViews, and none are shown that needed it.",
      "The fix does not change Parcel.allowSquashing or Bundle itself; any other framework user of allowSquashing is outside this change (none shown in the material).",
      "On Android 15 the ApplicationInfoCache parcel change bounds-checks ids in get(int) (null for out-of-range) and uses typed list reading; no explicit cap on the list size is added. Whether commit 1 was also applied on 14/16/17 is not shown; only the squashing-removal commit is shown for those branches."
     ],
     "behavior_changes": [
      "RemoteViews parcels are written without Parcel squashing, so repeated ApplicationInfo objects are no longer squashed by the Parcel; the effect on parcel size is not shown.",
      "Bundles in ReflectionAction are read with default in.readBundle() semantics (no forced eager unparcel; the commit message says early unparceling was removed).",
      "Android 15 (commit 1): RemoteViews wire format changes; the root writes a typed list of ApplicationInfo once and each node writes an int id instead of a full ApplicationInfo. A node whose id is out of range of the cache gets mApplication == null."
     ],
     "preconditions": [
      "A RemoteViews instance is parceled by one process and unparceled by another, e.g. app widget content received via RemoteViewsAdapter.updateRemoteViews (factory.getViewAt) or delivered to LauncherAppWidgetHostView.updateAppWidget, per index search.",
      "For the ReflectionAction BUNDLE path, the RemoteViews contains an action carrying a Bundle value.",
      "The exact trigger conditions and which privileged receiver is affected are not stated in the material; the bulletin rates it High EoP."
     ],
     "per_major": {
      "14": "adapted fix: only the squashing-removal commit (ae484e3d7a20, android-security-14.0.0_r29) is shown; same logic adapted to the older code (public writeToParcel(Parcel dest, int flags) without intentsToIgnore, field named this.value, no mHasDrawInstructions). Presence of the ApplicationInfoCache parcel change on 14 is not shown.",
      "15": "two commits: 04faeb30ca94 (Revert^2 of ApplicationInfoCache-in-parcel change) followed by cddac11f862e (remove allowSquashing and the Bundle ReadWriteHelper workaround), both in android-security-15.0.0_r17.",
      "16": "same fix as the 15 squashing-removal commit (6fe346abe6fb, android-security-16.0.0_r8), same code shape (mValue, writeToParcel with intentsToIgnore, mHasDrawInstructions); the ApplicationInfoCache commit is not shown for 16.",
      "17": "same fix (b78e05338801, android-security-17.0.0_r1) with minor context difference: dest.writeFloat(mOriginalDensity) precedes the removed restoreAllowSquashing; the ApplicationInfoCache commit is not shown for 17."
     },
     "backport_notes": [
      "On Android 15 apply 04faeb30ca94 before cddac11f862e (the second diff's base blob 0b23c63ad1ae is the output of the first).",
      "For Android 14-era code adjust the ReflectionAction field name (value vs mValue) and the writeToParcel signature (no intentsToIgnore parameter); the removed lines are otherwise identical."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete failure mode produced by squashing-enabled writes combined with forced eager Bundle unparceling (what state becomes inconsistent and in which receiving process) is not described in the material.",
      "Whether the ApplicationInfoCache-in-parcel change (commit 1) was applied on Android 14, 16 and 17 security branches; only the squashing-removal commit is shown for them, and the per-major line counts differ (6/8 on 15 vs 4/8 elsewhere).",
      "Whether the squashing-removal commit depends on the ApplicationInfoCache change being present (the commit message cites the cache as the reason squashing is redundant).",
      "Whether the Android 15 wire-format change raises any compatibility concern for persisted or cross-version RemoteViews parcels.",
      "Whether other framework components still enable Parcel.allowSquashing on data read by privileged processes."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28656": {
     "cve": "CVE-2026-28656",
     "summary": "Replaces the AppOpsManager user-restriction toggling (OP_SYSTEM_ALERT_WINDOW / OP_TOAST_WINDOW on show/hide and resume/pause) used by the autofill Fill/Save/Dialog UIs and by the DeviceAdminAdd screens of Settings, Car Settings and TvSettings with the per-window Window#setHideOverlayWindows(true) / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS mechanism, to prevent non-system overlay windows from being shown on top of these sensitive windows.",
     "component": "platform/frameworks/base (services/autofill UI: AutoFillUI, FillUi, SaveUi, DialogFillUi, OverlayControl); platform/packages/apps/Settings, platform/packages/apps/Car/Settings, platform/packages/apps/TvSettings (DeviceAdminAdd screens)",
     "weakness": "Improper restriction of rendered UI layers (ineffective overlay/tapjacking protection on sensitive system UI)",
     "root_cause": "The autofill UI (through the OverlayControl helper, which calls AppOpsManager.setUserRestrictionForUser for USER_ALL) and the DeviceAdminAdd activities (AppOpsManager.setUserRestriction in onResume/onPause) tried to keep overlays off the screen by toggling a global AppOps user restriction on OP_SYSTEM_ALERT_WINDOW and OP_TOAST_WINDOW while the sensitive UI was shown, and releasing it in hide()/destroy()/onPause(). The commit message says this did not stop overlay windows from showing again without SYSTEM_ALERT_WINDOW permission once the restrictions were released. The protection depended on lifecycle bookkeeping (show/hide pairs, try/finally) rather than on the window itself.",
     "fix_analysis": [
      "frameworks/base, FillUi.java: AnchoredWindow.show() now ORs SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS into params.privateFlags before the if (!mShowing) branch, so both branches get it. The OverlayControl parameter is removed from the FillUi and AnchoredWindow constructors, and the hideOverlays() call after addView and the finally { showOverlays() } in the AnchoredWindow hide path (the one handling destroyCallbackOnError) are deleted.",
      "frameworks/base, DialogFillUi.java: the constructor calls mDialog.getWindow().setHideOverlayWindows(true) right after setContentView. The OverlayControl field and parameter are removed. show() no longer calls hideOverlays(), and hide() and destroy() lose their try/finally showOverlays().",
      "frameworks/base, SaveUi.java: in the constructor's window setup, window.setHideOverlayWindows(true) is added after setCloseOnTouchOutside(true). OverlayControl is removed from the constructor, show(), hide() and destroy().",
      "frameworks/base, AutoFillUI.java: the mOverlayControl field and its construction are removed, and FillUi/SaveUi/DialogFillUi are constructed without it. OverlayControl.java is deleted.",
      "packages/apps/Settings, DeviceAdminAdd.java: onCreate() calls getWindow().setHideOverlayWindows(true) in place of the mAppOps lookup. The setUserRestriction(OP_SYSTEM_ALERT_WINDOW/OP_TOAST_WINDOW) calls in onResume() and onPause() are removed, along with the AppOpsManager import and the mAppOps field.",
      "packages/apps/Car/Settings, DeviceAdminAddActivity.java: a new onCreate() override calls getWindow().setHideOverlayWindows(true). DeviceAdminAddHeaderPreferenceController.java drops mAppOps/mToken and the setUserRestriction calls in onResumeInternal()/onPauseInternal(); preference.setEnabled(false) on pause is kept.",
      "packages/apps/TvSettings, DeviceAdminAdd.java: same change as in Settings. onCreate() calls getWindow().setHideOverlayWindows(true), the AppOps restriction calls are removed from onResume()/onPause(), and mToken/mAppOps and the related imports are removed."
     ],
     "completeness": [
      "Covers all three autofill presentation surfaces (anchored FillUi window, SaveUi dialog, DialogFillUi bottom sheet) and the DeviceAdminAdd screens in Settings, Car Settings and TvSettings. OverlayControl is deleted, so no autofill path still uses the old mechanism.",
      "In FillUi.AnchoredWindow.show() the flag is applied to params on every call, before the mShowing branch.",
      "In the activities the flag is set on the activity window in onCreate().",
      "In Settings DeviceAdminAdd, the Binder/IBinder imports are still present and the diff does not remove the mToken declaration.",
      "The per-major facts show only the frameworks/base commit for Android 14 and only the Settings commit for Android 16. The material does not show whether the Car Settings and TvSettings parts were applied on those branches."
     ],
     "behavior_changes": [
      "Overlay suppression is now tied to each flagged window (setHideOverlayWindows / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS). It no longer works by toggling a global AppOps user restriction (for autofill, set for USER_ALL).",
      "These components no longer set or clear any AppOps user restriction on OP_SYSTEM_ALERT_WINDOW/OP_TOAST_WINDOW.",
      "The autofill hide()/destroy() paths no longer have try/finally wrappers that call showOverlays()."
     ],
     "preconditions": [
      "system_server shows the autofill fill UI, save UI or fill dialog (AutoFillUI.showFillUi/showSaveUi/showFillDialog; per index search, showFillDialog constructs DialogFillUi), or the DeviceAdminAdd activity of Settings/Car Settings/TvSettings is in the foreground.",
      "An app on the device that can draw overlay or toast windows on top of that UI. The material does not show which window types are involved."
     ],
     "per_major": {
      "14": "Adapted fix: frameworks/base c441211a26c9 in android-security-14.0.0_r29 makes the same OverlayControl removal and setHideOverlayWindows/SYSTEM_FLAG additions, adjusted to the older FillUi constructor (no maxInputLengthForAutofill) and the older onShown() signature. The resolver reports 6/8 lines of the patched region present. The Settings, Car Settings and TvSettings commits for 14 are not in the material.",
      "15": "This fix: frameworks/base 90ce24ef3760, Settings cc183b2887ea, Car/Settings 084a3e353820 and TvSettings 7a21141ea7ef in android-security-15.0.0_r17. 8/8 lines of the patched region present.",
      "16": "Same fix: Settings f6b6ecb4b6e1 in android-security-16.0.0_r8 has the same blob hashes as the Android 15 Settings diff. 8/8 lines present. The frameworks/base, Car Settings and TvSettings commits for 16 are not shown in the material.",
      "17": "Not affected per the bulletin (Android 17 is not listed). At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The four commits change separate projects and the diffs show no ordering dependency between them. Each relies on Window#setHideOverlayWindows or the SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS private flag being available on the target branch.",
      "When adapting frameworks/base, remove every OverlayControl reference (the constructor parameters of FillUi, AnchoredWindow, SaveUi and DialogFillUi, and the AutoFillUI field), because the class is deleted. The Android 14 commit shows how the FillUi signature differs on older branches."
     ],
     "confidence": "high",
     "unknowns": [
      "Which overlay/window types could be shown over the protected UI despite the AppOps restriction, and exactly how the old restriction was bypassed (only the commit message describes it).",
      "Whether separate Dialog windows created by the DeviceAdminAdd activities are covered, since only the activity window is flagged.",
      "Whether the Car Settings and TvSettings commits were applied on Android 14 and 16, and whether the frameworks/base commit was applied on Android 16 (not in the material).",
      "The bulletin's affected list names 17-next, while the per-major facts treat Android 17 as not listed."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28658": {
     "cve": "CVE-2026-28658",
     "summary": "AccountsDb.findMetaAuthUid now derives the account type from a meta_key by stripping the fixed \"auth_uid_for_type:\" prefix with substring instead of splitting the key on \":\" and taking the second element, so account types that themselves contain \":\" are no longer truncated and mis-keyed.",
     "component": "platform/frameworks/base, AccountManagerService / AccountsDb (services/core/java/com/android/server/accounts)",
     "weakness": "Improper parsing of a delimited key (incorrect string tokenization leading to identity confusion between account types)",
     "root_cause": "Meta table rows for authenticator uids are stored with the key \"auth_uid_for_type:<accountType>\". findMetaAuthUid rebuilt the type with TextUtils.split(key, \":\")[1], which only works if the account type contains no \":\" character. For an account type containing \":\", the split yields more than two tokens and element [1] is only the part of the type before its first \":\". The uid stored for that row is therefore put into the returned Map<String, Integer> under a different, shorter type string than the one actually stored.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountsDb.java: the META_KEY_DELIMITER constant (\":\") is removed and replaced by META_KEY_PREFIX_LEN = META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX.length().",
      "AccountsDb.java:findMetaAuthUid: the loop body now reads the key into typeWithPrefix and computes type = typeWithPrefix.substring(META_KEY_PREFIX_LEN). It removes exactly the known prefix and keeps the full remainder, including any ':' characters, as the account type. The TextUtils.isEmpty(type) / isEmpty(uidStr) guard that follows is unchanged.",
      "No explicit startsWith check is added; the fix assumes every key reaching this loop carries the prefix."
     ],
     "completeness": [
      "Covers the only use of META_KEY_DELIMITER. Because the constant is deleted, nothing else referenced it. The material does not show whether any other code parses this key some other way.",
      "No explicit check that the key starts with the prefix is added. A key shorter than the prefix would make substring throw, and the material does not address that case (the old code could also throw ArrayIndexOutOfBounds on a key with no ':').",
      "The write side (how the meta key is built from the account type on insert) is not part of the diff; the fix only changes the read/parse side."
     ],
     "behavior_changes": [
      "Account types containing ':' are now returned intact as map keys from findMetaAuthUid, so a uid previously attributed to a truncated type is now attributed to the full stored type.",
      "For types without ':' the result is identical to before; no new error paths or return values."
     ],
     "preconditions": [
      "A row in the accounts meta table whose key is \"auth_uid_for_type:\" followed by an account type string that contains a ':' character.",
      "findMetaAuthUid being invoked (its callers are not shown in the material)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 051431afaec5 in android-security-14.0.0_r29, identical diff and line numbers to the 15 commit.",
      "15": "same fix: platform/frameworks/base 38dbed092b9d in android-security-15.0.0_r17, cherry-pick of 6a2d8b6bc382 (Change-Id I3c028c0cfdbdc44a09fffee3542ba8bc782c1435).",
      "16": "same fix: platform/frameworks/base 8c2ce8c567a2 in android-security-16.0.0_r8, identical diff and line numbers.",
      "17": "same fix: platform/frameworks/base 31e7981c9d23 in android-security-17.0.0_r1, identical content, only hunk offsets differ (old lines 173 and 1094)."
     },
     "backport_notes": [
      "Single self-contained commit touching one file. The two hunks should apply cleanly to any branch that still has META_KEY_DELIMITER and the TextUtils.split call in findMetaAuthUid. If a fork removed or renamed META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX, compute the prefix length from whatever constant is used to write the meta key."
     ],
     "confidence": "high",
     "unknowns": [
      "Who calls findMetaAuthUid and how the returned type-to-uid map is used, and therefore the exact privilege consequence of a mis-keyed entry (not in the material).",
      "Which selection query findMetaAuthUid uses, and whether it guarantees that every returned key carries the prefix (not in the material).",
      "Whether the insert path rejects account types containing ':' (not in the material).",
      "What the resolver's '6/8 lines of the patched region present' means at each tag, given that each tag contains the fix commit."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28660": {
     "cve": "CVE-2026-28660",
     "summary": "Adds a PackageInstallerService.getAllSessions(userId, filterCallingUid) overload and makes LauncherAppsService.getAllSessions pass the real caller UID into it. SessionInfo objects returned through LauncherApps.getAllPackageInstallerSessions() are therefore generated and filtered for the actual caller instead of for system_server.",
     "component": "platform/frameworks/base, package manager / PackageInstaller and LauncherApps system services",
     "weakness": "Information disclosure via identity confusion: an internal call made under Binder.clearCallingIdentity() used Binder.getCallingUid() (the system UID) for per-caller SessionInfo scrubbing and filtering, so sensitive fields were not scrubbed for the real caller.",
     "root_cause": "LauncherAppsService.LauncherAppsImpl.getAllSessions(String callingPackage) captures the caller's UID, then calls Binder.clearCallingIdentity() and invokes PackageInstallerService.getAllSessions(int userId) for each profile. Because identity was cleared, callingUid = Binder.getCallingUid() inside that method is system_server's UID. That UID went to session.generateInfoForCaller(false, callingUid) and to shouldFilterSession(snapshot, callingUid, info). The commit message says originatingUri and referrerUri were exposed, and the new javadoc describes the UID as 'used for SessionInfo scrubbing and filtering'. Together these indicate that generateInfoForCaller scrubs based on the UID and left the URIs intact for the system UID. Afterwards LauncherAppsService applied only its own shouldFilterSession(callingUid, info) (installer-UID match or canQueryPackage). That check removes whole sessions but does not scrub fields of the sessions it keeps. The result: an app calling LauncherApps.getAllPackageInstallerSessions() with package visibility to the session owner received unscrubbed originatingUri/referrerUri.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java:getAllSessions(int userId): the Binder-facing method now reads Binder.getCallingUid() and delegates to a new package-private overload, getAllSessions(int userId, int filterCallingUid). Behavior for direct IPackageInstaller callers is unchanged.",
      "PackageInstallerService.java:getAllSessions(int, int): still calls snapshot.enforceCrossUserPermission(callingUid, ...) with Binder.getCallingUid(). When invoked from LauncherAppsService under cleared identity, that UID is system_server. It uses filterCallingUid for session.generateInfoForCaller(false, filterCallingUid) and for result.removeIf(info -> shouldFilterSession(snapshot, filterCallingUid, info)).",
      "services/core/java/com/android/server/pm/LauncherAppsService.java:LauncherAppsImpl.getAllSessions(String): inside the clearCallingIdentity block, the call changes from getAllSessions(userId) to getAllSessions(userId, callingUid). callingUid was captured before identity was cleared. The later LauncherAppsService.shouldFilterSession pass stays in place.",
      "Android 17 only: services/tests/mockingservicestests/.../PackageInstallerServiceTest.java adds testGetAllSessions_filtersSessionsCorrectly. The test exercises getAllSessions(int) filtering via canQueryPackage. It does not directly test the URI scrubbing or the two-argument overload."
     ],
     "completeness": [
      "Covers the identity-cleared internal path shown: LauncherAppsService.getAllSessions -> PackageInstallerService.getAllSessions. The index search (which may be incomplete) returned only LauncherAppsService as a caller, reached from LauncherApps.getAllPackageInstallerSessions(), and found no users outside frameworks/base.",
      "The direct Binder entry points getAllSessions(int), getSessionInfo(int) and getStagedSessions() are unchanged and keep using Binder.getCallingUid(), which is the right UID when they are called directly over Binder.",
      "The fix does not change what generateInfoForCaller scrubs. It only makes sure the right UID reaches it. The material does not show whether any other internal caller invokes these PackageInstallerService methods under clearCallingIdentity.",
      "The cross-user permission check in the new overload is still enforced against the Binder UID (system_server on the LauncherApps path), not filterCallingUid. LauncherAppsService already limits userIds to the caller's profiles beforehand, so this behavior is unchanged rather than a gap introduced by the fix.",
      "The Android 15 and 16 cherry-picks include no regression test. Only Android 17 carries one, and it covers filtering, not URI scrubbing."
     ],
     "behavior_changes": [
      "LauncherApps.getAllPackageInstallerSessions() now returns SessionInfo objects generated for the calling app's UID, so originatingUri/referrerUri are scrubbed where they previously were not.",
      "On the LauncherApps path, PackageInstallerService.shouldFilterSession is now evaluated against the app caller's UID instead of the system UID, in addition to the existing LauncherAppsService.shouldFilterSession pass.",
      "No new exceptions, error codes or permission requirements appear in the diff. Direct IPackageInstaller.getAllSessions(int) callers see no change."
     ],
     "preconditions": [
      "The caller is an app that can invoke LauncherApps.getAllPackageInstallerSessions(). Per the code context and index search, LauncherAppsService.getAllSessions(String) only verifies the calling package; hidden-profile access permissions only decide whether hidden profiles are included.",
      "An install session exists in one of the caller's profiles and carries an originatingUri and/or referrerUri.",
      "The caller passes LauncherAppsService.shouldFilterSession for that session: it is the session's installer UID, or mPackageManagerInternal.canQueryPackage(uid, session.getAppPackageName()) returns true."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14, and the material contains no fix commit for it. The resolver reports 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Same fix: platform/frameworks/base 498791aa27ab in android-security-15.0.0_r17 (cherry-pick of bbeee4f37c25). Two files changed, no test added.",
      "16": "Same fix: platform/frameworks/base 3d614f9e98a3 in android-security-16.0.0_r8. The code change is identical, at shifted line offsets, with no test added.",
      "17": "Same fix plus a test: platform/frameworks/base 6acdd7f1a72d in android-security-17.0.0_r1. The code change is identical (the file already had a blank line before the return), plus a new PackageInstallerServiceTest.testGetAllSessions_filtersSessionsCorrectly."
     },
     "backport_notes": [
      "Single self-contained commit. When backporting, keep the two-argument overload package-private, and pass the UID that LauncherAppsService captured before Binder.clearCallingIdentity(). If a fork has added other internal callers of PackageInstallerService session-info methods under cleared identity, apply the same filterCallingUid pattern there."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of PackageInstallerSession.generateInfoForCaller is not in the material. The claim that it scrubs originatingUri/referrerUri based on the caller UID rests only on the commit message and the new javadoc.",
      "The material does not show which UIDs or permissions generateInfoForCaller treats as privileged, that is, which callers still receive unscrubbed URIs.",
      "The body of PackageInstallerService.shouldFilterSession is not shown, so whether the set of listed sessions changes on the LauncherApps path is unknown.",
      "The material does not explain why Android 14 is not listed as affected even though the resolver finds the patched region lines present at android-security-14.0.0_r29.",
      "The material does not show whether other system-service callers invoke PackageInstallerService session-info methods under clearCallingIdentity. The index search found only LauncherAppsService."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28664": {
     "cve": "CVE-2026-28664",
     "summary": "RuntimeImage::WriteImageToDisk in ART now calls fchmod() to set the runtime-generated app image (.art) file to 0444 right after creating it, and fails image generation if that cannot be done, so the file is no longer left writable on disk.",
     "component": "platform/art, runtime app image generation (runtime/runtime_image.cc)",
     "weakness": "Insecure file permissions on a runtime-generated artifact (image file left writable)",
     "root_cause": "RuntimeImage::WriteImageToDisk created the runtime app image file (image_file) and wrote it out without restricting its permissions, so the .art file stayed writable after generation. The commit message says making it read-only \"helps prevent tampering\". The bulletin rates this as a High severity EoP.",
     "fix_analysis": [
      "runtime/runtime_image.cc: adds #include <sys/stat.h> for fchmod and the S_IR* constants.",
      "runtime/runtime_image.cc:RuntimeImage::WriteImageToDisk: right after an existing early-return error check, described in the new comment as 'immediately after creation', it calls fchmod(image_file->Fd(), S_IRUSR | S_IRGRP | S_IROTH), so the on-disk file is mode 0444 before image->FillData and the data write. The comment notes that writes through the already-open descriptor still succeed.",
      "If fchmod fails, error_msg is set to \"Failed to make runtime image read-only: <strerror(errno)>\" and the function returns false.",
      "The commit carries two Cherrypick-From lines. The diff applies the permission change at creation time, not after the move to the final path as the first paragraph of the commit message describes."
     ],
     "completeness": [
      "Covers only the file created in RuntimeImage::WriteImageToDisk. The mode is set once, on the fd at creation. The move to the final path is not shown in the material, so the claim that the mode survives the move is an inference.",
      "Does not change the permissions or ownership of any containing directory. The diff touches only the file mode.",
      "Adds no content integrity check. An image that already exists from before the patch is not changed by this code.",
      "The commit message says a failed chmod leads to deleting the image, but the diff only returns false. The material does not show whether an existing cleanup path removes the partially created file."
     ],
     "behavior_changes": [
      "Runtime-generated app image files are now set to mode 0444 instead of their previous default mode.",
      "New failure mode: if fchmod fails, WriteImageToDisk returns false with error_msg \"Failed to make runtime image read-only: ...\". A partially created file may still remain on disk.",
      "Opening the generated .art file for writing by path after it is created may now fail because the file is read-only. Writes through the original descriptor are unaffected."
     ],
     "preconditions": [
      "Runtime app image generation must be active so that RuntimeImage::WriteImageToDisk runs and produces an .art file on disk.",
      "Exploiting the original flaw needed write access to the generated image file. The material does not identify which principals had that access."
     ],
     "per_major": {
      "14": "same fix: platform/art f8f793610644 in android-security-14.0.0_r29. Identical hunk, only the context differs (an extra #include <sstream> line, different line offsets). 6/6 patched lines present.",
      "15": "same fix: platform/art 19665de3fb4d in android-security-15.0.0_r17. 6/6 patched lines present.",
      "16": "same fix: platform/art a57f914df7ee in android-security-16.0.0_r8, identical hunk. 6/6 patched lines present.",
      "17": "same fix: platform/art 70be2043e6db in android-security-17.0.0_r1, identical hunk. 6/6 patched lines present."
     },
     "backport_notes": [
      "Single self-contained hunk: add <sys/stat.h> and insert the fchmod block right after the image_file creation failure check in WriteImageToDisk. Google applied the same hunk on 14, 15, 16 and 17. Context differs slightly on 14. ART is a mainline module (com.android.art), so the fix may also arrive through a module update rather than a platform patch (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which process or uid could write to the generated image file, and how a tampered image would lead to privilege escalation (for example, whether and how the runtime later loads and trusts it), is not shown in the material.",
      "Whether surrounding code not included in the diff deletes the file when fchmod fails, as the commit message states.",
      "Where the file is moved to its final path, and whether its mode is preserved there.",
      "What the two Cherrypick-From commits contain, and whether they are separate changes."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28668": {
     "cve": "CVE-2026-28668",
     "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that a realloc rejected by the android allocation limit returns nullptr without freeing the caller's still-valid original block.",
     "component": "platform/bionic, libc malloc allocation-limit hooks (libc/bionic/malloc_limit.cpp)",
     "weakness": "Use-after-free / double free: realloc failure path freed the original pointer, violating realloc semantics (CWE-416/CWE-415)",
     "root_cause": "In libc/bionic/malloc_limit.cpp, LimitRealloc() (used when the android allocation limit code is enabled, which the tests do via android_mallopt M_SET_ALLOCATION_LIMIT_BYTES) checks whether a size increase would cross gAllocLimit. If it would, the old code logged a warning, called LimitFree(old_mem) and returned nullptr. Standard realloc semantics require the original block to stay valid and owned by the caller on failure, so callers keep using the old pointer and later free() it. Freeing it inside the limit check left the caller with a dangling pointer, which leads to use-after-free and to a double free when the caller releases it.",
     "fix_analysis": [
      "libc/bionic/malloc_limit.cpp:LimitRealloc: in the branch `bytes > old_usable_size && !CheckLimit(bytes - old_usable_size)`, the `LimitFree(old_mem);` call is deleted and replaced with a comment. The function now only logs the warning and returns nullptr, and old_mem stays allocated.",
      "tests/malloc_test.cpp:TEST(android_mallopt, set_allocation_limit_realloc_increase): the over-limit realloc result is stored in a separate `new_memory` variable (asserted nullptr), and the original `memory` is then free()d. Per the commit message, the test fails if the old behavior is restored."
     ],
     "completeness": [
      "Covers the only place in the shown code where LimitRealloc released the caller's block on a failure path.",
      "The second nullptr path in LimitRealloc (the underlying dispatch_table->realloc / Malloc(realloc) returns nullptr) never freed old_mem and is unchanged. It still subtracts old_usable_size from gAllocated, with the comment 'acts as if the pointer was freed'. That is an accounting choice that this fix leaves as is.",
      "The other limit wrappers visible in the context (LimitPosixMemalign, LimitAlignedAlloc) do not take an existing pointer, so this pattern does not apply to them.",
      "No change to LimitUsableSize, CheckLimit or IncrementLimit. The fix only removes the erroneous free."
     ],
     "behavior_changes": [
      "When the allocation limit is enabled and a growing realloc would exceed it, realloc now returns nullptr and the original allocation stays valid. Callers must free it themselves, as the standard realloc contract requires.",
      "The warning log message is unchanged."
     ],
     "preconditions": [
      "The process must have the android allocation limit enabled (for example android_mallopt(M_SET_ALLOCATION_LIMIT_BYTES, ...), as in the tests).",
      "A realloc() call must increase the usable size by more than the remaining headroom under gAllocLimit, so that CheckLimit() fails.",
      "The caller then uses or frees the original pointer, as realloc semantics permit. The material does not show which Android processes enable the limit."
     ],
     "per_major": {
      "14": "same fix: platform/bionic cd1be6e8bfb8 in android-security-14.0.0_r29. The malloc_limit.cpp hunk is identical (at line 199); the test hunk is at a different offset (1279).",
      "15": "same fix: platform/bionic 10a1e2ce6e59 in android-security-15.0.0_r17. malloc_limit.cpp hunk at line 200, test hunk at line 1289.",
      "16": "same fix: platform/bionic 50cbdffd6f7e in android-security-16.0.0_r8. malloc_limit.cpp has the same file blobs as Android 15 (5128a3526 -> e00b8c636); test hunk at line 1291.",
      "17": "same fix: platform/bionic 48a7e3362ff9 in android-security-17.0.0_r1. Same malloc_limit.cpp hunk at line 201; test hunk at line 818 with the same context and change."
     },
     "backport_notes": [
      "Single commit with a two-line functional change. The malloc_limit.cpp hunk text is the same on all four majors (only line offsets differ), and the test hunk differs only in offset. The material shows no dependencies."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which production processes enable M_SET_ALLOCATION_LIMIT_BYTES and so reach LimitRealloc.",
      "The concrete reporter scenario behind bug 501541998 is not shown.",
      "LimitFree's body is not shown, so the material does not show how the old path affected gAllocated accounting."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28671": {
     "cve": "CVE-2026-28671",
     "summary": "In the file-movement path, MediaProvider.update now prunes a stale MediaStore row (one whose _data matches the recomputed destination path but has no file on disk) before renaming, or requires write permission on that row if a file exists. If a UNIQUE(_data) conflict on the rename destination still occurs during the DB update, it deletes the conflicting row and retries in the same transaction. Another app's pre-inserted row can then no longer squat the name a pending file is being published to.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.update / updateAllowingReplace (file placement and pending-file publish)",
     "weakness": "Race condition / namespace squatting: before the rename and DB update, nothing checked for an existing database row at the destination path, so another app's pre-inserted row could block or claim the name.",
     "root_cause": "In MediaProvider.update, when placement columns (sPlacementColumns; the tests reach this path by clearing IS_PENDING with QUERY_ARG_ALLOW_MOVEMENT=true) change and movement is allowed, the provider recomputes afterPath, calls Os.rename(beforePath, afterPath), then writes the new values through updateAllowingReplace. Nothing checked whether the Files table already had a row with _data == afterPath. If such a row existed (the test models it as a second insert with the same DISPLAY_NAME/RELATIVE_PATH and IS_PENDING=0), qb.update would throw SQLiteConstraintException. The old handler in updateAllowingReplace rethrew this for callers targeting SDK >= R. For legacy callers it attempted a replace only when a file existed at the path. So the legitimate owner's update could fail while the squatter row kept the name.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:update: declares `String renameAfterPath = null` before the movement block. After afterPath is computed and before `Os.rename(beforePath, afterPath)`, it calls the new `checkIfPathAlreadyExists(helper, beforeVolume, afterPath)` and records `renameAfterPath = afterPath`. The later call becomes `updateAllowingReplace(qb, helper, values, userWhere, userWhereArgs, renameAfterPath)`.",
      "MediaProvider.java:checkIfPathAlreadyExists (new): clears the local calling identity, then runs `queryForSingleItem(MediaStore.Files.getContentUri(volume), {_ID}, DATA=?, {afterPath})`. If the query throws FileNotFoundException or returns null, it does nothing. If a row exists and `new File(afterPath).exists()` is false, it calls `deleteEntryForPath(helper, afterPath)`. If both the row and the file exist, it restores identity and then calls `enforceCallingPermission(MediaStore.Files.getContentUri(volume, id), Bundle.EMPTY, /* forWrite */ true)`.",
      "MediaProvider.java:deleteEntryForPath (new): clears the calling identity and, inside `helper.runWithTransaction`, runs `db.delete(Files.TABLE, \"_data=?\", {path})` and returns the deleted count. Any exception is logged and 0 is returned.",
      "MediaProvider.java:updateAllowingReplace: gains a `@Nullable String renameAfterPath` parameter. In the SQLiteConstraintException handler it first reads `path = values.getAsString(FileColumns.DATA)` and calls `handleNonExistingEntryFile(...)`, before the targetSdk >= R check. Any result other than -1 is returned directly. The former `if (path == null || !new File(path).exists()) throw e;` guard is removed; only the null check survives, inside the new helper.",
      "MediaProvider.java:handleNonExistingEntryFile (new): throws the original exception if path is null. If `Objects.equals(path, renameAfterPath)` and `deleteEntryForPath(helper, path) > 0`, it re-runs `qb.update(helper, values, userWhere, userWhereArgs)` inside the enclosing transaction and returns its count. Otherwise it returns -1 so the existing handling applies.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: adds testPendingUpdate_SquatterRowDeleted (a squatter row without a file is removed and the publish update returns 1) and testPendingUpdate_SquatterRowFileExists (a squatter row with a real file is kept and the publishing row gets a different display name)."
     ],
     "completeness": [
      "Covers only the movement path of MediaProvider.update. renameAfterPath is set only inside the placement/movement block, so a UNIQUE(_data) conflict from any other update (renameAfterPath == null) gets the previous treatment: throw for targetSdk >= R, legacy replace otherwise.",
      "handleNonExistingEntryFile matches only on path equality with renameAfterPath. It does not re-check whether the file exists itself.",
      "checkIfPathAlreadyExists queries only the Files URI of beforeVolume. The pre-rename check and Os.rename are not atomic. The commit message says the in-transaction retry is meant to prevent re-insertion.",
      "The insert path is not changed by this diff. Rows without a backing file are pruned only when they collide with a rename destination.",
      "The legacy (targetSdk < R) replace branch lost its `new File(path).exists()` precondition. The material does not say whether this was intended.",
      "deleteEntryForPath swallows exceptions and returns 0. In that case handleNonExistingEntryFile returns -1 and the old failure behavior applies."
     ],
     "behavior_changes": [
      "A database row whose _data equals the rename destination, with no file on disk, is now deleted with the calling identity cleared during another caller's update. Querying its content URI then returns 0 rows (per the test).",
      "If both a row and a real file exist at the destination path, the updating caller must now pass enforceCallingPermission(forWrite=true) on that row before the rename happens.",
      "Per the new test, the publish update in the squatter-without-file scenario succeeds and returns 1.",
      "Legacy (targetSdk < R) callers that hit a UNIQUE conflict outside the rename case no longer need the conflicting file to exist on disk before the replace logic runs.",
      "New log lines: 'Deleted stale entry' (verbose) and 'Failure in deleting stale entry' (error)."
     ],
     "preconditions": [
      "The caller invokes MediaProvider.update on a single-item URI in a well-defined collection (AUDIO_MEDIA_ID, AUDIO_PLAYLISTS_ID, VIDEO_MEDIA_ID, IMAGES_MEDIA_ID, DOWNLOADS_ID, FILES_ID). The values must touch sPlacementColumns, omit an explicit DATA column, not target a thumbnail, and movement must be allowed (QUERY_ARG_ALLOW_MOVEMENT defaults to !isCallingPackageSelf()).",
      "Another row already exists in the Files table with _data equal to the recomputed destination path. The tests create it by inserting with the same DISPLAY_NAME and RELATIVE_PATH and IS_PENDING=0.",
      "Per index search, DownloadProvider.updateMediaProvider also issues updates to MediaProvider rows. The material does not show whether those reach the movement path."
     ],
     "per_major": {
      "14": "same fix: commit e27ea39d7184 in android-security-14.0.0_r29 (RESTRICT AUTOMERGE). Hunks are identical, at different line offsets. Patched region: 8/8 lines present.",
      "15": "same fix: commit 9e42f05c7c32 in android-security-15.0.0_r17 (RESTRICT AUTOMERGE). Patched region: 8/8 lines present.",
      "16": "same fix: commit 94374dc41151 in android-security-16.0.0_r8 (RESTRICT AUTOMERGE). Hunks are identical, at higher line offsets. Patched region: 8/8 lines present.",
      "17": "same fix, trivially adapted: commit 87beb3978b6a in android-security-17.0.0_r1 (RESTRICT AUTOMERGE). The surrounding code uses Logging.logIfLoggable and markPathAsDeletedAndInvalidateFuseDentry, but the added logic is identical. Patched region: 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit with the same title on each branch. The Android 15 commit carries Change-Id/Merged-In Ica7cec90bb2d1870ce8902fa3d3e9899d59a743b. The diff adds no imports, so Objects, BaseColumns, FileNotFoundException, queryForSingleItem, enforceCallingPermission and clear/restoreLocalCallingIdentity must already be available on the target. Also confirm that DatabaseHelper.runWithTransaction tolerates the nested call that deleteEntryForPath makes.",
      "MediaProvider is also shipped as a mainline module, so devices may receive this change through a module update independent of the platform security tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "How afterPath is made unique against files on disk before checkIfPathAlreadyExists runs; the file-exists behavior is inferred from test expectations only.",
      "The bulletin classifies this as ID (information disclosure). The material does not show what information is exposed.",
      "The state of the file and the database after the pre-fix failure.",
      "Whether dropping the File.exists() precondition for legacy-caller replace was intentional.",
      "Whether a corresponding fix shipped in the MediaProvider mainline module train.",
      "The Change-Ids of the 14, 16 and 17 commits."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45517": {
     "cve": "CVE-2026-45517",
     "summary": "Adds structural checks (scheme, authority, picker path pattern) and a current-user-id check to caller-supplied preselection URIs in PickerUriResolver.processUrisForSelection, ahead of the per-URI permission check, so malformed, foreign-authority or cross-user URIs are dropped before their ids reach the picker query.",
     "component": "platform/packages/providers/MediaProvider, photopicker preselection (PickerUriResolver, and MediaQueryForPreSelection on 16/17)",
     "weakness": "Improper input validation of caller-controlled URIs (no scheme/authority/path checks and no user-id consistency check before use in a database query)",
     "root_cause": "PickerUriResolver.processUrisForSelection takes the URI strings an app passes for preselection (EXTRA_PICKER_PRE_SELECTION_URIS, delivered as QUERY_ID_SELECTION per the tests) and runs Uri.parse on each. Before the fix, its only check was checkUriPermission(uri, -1, callingUid), after which the URI went into accessibleUris and its id was placed in QUERY_LOCAL_ID_SELECTION / QUERY_CLOUD_ID_SELECTION. Nothing checked that the URI was a content:// URI with MediaStore.AUTHORITY and a valid picker path shape. Nothing checked that the user id segment (content://media/picker/<user-id>/...) matched the current user profile. According to the commit message, the user id check prevents data mismatch during database queries. The bulletin rates the issue High, type information disclosure.",
     "fix_analysis": [
      "src/com/android/providers/media/PickerUriResolver.java: adds a static LocalUriMatcher sStaticUriMatcher = new LocalUriMatcher(MediaStore.AUTHORITY) and a new public static isValidPickerUri(Uri). It returns false for null and for any scheme other than ContentResolver.SCHEME_CONTENT. Otherwise it returns true only if sStaticUriMatcher.matchUri(uri, /* allowHidden */ false) returns PICKER_ID or PICKER_GET_CONTENT_ID (16/17 also accept PICKER_TRANSCODED_ID). Binding the matcher to MediaStore.AUTHORITY is what enforces the authority check.",
      "PickerUriResolver.processUrisForSelection: inside the per-URI loop and before checkUriPermission, skips (continue, with Log.d) any URI that fails isValidPickerUri. It then skips any URI where getUserId(uriForSelection) != UserHandle.myUserId(). Only URIs that pass both checks are permission-checked and added to accessibleUris.",
      "Android 16/17 only: getUserId(Uri) becomes public, and the same two checks are added to src/com/android/providers/media/photopicker/v2/model/MediaQueryForPreSelection.java, ahead of PickerUriResolver.checkUriPermission(appContext, uri, -1, callingPackageUid).",
      "Tests: in PickerUriResolverTest, testProcessUrisForSelection_permissionChecks and a cloud-authority test now build picker URIs with UserHandle.myUserId() instead of TEST_USER. 16/17 also add PickerDataLayerV2Test cases for cross-user, malformed, fake-authority and invalid-segment URIs."
     ],
     "completeness": [
      "Covers the Android 15 processUrisForSelection path: scheme, authority (via LocalUriMatcher bound to MediaStore.AUTHORITY), path pattern (PICKER_ID / PICKER_GET_CONTENT_ID) and user id equality with UserHandle.myUserId().",
      "The Android 15 commit only touches PickerUriResolver.java and its test. The 16/17 commits also patch photopicker/v2/model/MediaQueryForPreSelection.java and accept PICKER_TRANSCODED_ID. The material does not show whether a v2 MediaQueryForPreSelection path exists on the Android 15 branch.",
      "The user id check compares against UserHandle.myUserId(), which the commit message describes as the current active user profile. URIs for other profiles are rejected rather than resolved against that profile.",
      "The existing checkUriPermission step is unchanged. The fix adds filters in front of it and does not change the permission semantics."
     ],
     "behavior_changes": [
      "Preselection URIs are silently dropped, with a Log.d message, unless they have the content scheme and match the PICKER_ID or PICKER_GET_CONTENT_ID pattern of LocalUriMatcher for MediaStore.AUTHORITY (on 16/17, PICKER_TRANSCODED_ID also matches).",
      "Preselection URIs whose user id segment differs from the current user are silently dropped with a Log.d message.",
      "A new public static helper, PickerUriResolver.isValidPickerUri(Uri), is introduced. On 16/17, PickerUriResolver.getUserId(Uri) becomes public."
     ],
     "preconditions": [
      "A calling app supplies preselection URIs (EXTRA_PICKER_PRE_SELECTION_URIS), so PickerUriResolver.processUrisForSelection runs with QUERY_ID_SELECTION populated and with QUERY_SHOULD_SCREEN_SELECTION_URIS and EXTRA_CALLING_PACKAGE_UID set, as mimicked in PickerUriResolverTest.",
      "The calling app controls the supplied URI strings. Before the fix, their only check was checkUriPermission for the calling uid."
     ],
     "per_major": {
      "14": "not affected per the bulletin (not listed). 6/8 lines of the patched region are present at android-security-14.0.0_r29, but no fix is recorded there.",
      "15": "same fix (RESTRICT AUTOMERGE cherry-pick 1b032be59978, Change-Id If872e5ee84f18abde0551fb52ca025a9d3808cfa) in android-security-15.0.0_r17. It touches only PickerUriResolver.java and its test, and accepts PICKER_ID and PICKER_GET_CONTENT_ID.",
      "16": "adapted fix (32217e55e510) in android-security-16.0.0_r8: the same PickerUriResolver changes, plus PICKER_TRANSCODED_ID accepted, getUserId made public, the same checks added to photopicker/v2/model/MediaQueryForPreSelection.java, and new PickerDataLayerV2Test cases.",
      "17": "adapted fix (50d220c204df) in android-security-17.0.0_r1, with the same code changes as the Android 16 commit."
     },
     "backport_notes": [
      "The fix depends on LocalUriMatcher.matchUri(Uri, boolean allowHidden) and the PICKER_ID / PICKER_GET_CONTENT_ID constants, which the 15 commit uses without adding imports. Add PICKER_TRANSCODED_ID only if that constant exists on the target branch. If the target branch has the photopicker v2 MediaQueryForPreSelection class, port the 16/17 variant, which also patches that class and makes getUserId public.",
      "MediaProvider ships as a mainline module, so device fixes may arrive via module update rather than the platform security tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether photopicker/v2/model/MediaQueryForPreSelection.java exists on the Android 15 branch and, if so, why the 15 commit does not patch it.",
      "The exact LocalUriMatcher path patterns for PICKER_ID / PICKER_GET_CONTENT_ID (not shown). So it is unclear whether getUserId can still throw NumberFormatException after isValidPickerUri passes, and how the surrounding catch handles that.",
      "The concrete mismatch mechanism and what data was exposed. The material only says the fix prevents data mismatch during database queries."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45518": {
     "cve": "CVE-2026-45518",
     "summary": "Adds an OPTION_CHECK_BRACKETS mode to android.database.sqlite.SQLiteTokenizer that rejects SQL fragments with unbalanced parentheses, and makes ContactsProvider's SelectionBuilder.addClause() run every clause through it so a caller-supplied selection cannot close the wrapping parentheses and escape the AND-concatenated restriction clauses.",
     "component": "platform/frameworks/base (core/java/android/database/sqlite/SQLiteTokenizer) and platform/packages/providers/ContactsProvider (util/SelectionBuilder)",
     "weakness": "SQL injection via missing validation of a caller-supplied selection fragment (unbalanced parentheses breaking out of a parenthesised AND clause)",
     "root_cause": "SelectionBuilder (ContactsProvider) builds a WHERE clause by wrapping each added clause in parentheses and joining them with AND, e.g. \"(A) AND (B)\". The constructor passes the base selection, documented as \"typically the user supplied selection arg\", straight to addClause(), which only checked TextUtils.isEmpty() before storing it. A selection containing extra closing parentheses (the test uses \"1=1)) OR ((1=1\") could terminate the wrapper's parentheses and append an OR branch, so restriction clauses added afterwards by the provider no longer constrain the query. SQLiteTokenizer had no way to detect this: it tracked quotes, comments and semicolons but ignored '(' and ')'. It also contained a bug where \"options &= OPTION_TOKEN_ONLY\" (compound assignment instead of a test) overwrote the options value, which would have cleared any additional option bits.",
     "fix_analysis": [
      "frameworks/base core/java/android/database/sqlite/SQLiteTokenizer.java: adds public constant OPTION_CHECK_BRACKETS = 1 << 1. In tokenize(String, int, Consumer<String>) a new 'level' counter is kept: '(' increments it, ')' decrements it and, if level goes below zero and OPTION_CHECK_BRACKETS is set, throws IllegalArgumentException(\"Unbalanced brackets in '...'\") via genException; after the loop, a non-zero level with the option set also throws. Because the '(' and ')' handling sits after the identifier, quoted-string and comment handling in the loop, brackets inside quotes or comments are not counted (comments covered by testCheckBracketsInComments).",
      "SQLiteTokenizer.tokenize(): the Consumer parameter becomes @Nullable and every checker.accept(token) call is guarded by a null check, so callers that only want validation can pass null.",
      "SQLiteTokenizer.tokenize(): the two occurrences of '(options &= OPTION_TOKEN_ONLY) != 0' are corrected to '(options & OPTION_TOKEN_ONLY) != 0'. This is required for the new option to work, since the compound assignment would have stripped OPTION_CHECK_BRACKETS from options the first time a non-token character was processed.",
      "ContactsProvider src/com/android/providers/contacts/util/SelectionBuilder.java: addClause(String clause) now calls SQLiteTokenizer.tokenize(clause, SQLiteTokenizer.OPTION_CHECK_BRACKETS, null) before mWhereClauses.add(clause). Since the constructor delegates to addClause(), the base (user-supplied) selection is checked too; an unbalanced clause throws IllegalArgumentException and is never stored.",
      "Tests: SQLiteTokenizerTest.testCheckBrackets / testCheckBracketsInComments and SelectionBuilderTest.testInjection exercise the rejection of \"1=1)) OR ((1=1\" through both the constructor and addClause."
     ],
     "completeness": [
      "Covers: any clause passed to SelectionBuilder (constructor or addClause) in ContactsProvider is rejected if its parentheses are unbalanced or if a ')' closes more than was opened within the clause, which is what is needed to keep each clause inside its own '(...)' wrapper. Semicolons were already rejected by the tokenizer.",
      "The bracket check is opt-in (OPTION_CHECK_BRACKETS); existing tokenize() callers that do not pass the flag get no new validation. Only SelectionBuilder in ContactsProvider is shown adopting it; whether other ContactsProvider code paths concatenate caller selections without going through SelectionBuilder is not shown in the material.",
      "Validation is syntactic only: a clause with balanced brackets is accepted regardless of content, which is sufficient for the wrapper-escape issue but does not address any other form of selection injection that may exist elsewhere.",
      "Tokenizer side effect: the '&=' to '&' correction also changes OPTION_TOKEN_ONLY handling, but only in the sense of no longer corrupting the options value; no caller-visible behaviour change for that flag is evident from the diff.",
      "Brackets inside quoted identifiers/strings and comments are skipped by the pre-existing tokenizer branches, so they do not affect the level count; unterminated quotes/brackets already raise 'Unterminated quote' per existing tests (testBrackets)."
     ],
     "behavior_changes": [
      "ContactsProvider queries whose selection (or any internally added clause) has unbalanced parentheses now fail with IllegalArgumentException(\"Unbalanced brackets in '<clause>'\") from SelectionBuilder.addClause instead of being executed.",
      "New public constant SQLiteTokenizer.OPTION_CHECK_BRACKETS and a @Nullable Consumer parameter on tokenize(String, int, Consumer); passing null is now legal.",
      "SQLiteTokenizer.tokenize() no longer mutates its options value via '&=' when a non-token or non-identifier character is seen, so option bits other than OPTION_TOKEN_ONLY survive the whole scan."
     ],
     "preconditions": [
      "A caller must reach a ContactsProvider code path that feeds a caller-controlled selection string into SelectionBuilder (constructor or addClause) and relies on later addClause() calls to restrict the result; the SelectionBuilder javadoc states the base selection is typically the user supplied selection arg.",
      "The selection must contain parentheses outside quotes and comments that close the wrapper (SQLiteTokenizer skips bracket characters inside quoted strings and comments).",
      "Which ContactsProvider entry points use SelectionBuilder is not shown in the material (per index search, only SelectionBuilder itself and its test were returned)."
     ],
     "per_major": {
      "14": "Same frameworks/base tokenizer fix (9183bfe6574b, identical diff) in android-security-14.0.0_r29; resolver marks the CVE resolved by bug_id, 7/8 lines of the patched region present. The ContactsProvider commit for 14 is not included in the material.",
      "15": "Both commits present in android-security-15.0.0_r17: frameworks/base e93df405837f (tokenizer) and ContactsProvider b8e00892122c (SelectionBuilder); 8/8 lines of the patched region present.",
      "16": "Same ContactsProvider fix (3c11261b2a6b, identical diff) in android-security-16.0.0_r8; 8/8 lines present. The frameworks/base tokenizer commit for 16 is not shown in the material but the ContactsProvider change requires it.",
      "17": "Same frameworks/base tokenizer fix (5a73367d5b3a, identical source diff; only the test file's blob ids differ) in android-security-17.0.0_r1; 7/8 lines present. The ContactsProvider commit for 17 is not shown in the material."
     },
     "backport_notes": [
      "Two-repo dependency: the ContactsProvider change compiles only once frameworks/base provides SQLiteTokenizer.OPTION_CHECK_BRACKETS and the @Nullable Consumer overload; land the frameworks/base commit first, including the '&=' to '&' correction, otherwise the new flag is silently cleared during the scan.",
      "SQLiteTokenizer is in the platform framework (not an updatable module), so both commits must ship in the same platform build (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which ContactsProvider query/update/delete entry points route caller-supplied selections through SelectionBuilder, and which privilege or data exposure the escape allowed (bulletin only says EoP, High).",
      "Whether the ContactsProvider commit was applied to Android 14 and 17 and the tokenizer commit to Android 16 (only the opposite halves are shown for those branches, though the resolver marks all four as resolved).",
      "Whether other ContactsProvider or platform callers that concatenate selections were also updated to use OPTION_CHECK_BRACKETS.",
      "How the tokenizer handles unterminated comments; existing tests shown only cover unterminated quotes."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45519": {
     "cve": "CVE-2026-45519",
     "summary": "Adds structural, scheme, authority and user-ID validation of caller-supplied photopicker preselection URIs (EXTRA_PICKER_PRE_SELECTION_URIS) in PickerUriResolver.processUrisForSelection before they are used to build the picker database query.",
     "component": "platform/packages/providers/MediaProvider, photopicker (PickerUriResolver / preselection URI handling)",
     "weakness": "Improper input validation of caller-controlled URIs (missing user-ID / authority / path consistency check before use in DB query)",
     "root_cause": "PickerUriResolver.processUrisForSelection parsed each string in the preselection list (QUERY_ID_SELECTION) with Uri.parse and relied only on checkUriPermission(uri, -1, callingUid) before adding it to accessibleUris, whose IDs then feed the picker DB query selection. Nothing checked that the URI used the content scheme, the MediaStore.AUTHORITY authority, or a valid picker path shape (content://media/picker/<user-id>/<authority>/media/<id>, per the 16/17 tests). Nothing checked that the user ID encoded in the URI matched the user the picker runs as (UserHandle.myUserId()). A URI with a foreign user ID or an unexpected structure could therefore be accepted, and the commit message describes the result as a data mismatch during database queries (the bulletin classifies the issue as information disclosure).",
     "fix_analysis": [
      "src/com/android/providers/media/PickerUriResolver.java: adds a static LocalUriMatcher sStaticUriMatcher = new LocalUriMatcher(MediaStore.AUTHORITY) and a new public static method isValidPickerUri(@Nullable Uri). It rejects null and any scheme other than ContentResolver.SCHEME_CONTENT, then requires sStaticUriMatcher.matchUri(uri, /* allowHidden */ false) to return PICKER_ID or PICKER_GET_CONTENT_ID. Per its javadoc, this also enforces the MediaStore.AUTHORITY authority and the picker path segments.",
      "src/com/android/providers/media/PickerUriResolver.java: processUrisForSelection now runs two checks inside the per-URI try block, before checkUriPermission. It skips (continue, Log.d) any URI that fails isValidPickerUri, then skips any URI whose getUserId(uriForSelection) != UserHandle.myUserId(). In the 16/17 diffs, getUserId is Integer.parseInt of path segment 1. Only URIs that pass both checks reach checkUriPermission and accessibleUris.",
      "tests/src/com/android/providers/media/PickerUriResolverTest.java: testProcessUrisForSelection_permissionChecks and the cloud-authority test now build picker URIs with UserHandle.myUserId() instead of TEST_USER, so they pass the new user-ID filter."
     ],
     "completeness": [
      "Covers only the PickerUriResolver.processUrisForSelection path. The checks run per URI before the existing permission check, so a URI must be a well-formed picker URI for the current user AND be permission-granted to the calling uid.",
      "On Android 15, isValidPickerUri accepts only PICKER_ID and PICKER_GET_CONTENT_ID. The 16/17 versions also accept PICKER_TRANSCODED_ID. Transcoded picker URIs, if they exist on 15, would be filtered out.",
      "The Android 15 commit does not touch photopicker/v2/model/MediaQueryForPreSelection.java, which the 16/17 commits patch with the same check sequence. The material does not show whether that class exists at android-security-15.0.0_r17.",
      "The user-ID check compares against UserHandle.myUserId() rather than a caller-specified target user, so cross-profile preselection URIs are dropped."
     ],
     "behavior_changes": [
      "Preselection URIs are now silently ignored (Log.d) if they have a non-content scheme, an authority other than MediaStore.AUTHORITY, a path that does not match the picker / picker_get_content item patterns, or a user ID different from the running user. Such URIs no longer contribute to QUERY_LOCAL_ID_SELECTION / QUERY_CLOUD_ID_SELECTION.",
      "Preselection of media from another user profile (if any app relied on it) is no longer honored.",
      "New public static API PickerUriResolver.isValidPickerUri(Uri)."
     ],
     "preconditions": [
      "An app launches the photopicker with URIs in EXTRA_PICKER_PRE_SELECTION_URIS. These reach PickerUriResolver.processUrisForSelection through queryArgs QUERY_ID_SELECTION, with QUERY_SHOULD_SCREEN_SELECTION_URIS and EXTRA_CALLING_PACKAGE_UID set (as PickerUriResolverTest in the material mimics).",
      "For the user-ID mismatch case, the URI encodes a user ID that differs from the user the picker runs as.",
      "The calling uid must still hold a URI read grant for the supplied URI to pass the pre-existing checkUriPermission."
     ],
     "per_major": {
      "14": "Not affected (the bulletin does not list Android 14). No fix commit is referenced for 14. For context, 6/8 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Resolved by commit 1b032be59978 in android-security-15.0.0_r17 (RESTRICT AUTOMERGE cherry-pick, Change-Id If872e5ee84f18abde0551fb52ca025a9d3808cfa). It touches only PickerUriResolver.java (isValidPickerUri accepting PICKER_ID / PICKER_GET_CONTENT_ID, plus the user-ID check) and PickerUriResolverTest.java. 8/8 patched lines present.",
      "16": "Adapted fix 32217e55e510 in android-security-16.0.0_r8. It makes the same PickerUriResolver changes and also accepts PICKER_TRANSCODED_ID and makes getUserId public. It adds the same validate / user-ID / permission sequence to photopicker/v2/model/MediaQueryForPreSelection.java, and adds PickerDataLayerV2Test cases for cross-user, malformed, fake-authority and invalid-segment URIs. 7/8 patched lines present.",
      "17": "Adapted fix 50d220c204df in android-security-17.0.0_r1, with the same content as the Android 16 commit (PickerUriResolver + MediaQueryForPreSelection + tests). 7/8 patched lines present."
     },
     "backport_notes": [
      "The fix depends on LocalUriMatcher and its PICKER_ID / PICKER_GET_CONTENT_ID constants (the PICKER_ID static import is visible at the 15 tag) and on the existing getUserId(Uri) helper. If the target tree contains photopicker/v2/model/MediaQueryForPreSelection.java, port the 16/17 variant instead, which also makes getUserId public and accepts PICKER_TRANSCODED_ID.",
      "Keep isValidPickerUri ahead of the getUserId check, so malformed URIs are rejected before the user segment is parsed. Verify that the surrounding catch handles any parse failure."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether photopicker/v2/model/MediaQueryForPreSelection.java (patched on 16/17) exists at android-security-15.0.0_r17 and, if so, why the 15 commit leaves it out.",
      "The exact disclosure outcome for a mismatched user ID is not shown. The commit only describes it as a data mismatch during database queries.",
      "Which exceptions the surrounding try/catch in processUrisForSelection handles (not shown in the diff context).",
      "The implementation of getUserId on the 15 branch (shown only in the 16/17 diffs)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45520": {
     "cve": "CVE-2026-45520",
     "summary": "BiometricsSettingsBase now accepts the EXTRA_USER_ID and gatekeeper password handle intent extras only when the hosting SettingsActivity was launched by the Settings package itself; external launches fall back to the current user and ignore any supplied gatekeeper handle.",
     "component": "platform/packages/apps/Settings, biometrics/combination (CombinedBiometricSettingsActivity / BiometricsSettingsBase)",
     "weakness": "Missing caller validation on trusted intent extras (improper authorization via caller-controlled state)",
     "root_cause": "BiometricsSettingsBase.onAttach read Intent.EXTRA_USER_ID straight from the host activity intent (defaulting to UserHandle.myUserId()). BiometricsSettingsBase.onCreate read the gatekeeper password handle from the same intent via BiometricUtils.containsGatekeeperPasswordHandle/getGatekeeperPasswordHandle. Neither checked who launched the activity. Per the commit message, an unprivileged local app launching CombinedBiometricSettingsActivity could inject both extras to bypass device credential confirmation and manipulate biometric settings such as Watch Unlock for arbitrary users. Per the new tests, without a handle the fragment calls launchChooseOrConfirmLock().",
     "fix_analysis": [
      "src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java:onAttach: mUserId is now initialised to UserHandle.myUserId() and a new field mAllowInternalExtras is set to false. Only if getActivity() is a SettingsActivity and SettingsActivity.getInitialCallingPackage() equals activity.getPackageName() is mAllowInternalExtras set to true and EXTRA_USER_ID read from the intent.",
      "src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java:onCreate: the gatekeeper password handle is now read only when mAllowInternalExtras is true in addition to BiometricUtils.containsGatekeeperPasswordHandle(getIntent()); otherwise mGkPwHandle stays 0, and the external-caller test verifies launchChooseOrConfirmLock() is called.",
      "tests/robotests/.../CombinedBiometricProfileSettingsTest.java: host activity switched from FragmentActivity to SettingsActivity, ShadowPasswordUtils added so the calling package can be set; new tests testInternalCaller_allowsSensitiveExtras (user 10 / handle 12345 accepted) and testExternalCaller_ignoresSensitiveExtras (EXTRA_INITIAL_CALLING_PACKAGE = com.poc.app yields myUserId, handle 0, and launchChooseOrConfirmLock() called)."
     ],
     "completeness": [
      "Covers both sensitive extras consumed by BiometricsSettingsBase (EXTRA_USER_ID and EXTRA_KEY_GK_PW_HANDLE). Because the check lives in the shared base class, it applies to all subclasses hosted by a SettingsActivity.",
      "The trust decision is delegated entirely to SettingsActivity.getInitialCallingPackage(), whose implementation is not shown in the material.",
      "If the host activity is not a SettingsActivity, the extras are now ignored unconditionally (fail closed).",
      "Other intent extras read by this fragment or its subclasses, and other biometric entry points reading EXTRA_USER_ID or a gatekeeper handle, are not touched by this commit; the material does not show whether they have equivalent checks."
     ],
     "behavior_changes": [
      "For launches not originating from the Settings package, mUserId is the current user and mGkPwHandle is 0, so the fragment goes through launchChooseOrConfirmLock() (device credential confirmation) as shown by the new test.",
      "Any host that is not a SettingsActivity can no longer pass EXTRA_USER_ID or a gatekeeper handle to this fragment.",
      "No new error or result code; stricter input handling only."
     ],
     "preconditions": [
      "A local app able to start CombinedBiometricSettingsActivity (the commit message states unprivileged local apps could launch it) with EXTRA_USER_ID and/or EXTRA_KEY_GK_PW_HANDLE extras set."
     ],
     "per_major": {
      "14": "same fix, commit a88ca4587208 in android-security-14.0.0_r29; identical logic, additionally adds 'import android.app.Activity;' which the 14 file lacked; test class is annotated @Ignore on this branch. Patched region 8/8 present.",
      "15": "fix commit 783b7806995f in android-security-15.0.0_r17 (this analysis); 8/8 lines of the patched region present.",
      "16": "same fix, commit 23e5e4077bc9 in android-security-16.0.0_r8; identical hunks, only surrounding context differs (mBiometricsAuthenticationRequested field). 8/8 present.",
      "17": "same fix, commit 759e75cec1c1 in android-security-17.0.0_r1; identical hunks. 8/8 present."
     },
     "backport_notes": [
      "Single commit; requires SettingsActivity.getInitialCallingPackage() and the test shadow ShadowPasswordUtils (setCallingAppPackageName/reset) to exist in the target branch. Android 14 needed an extra 'import android.app.Activity;'."
     ],
     "confidence": "high",
     "unknowns": [
      "Implementation of SettingsActivity.getInitialCallingPackage() and how it handles the EXTRA_INITIAL_CALLING_PACKAGE extra versus the real calling package is not in the material.",
      "Whether other biometric settings entry points outside BiometricsSettingsBase read the same extras without a caller check.",
      "Exact exported status / intent filter of CombinedBiometricSettingsActivity is not shown (only stated in the commit message).",
      "Which device configurations expose the combined biometrics settings page.",
      "How a non-zero mGkPwHandle is used internally to skip confirmation is not shown in the diff."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45521": {
     "cve": "CVE-2026-45521",
     "summary": "StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid(), and AppFuseBridge.openFile rejects the request unless the caller UID equals the uid recorded in the MountScope for the requested mountId. The rejection is a FuseUnavailableMountException, which the caller turns into a null return.",
     "component": "platform/frameworks/base, system_server StorageManagerService / AppFuse proxy file descriptor bridge (services/core/java/com/android/server/storage/AppFuseBridge.java)",
     "weakness": "Missing ownership/authorization check on a Binder entry point (caller UID not compared to the owner of the referenced AppFuse mount)",
     "root_cause": "IStorageManager.openProxyFileDescriptor(mountId, fileId, mode) is a Binder method of StorageManagerService. It looked up the AppFuse MountScope only by the caller-supplied mountId (mScopes.get(mountId) in AppFuseBridge.openFile). If a scope was found, it waited for the mount and called scope.openFile(mountId, fileId, flags). Mount scopes are created as new AppFuseMountScope(uid, name), with names assigned sequentially (mNextAppFuseName++), so each scope records a uid. openFile never compared that uid to the calling UID. Per the commit message, this allowed unauthorized cross-app access to another app's AppFuse mount. The bulletin rates the issue Information Disclosure, High severity.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java :: openProxyFileDescriptor: adds `final int uid = Binder.getCallingUid();` at method entry, before the mode masking. It passes the uid as a new first argument: mAppFuseBridge.openFile(uid, mountId, fileId, mode).",
      "services/core/java/com/android/server/storage/AppFuseBridge.java :: openFile: the signature changes to openFile(int uid, int mountId, int fileId, int mode). Inside the synchronized(this) block, the lookup condition becomes `if (scope == null || scope.uid != uid)` and throws FuseUnavailableMountException(mountId). The ownership test runs under the same lock as the scope lookup and before scope.waitForMount() and scope.openFile(), so a non-owner never reaches the mount wait or the file open.",
      "openProxyFileDescriptor already catches FuseUnavailableMountException (together with InterruptedException), logs it at Slog.v as \"The mount point has already been invalid\" and returns null. No new exception type crosses the Binder boundary."
     ],
     "completeness": [
      "Covers the only consumer of mScopes for file opening shown in the material: AppFuseBridge.openFile, called from StorageManagerService.openProxyFileDescriptor. The check requires exact equality with scope.uid, the uid stored when the AppFuseMountScope was built.",
      "The check is an exact uid match, not an app-id or package match. No permission check or allowlist is added, so system or privileged callers that are not the owning uid get no exception.",
      "Other AppFuseBridge entry points (addBridge, onMount), AppFuseMountScope.openFile and the native code are untouched. The material does not show where the uid passed to AppFuseMountScope comes from, or whether mountId reuse after a scope is removed interacts with the check.",
      "The bulletin lists 16-qpr2 and 17-next as affected. The material only shows the fix landing in the four security tags, not in those branches."
     ],
     "behavior_changes": [
      "openProxyFileDescriptor now returns null when the calling UID differs from the mount owner's uid. This is the same path as an already-invalid mount. Before the fix, it returned a ParcelFileDescriptor. The failure is logged only at verbose level, with the existing message \"The mount point has already been invalid\", so in logs it looks the same as a stale mount.",
      "AppFuseBridge.openFile has a new signature (int uid, int mountId, int fileId, int mode). Any other in-tree caller of the old 3-argument signature would fail to compile.",
      "Any legitimate caller that opened a proxy descriptor on a mount created by a different uid (if one exists) is now refused."
     ],
     "preconditions": [
      "The caller can invoke IStorageManager.openProxyFileDescriptor on system_server over Binder. No permission check is visible on the method in the shown context.",
      "mAppFuseBridge has been created and at least one AppFuseMountScope exists in mScopes.",
      "The caller supplies a mountId matching another uid's live scope. Mount names are allocated sequentially from mNextAppFuseName in StorageManagerService, as shown in the context.",
      "Before the fix, the scope's waitForMount() must also succeed and scope.openFile must accept the given fileId. The material does not show how AppFuseMountScope validates fileId."
     ],
     "per_major": {
      "14": "same fix: commit 0d2ee8e16afc in android-security-14.0.0_r29. The AppFuseBridge.java hunk is byte-identical (292314840efb..d839b30ddf44). The StorageManagerService.java hunk is identical except for line offsets (3571/3581). Resolver: resolved via bug_id, 7/8 lines of the patched region present at the tag.",
      "15": "same fix: commit bca099fcb293 in android-security-15.0.0_r17 (the analysed commit). The code context confirms that both the Binder.getCallingUid() capture and the scope.uid != uid check are present at the tag. Resolver: 7/8 lines of the patched region present.",
      "16": "same fix: commit 63f908ebd9ee in android-security-16.0.0_r8. Identical AppFuseBridge.java hunk; StorageManagerService.java offsets 3829/3839. Resolver: resolved via bug_id, 7/8 lines of the patched region present at the tag.",
      "17": "same fix: commit 03988cf95e56 in android-security-17.0.0_r1. Identical AppFuseBridge.java hunk; StorageManagerService.java offsets 3793/3803. Resolver: resolved via bug_id, 7/8 lines of the patched region present at the tag."
     },
     "backport_notes": [
      "Single self-contained commit. AppFuseBridge.java has the same pre- and post-fix blob hashes on 14 through 17, so that hunk should apply cleanly wherever AppFuseBridge.openFile exists. On a fork, grep for any other caller of AppFuseBridge.openFile(int,int,int), because the signature changes. Such callers must now pass a uid explicitly, and the open fails unless that uid equals the scope's recorded uid."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the Binder method has any permission or SELinux gating beyond this UID check (none visible in the shown context).",
      "Where the uid stored in AppFuseMountScope comes from (the enclosing method of the scope creation is only partially shown).",
      "The implementation of AppFuseMountScope.openFile and how it validates fileId, so the material does not show exactly what a cross-uid caller could read before the fix.",
      "Status of the fix on the 16-qpr2 and 17-next branches listed as affected by the bulletin; only the four security tags are shown.",
      "Why the per-major resolver reports 7/8 lines of the patched region rather than 8/8 at each tag, when the code context at 15.0.0_r17 shows the full patch."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45525": {
     "cve": "CVE-2026-45525",
     "summary": "Makes MediaProvider's ISO/MP4 metadata parser keep going when an XMP box exceeds the in-memory size limit and flags the whole unreadable XMP box range for redaction, instead of aborting box parsing and returning no XMP redaction ranges.",
     "component": "platform/packages/providers/MediaProvider, media metadata redaction (util/IsoInterface, util/XmpDataParser; util/XmpInterface on Android 14)",
     "weakness": "Fail-open error handling in redaction: an oversized XMP box caused the parser to stop and the redaction logic to treat the XMP as empty, so location metadata was left unredacted (information disclosure).",
     "root_cause": "In IsoInterface.java the box-parsing method allocates a buffer for XMP payloads (XMP UUID box or BOX_XMP) via allocateBuffer, which yields null when the payload exceeds the memory limit (1 MiB per the commit message). On null the code did `return false`, aborting parsing so all later boxes in the file were never seen. In XmpDataParser.java, XmpData.extractXmpData(IsoInterface) treated a null buffer from getBoxBytesForXmpUuid as 'no XMP', fell back to BOX_XMP, and if that was also null reset both buf and xmpOffsets to empty arrays, so getRedactionRanges(XmpData) parsed an empty document and returned no ranges. The net effect was that a file with an oversized XMP box had neither the XMP contents nor subsequent boxes considered for redaction. (Android 14 had the equivalent `return null` paths on `len > Integer.MAX_VALUE` and OutOfMemoryError, and XmpInterface.process had no handling for a missing buffer.)",
     "fix_analysis": [
      "IsoInterface.java, box-parsing method (hunk around allocateBuffer): for both the XMP_UUID branch and the BOX_XMP branch, `if (box.data == null) return false; Os.read(...)` is replaced by `if (box.data != null) { Os.read(...) }`. The Box object (with its range {pos, len}) is still created and parsing continues to the following boxes; only the payload read is skipped.",
      "XmpDataParser.java, XmpData.extractXmpData(IsoInterface): the fallback to BOX_XMP now only happens when `buf == null && xmpOffsets.length == 0` (box truly absent). If buf is null but xmpOffsets is non-empty (box present but too large to load), buf becomes `new byte[0]` and xmpOffsets is preserved (the line `xmpOffsets = new long[0]` is removed).",
      "XmpDataParser.java, getRedactionRanges(XmpData): new early return. If `mRawXmp.length == 0 && mXmpOffsets.length > 0`, it returns `LongArray.fromArray(xmpData.mXmpOffsets, xmpData.mXmpOffsets.length)`, i.e. the entire XMP box range(s) are flagged for redaction rather than attempting to parse an empty document.",
      "Test: new tests/res/raw/gps_test_xmp_big.mp4 (about 4.3 MB) and XmpDataParserTest.testContainer_IsoRedactionRanges_OversizedXmp asserting that the oversized box still appears in getBoxRanges/getBoxRangesForXmpUuid, that getRedactionRanges returns exactly one [start, end] pair, and that createXmpInterface yields an empty redacted XMP.",
      "Android 14 variant (93cf0194cff2): IsoInterface.java adds MAX_XMP_SIZE_BYTES = 1 MiB and a public XMP_UUID constant; only XMP_UUID UUID boxes have data read; oversize (len - headerSize > MAX_XMP_SIZE_BYTES) or OutOfMemoryError is logged and parsing continues instead of `return null`. XmpInterface.java: constructor/process accept @Nullable rawXmp; when rawXmp is null and xmpOffsets.length >= 2 the pair [offsets[0], offsets[1]] is added to mRedactedRanges and mRedactedXmp is set to an empty array; Exif XMP offsets are converted from [start, length] to [start, end] where they are read from ExifInterface."
     ],
     "completeness": [
      "Covers both ISO XMP carriers handled by IsoInterface: the UUID box with XMP_UUID and the BOX_XMP box. Both no longer abort parsing, so boxes located after the oversized XMP box are parsed again.",
      "The whole-range redaction in getRedactionRanges(XmpData) is in the shared private method, so it applies to any XmpData with empty raw bytes and non-empty offsets, including the ExifInterface path; the material does not show whether the Exif path can produce that state.",
      "allocateBuffer and its size limit are unchanged on 15+; the fix only changes how a null result is handled.",
      "When an oversized XMP UUID box is present, the BOX_XMP fallback is intentionally skipped; the code considers one XMP source per file (as it did before the fix when the UUID box was readable).",
      "This diff does not change how non-XMP boxes are handled for redaction; they benefit only indirectly because parsing no longer stops early. How RedactionUtils handles those boxes is not in the material."
     ],
     "behavior_changes": [
      "An XMP box larger than the allocation limit no longer terminates ISO box parsing; later boxes are now enumerated.",
      "For an oversized XMP box, getRedactionRanges returns the full box range(s) as redaction ranges instead of an empty list.",
      "createXmpInterface for such a file returns an XmpInterface whose getRedactedXmp() is empty (per the new test).",
      "If an oversized XMP UUID box exists, a coexisting BOX_XMP box is no longer consulted as a fallback.",
      "Android 14 only: non-XMP UUID boxes no longer have their payload read into memory; the per-box check changes from `len > Integer.MAX_VALUE` to a 1 MiB cap on the XMP payload; Exif XMP offsets are reported as [start, end] rather than [start, length]."
     ],
     "preconditions": [
      "A media file in an ISO-BMFF style container (parsed by IsoInterface.fromFile) that contains an XMP box (XMP_UUID uuid box or BOX_XMP) whose payload exceeds the allocateBuffer limit (1 MiB per the commit message).",
      "The file is read through a path where MediaProvider computes redaction ranges: MediaProvider.getRedactionRangesForFuse calls RedactionUtils.getRedactionRanges(file) for opens that require redaction, per index search, which lists it among users of XmpDataParser.getRedactionRanges.",
      "The reader is an app for which location metadata must be redacted (the exact policy is decided in MediaProvider and not shown in the material)."
     ],
     "per_major": {
      "14": "adapted fix: resolved at android-security-14.0.0_r29 (93cf0194cff2). The 14 fix does not use allocateBuffer or XmpDataParser; it introduces MAX_XMP_SIZE_BYTES and XMP_UUID in IsoInterface, replaces the `return null` abort paths with log-and-continue, and handles a null XMP buffer in XmpInterface.process by redacting [offsets[0], offsets[1]]. Test added to XmpInterfaceTest.",
      "15": "same fix: resolved at android-security-15.0.0_r17 (ec0834680bfe), IsoInterface.java + XmpDataParser.java + new test and test asset.",
      "16": "same fix: resolved at android-security-16.0.0_r8 (cedfc5086b8d), same diff content as 15 (line numbers shifted by one in IsoInterface.java).",
      "17": "same fix: resolved at android-security-17.0.0_r1 (21270fd56118), same diff content as 15/16."
     },
     "backport_notes": [
      "The 15+ patch assumes IsoInterface.allocateBuffer (null when over the limit), getBoxBytesForXmpUuid/getBoxRangesForXmpUuid, and the XmpDataParser/XmpData split. Trees structured like Android 14 (XmpInterface.process, reading every UUID box) need the 14-style variant that adds the size constant and the null-buffer handling in XmpInterface.",
      "The fix relies on the Box still being added to the parsed box list when box.data is null so that getBoxRanges returns its range; the new test asserts exactly this, so run testContainer_IsoRedactionRanges_OversizedXmp after backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact name of the IsoInterface box-parsing method containing the patched hunk is not shown in the material.",
      "The body of allocateBuffer is not shown; the 1 MiB limit comes from the commit message and code comments.",
      "The material does not show getBoxRangesForXmpUuid/getBoxRanges, so whether the Box range {pos, len} is converted to [start, end] before being returned as redaction offsets is not visible (the test only checks offsets[1] > offsets[0]).",
      "How RedactionUtils.getRedactionRanges consumes the XMP ranges, how it handles non-XMP boxes, and which redaction policy applies to a given reader is not in the material.",
      "The pre-fix code for Android 15 showed `return false` on null buffer; whether other callers of that return value treat it as an error or silently stop is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45527": {
     "cve": "CVE-2026-45527",
     "summary": "In the MP4/HEIF extractor, convertCleanApertureToRect() now checks the crop rectangle bounds with int64_t arithmetic before computing right/bottom, so crafted CleanAperture values can no longer trigger a signed int32 overflow (IntSan abort) in media.extractor.",
     "component": "platform/frameworks/av, media/module/extractors/mp4 (HEIF clean aperture handling, media.extractor)",
     "weakness": "Signed integer overflow due to bounds check performed after the overflowing addition (ineffective bounds check)",
     "root_cause": "In media/module/extractors/mp4/HeifCleanAperture.cpp:convertCleanApertureToRect(), *right = *left + clapW and *bottom = *top + clapH were computed as int32_t additions before any range check. *left/*top come from centerX/centerY (which include the file-supplied horizOff/vertOff) and clapW/clapH are the file-supplied clean aperture width/height. With large offsets the addition overflows int32_t, which aborts the process under IntSan. The check that followed (*left > (UINT32_MAX - clapW) || *right > imageW || ...) did not help: for a non-negative int32 left, the UINT32_MAX comparison is effectively never true, and the *right > imageW comparison ran after the overflow had already happened.",
     "fix_analysis": [
      "media/module/extractors/mp4/HeifCleanAperture.cpp:convertCleanApertureToRect(): the two additions producing *right and *bottom now run after the bounds check instead of before it.",
      "The bounds check is rewritten as ((int64_t)*left + clapW > (int64_t)imageW || (int64_t)*top + clapH > (int64_t)imageH) -> return false. Earlier in the function, imageW and imageH are rejected if they are > INT32_MAX. So passing this check guarantees that left + clapW and top + clapH fit in int32_t, and the int32 additions that follow cannot overflow.",
      "media/module/extractors/mp4/tests/HeifCleanApertureUnitTest.cpp: two negative test vectors are added to kInvalidClapPropertyTestParams. Each uses a 2x2 image with horizOff or vertOff set to 0x7FFFFFFE and expects convertCleanApertureToRect() to return false."
     ],
     "completeness": [
      "Covers both the horizontal (left + clapW) and the vertical (top + clapH) addition in convertCleanApertureToRect().",
      "The other arithmetic in the function is already guarded in the shown code: denominators must be > 0, width/height numerators must be >= 0 and width/height non-zero, imageW/imageH must be <= INT32_MAX, the bool results of Fraction::add/subtract are checked, and centerX.n/centerY.n must be >= 0 and integer.",
      "Fraction::isInteger() and getInt32() (n % d, n / d) are only reached after the d <= 0 checks in this function. This fix does not change how safe they are when called from elsewhere.",
      "For inputs that do not overflow, the retained check behaves as before (reject if right > imageW or bottom > imageH). Only the overflow cases are newly rejected."
     ],
     "behavior_changes": [
      "CleanAperture properties whose offset plus width/height exceeds the int32 range now make convertCleanApertureToRect() return false (invalid clap) instead of aborting the media.extractor process under IntSan.",
      "*right and *bottom are no longer written when the function returns false for an out-of-bounds rectangle. Before the fix they were assigned before the check."
     ],
     "preconditions": [
      "A HEIF/MP4 file containing a CleanAperture (clap) property is parsed by the MP4 extractor, so that convertCleanApertureToRect() runs in media.extractor. This comes from the commit message; the production call site is not shown in the material.",
      "The clap values must pass the earlier checks: positive denominators, non-negative non-zero integer width/height, non-negative integer computed center numerators, and image dimensions <= INT32_MAX.",
      "The crash as described requires the integer sanitizer to be enabled for the extractor (the commit message attributes it to an IntSan abort)."
     ],
     "per_major": {
      "14": "not affected per the bulletin; HeifCleanAperture.cpp and its unit test are absent at android-security-14.0.0_r29 (code absent).",
      "15": "same fix: c92a92203957 in android-security-15.0.0_r17, identical diff (blob f0a086703e -> 7d78f0e869). The code context confirms that the int64_t check comes before the additions.",
      "16": "same fix: 9456667afa01 in android-security-16.0.0_r8, identical diff and blob hashes.",
      "17": "same fix: 1e00537f8f43 in android-security-17.0.0_r1, identical diff and blob hashes."
     },
     "backport_notes": [
      "Single self-contained commit. The pre-image of HeifCleanAperture.cpp (blob f0a086703e) is identical on 15, 16 and 17, so the patch applies as-is wherever that file exists. The fix relies on the existing imageW/imageH > INT32_MAX rejection earlier in the function; keep that check when porting."
     ],
     "confidence": "high",
     "unknowns": [
      "The production caller of convertCleanApertureToRect() in the MP4 extractor is not shown in the material (the index search only returned the header, the implementation file and the unit test).",
      "Whether the Fraction(int32_t n, int32_t d) constructor or the clap box parser does any additional validation before this function is not shown.",
      "The implementations of calculateCenter() and Fraction::add/subtract/commonDenominator are not shown, so the material does not confirm that the resulting denominator is positive (and therefore that left/top are non-negative)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45528": {
     "cve": "CVE-2026-45528",
     "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp, passing the target package name and uid. Before, it called PendingIntent.getActivity on a package context created from the system server's own context while the calling identity was cleared.",
     "component": "platform/frameworks/base, system_server StorageManagerService (services/core/java/com/android/server/StorageManagerService.java)",
     "weakness": "Improper privilege management: the PendingIntent was registered under the system server's identity but attributed to the target package (identity confusion in PendingIntent creation)",
     "root_cause": "In StorageManagerService.getManageSpaceActivityIntent, after checking that the caller has external storage access, the code called Binder.clearCallingIdentity(). It then created a Context for the target package with mContext.createPackageContext(packageName, 0) on Android 14/15, or mContext.createPackageContextAsUser(...) on Android 16/17, and called PendingIntent.getActivity(targetAppContext, ...). The resulting PendingIntent went back to the caller (an app that passes the MANAGE_EXTERNAL_STORAGE / hasExternalStorageAccess check). The commit message says this let callers obtain a PendingIntent registered under the target package identity with system privileges.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java:getManageSpaceActivityIntent: removes the Binder.clearCallingIdentity()/restoreCallingIdentity() block and the mContext.createPackageContext / createPackageContextAsUser call, so the system server no longer creates a Context for the target package.",
      "Same function: the Intent (ACTION_DEFAULT, setClassName(packageName, appInfo.manageSpaceActivityName), FLAG_ACTIVITY_NEW_TASK) and the ActivityOptions with MODE_BACKGROUND_ACTIVITY_START_DENIED stay as they were.",
      "Same function: the PendingIntent now comes from LocalServices.getService(ActivityManagerInternal.class).getPendingIntentActivityAsApp(requestCode, intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, options.toBundle(), packageName, appInfo.uid). This passes the target package and its uid explicitly. appInfo is resolved for UserHandle.getUserId(originalUid).",
      "Same function: the catch of PackageManager.NameNotFoundException (which threw IllegalArgumentException(\"packageName not found\")) is removed because no package context is created anymore. A nonexistent package is still handled by the existing appInfo == null check (IllegalArgumentException(\"Invalid packageName\"))."
     ],
     "completeness": [
      "The fix covers the only PendingIntent creation in getManageSpaceActivityIntent. The diff touches no other function.",
      "The fix does not change the caller authorization checks: getPackagesForUid, hasExternalStorageAccess on packagesFromUid[0], getApplicationInfo for the caller's user, and the manageSpaceActivityName null check.",
      "The PendingIntent flags (FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE) and the background-activity-start denial option are kept. The change is in how the PendingIntent is created and which identity it gets.",
      "Whether the fix is correct depends on getPendingIntentActivityAsApp registering the PendingIntent under appInfo.uid/packageName. Its implementation is not in the material."
     ],
     "behavior_changes": [
      "The PendingIntent is now created by ActivityManagerInternal.getPendingIntentActivityAsApp with packageName and appInfo.uid, instead of by PendingIntent.getActivity on a system-derived package context.",
      "The IllegalArgumentException(\"packageName not found\") path no longer exists. A missing package is reported only by the earlier IllegalArgumentException(\"Invalid packageName\").",
      "The calling identity is no longer cleared while the PendingIntent is created.",
      "The uid passed is appInfo.uid, resolved for the caller's user (UserHandle.getUserId(originalUid))."
     ],
     "preconditions": [
      "The caller invokes IStorageManager.getManageSpaceActivityIntent(packageName, requestCode) over Binder.",
      "The caller's uid must pass mStorageManagerInternal.hasExternalStorageAccess(originalUid, packagesFromUid[0]) (MANAGE_EXTERNAL_STORAGE file-manager apps).",
      "The target packageName must resolve through mIPackageManager.getApplicationInfo in the caller's user and must declare a manageSpaceActivityName."
     ],
     "per_major": {
      "14": "Same fix, applied as RESTRICT AUTOMERGE commit 51b647175642 in android-security-14.0.0_r29. The removed code used mContext.createPackageContext(packageName, 0), the same as Android 15.",
      "15": "Fix commit 8c8709a5ad8c in android-security-15.0.0_r17 (the analysed commit). The removed code used mContext.createPackageContext(packageName, 0).",
      "16": "Adapted fix, commit 84d85dbe121f in android-security-16.0.0_r8. The removed code used mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(UserHandle.getUserId(originalUid))). The added code is identical.",
      "17": "Adapted fix, commit 43ef4a09e448 in android-security-17.0.0_r1. The removed code is the same createPackageContextAsUser variant as Android 16, and the added code is the same."
     },
     "backport_notes": [
      "Single-file, single-function change. The added block is identical across 14 to 17; only the removed lines differ (createPackageContext on 14/15, createPackageContextAsUser on 16/17). The backport needs ActivityManagerInternal.getPendingIntentActivityAsApp(int, Intent, int, Bundle, String, int) in the branch. Google's single-file commits call it on all four majors, so it already exists there.",
      "If a fork customized the Intent or ActivityOptions in this function, keep those changes and replace only the PendingIntent.getActivity(targetAppContext, ...) call and the clearCallingIdentity/createPackageContext wrapper around it."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the implementation of ActivityManagerInternal.getPendingIntentActivityAsApp or any extra checks it performs, so the identity of the PendingIntent when sent is not shown.",
      "The material does not show which user mContext.createPackageContext(packageName, 0) resolved to on Android 14/15.",
      "The concrete impact of the old PendingIntent is described only by the commit message (a PendingIntent with system privileges). The bulletin rates it High EoP.",
      "Whether any test was added (the Test: field is empty)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45529": {
     "cve": "CVE-2026-45529",
     "summary": "MmsSmsProvider.query now applies the per-user subscription-ID filter (selectionBySubIds) to the simple conversations, conversation recipients/subject, search and searchSuggest URIs, and returns an empty cursor when the calling user has no associated subscriptions.",
     "component": "platform/packages/providers/TelephonyProvider, MmsSmsProvider (content://mms-sms)",
     "weakness": "Missing authorization / incomplete per-user data filtering: several query paths did not restrict results to the calling user's subscription IDs",
     "root_cause": "In src/com/android/providers/telephony/MmsSmsProvider.java, query() has a selectionBySubIds value available. The test mock suggests it is derived from SubscriptionManager.getSubscriptionInfoListAssociatedWithUser. Several URI cases did not use it: URI_CONVERSATIONS with simple=true (getSimpleConversations), URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT (getConversationById), URI_SEARCH_SUGGEST (the fixed SEARCH_QUERY against the words table) and URI_SEARCH (getTextSearchQuery over sms/pdu joined with words). These paths ran their SQL without any sub_id constraint, so results were not restricted to the user's subscriptions. They also did not handle the case where the user has no subscriptions (selectionBySubIds == null).",
     "fix_analysis": [
      "MmsSmsProvider.java:query, URI_CONVERSATIONS (simple=true branch): if selectionBySubIds == null, it logs and returns emptyCursor. Otherwise it sets selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds) before calling getSimpleConversations.",
      "MmsSmsProvider.java:query, URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT: same pattern. Early empty cursor when null, otherwise selectionBySubIds is concatenated into selection before getConversationById.",
      "MmsSmsProvider.java:query, URI_SEARCH_SUGGEST: early empty cursor when selectionBySubIds == null. The SEARCH_QUERY constant loses its trailing ' ORDER BY snippet LIMIT 50;'. The query is now built at runtime as SEARCH_QUERY + \" AND \" + selectionBySubIds.replace(\"'\", \"\") + \" ORDER BY snippet LIMIT 50;\" and passed to db.rawQuery.",
      "MmsSmsProvider.java:query, URI_SEARCH: early empty cursor when selectionBySubIds == null, placed after an existing check that throws ('... with this query'). getTextSearchQuery now receives selectionBySubIds.",
      "MmsSmsProvider.java:getTextSearchQuery(smsTable, pduTable, selectionBySubIds): builds smsSubIdClause (\" AND \" + smsTable + \".\" + clause with single quotes stripped) and inserts it into the sms/words WHERE. Builds mmsSubIdClause the same way with the pduTable prefix and inserts it into the pdu/part/words WHERE. Both clauses are empty when selectionBySubIds is empty.",
      "tests/.../MmsSmsProviderTest.java: testQuery_subIdEmpty_returnsEmptyCursor mocks getSubscriptionInfoListAssociatedWithUser to return an empty list and asserts a non-null, zero-count cursor for the five URIs. testQuery_subIdValid_doesNotReturnEmptyCursor checks that the valid-subId path still returns a cursor."
     ],
     "completeness": [
      "Covers exactly five query paths, matching the test URI list: conversations?simple=true, conversations/<id>/recipients, conversations/<id>/subject, search?pattern= and searchSuggest?pattern=.",
      "This diff does not modify the non-simple URI_CONVERSATIONS branch (getConversations) or other cases in query() such as URI_MESSAGE_ID_TO_THREAD. The material does not show whether those already applied selectionBySubIds.",
      "Filtering relies on selectionBySubIds being computed earlier in query(), and the diff does not change how it is derived. When it is empty (not null), getTextSearchQuery adds no clause because of the TextUtils.isEmpty guard, while the URI_SEARCH_SUGGEST path appends it unconditionally.",
      "The subId clause is added by string concatenation after stripping single quotes, not through bound selectionArgs. Whether this is correct depends on the exact format of selectionBySubIds, which is not shown."
     ],
     "behavior_changes": [
      "For the five affected URIs, a caller whose user has no associated subscriptions now gets an empty cursor instead of results, and a Log.d line is written for each URI.",
      "Results for those URIs now include an additional selectionBySubIds constraint, intended to restrict rows to the calling user's subscriptions. Before the fix these paths added no such constraint.",
      "searchSuggest SQL is now built at runtime (the SEARCH_QUERY constant no longer contains ORDER BY / LIMIT). ORDER BY snippet LIMIT 50 is kept in the final query.",
      "Full-text search SQL (getTextSearchQuery) gains an additional sub_id predicate on both the sms and the pdu sub-queries."
     ],
     "preconditions": [
      "The caller can query content://mms-sms/ on one of the affected URIs (conversations?simple=true, conversations/<id>/recipients, conversations/<id>/subject, search, searchSuggest). The provider's permission requirements are not shown in the material.",
      "The calling user is not associated with all subscriptions on the device, so that selectionBySubIds restricts the results or is null for the caller."
     ],
     "per_major": {
      "14": "same fix: commit 475853ddf6b3 in android-security-14.0.0_r29. The hunks are identical apart from line offsets, and the tests are identical.",
      "15": "same fix: commit 4fab244beaa3 in android-security-15.0.0_r17 (the analysed commit).",
      "16": "same fix: commit 190c0a3b8e44 in android-security-16.0.0_r8. The diff is byte-identical to Android 15 (same blob indices dd9f07cf..0fa97e1f).",
      "17": "adapted fix: commit 1fab99e951d5 in android-security-17.0.0_r1. getTextSearchQuery already takes canReadRestrictedMessages and otpFilter, so selectionBySubIds is added as a fifth parameter and the sub_id clauses go after the existing read-restriction/OTP clauses. The URI_SEARCH_SUGGEST check is placed after an existing otpFilter empty-cursor check. The getSimpleConversations/getConversationById calls carry the extra 17-only arguments. The tests are annotated with @DisableFlags(Flags.FLAG_SECURE_ACCESS_TO_RESTRICTED_RCS_MESSAGES)."
     },
     "backport_notes": [
      "This is a single commit. It requires query() to already compute selectionBySubIds and emptyCursor earlier in the method, and the test harness to mock SubscriptionManager.getSubscriptionInfoListAssociatedWithUser. On trees with the 17-style getTextSearchQuery signature (restricted RCS / OTP filter), add selectionBySubIds as the last parameter and place the clauses after the existing ones, as in 1fab99e951d5."
     ],
     "confidence": "high",
     "unknowns": [
      "How selectionBySubIds is built (its exact SQL form and why single quotes are stripped) is not in the shown context.",
      "Whether the words table has a sub_id column suitable for the clause appended in URI_SEARCH_SUGGEST.",
      "Whether the non-simple getConversations path, or any other path in the provider, already filtered by subscription.",
      "The permission gate on content://mms-sms/ and the concrete cross-user scenario reported in bug 418776187."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49895": {
     "cve": "CVE-2026-49895",
     "summary": "Raises the minimum EHT Operation element length constant in src/common/ieee802_11_defs.h from 1 to 5, so that the length check guarding access to the EHT Operation Information field accounts for the fixed fields that precede it.",
     "component": "platform/external/wpa_supplicant_8, src/common IEEE 802.11 element definitions (EHT Operation element parsing)",
     "weakness": "Incorrect bounds-check constant leading to a heap out-of-bounds read (information disclosure)",
     "root_cause": "Per the commit message and the diff, EHT_OPERATION_IE_MIN_LEN (named EHT_OPERATION_ELEM_MIN_LEN on Android 17) was defined as 1. That value predated the addition of the 4-byte basic_eht_mcs_nss_set field to struct ieee80211_eht_operation, so the fixed part of the element is 5 bytes before the optional EHT Operation Information field. Elements with length 1 to 4 passed the check. When the EHT_OPER_INFO_PRESENT bit was set, get_eht_operation_channel_width() read oper_info at offset 5, past the end of the element data (heap out-of-bounds read).",
     "fix_analysis": [
      "src/common/ieee802_11_defs.h: EHT_OPERATION_IE_MIN_LEN (Android 14/15/16) or EHT_OPERATION_ELEM_MIN_LEN (Android 17) changes from 1 to 5. On 14/15/16 the spec comment also changes, from IEEE P802.11be/D2.3, 9.4.2.311 to IEEE Std 802.11be-2024, 9.4.2.321, and a blank line is removed. On 17 only the value line changes.",
      "No parsing code is changed. Per the commit message, the existing bounds check now accounts for the 5 fixed bytes, so elements shorter than 5 bytes should no longer reach the oper_info access at offset 5. The material does not show the check itself."
     ],
     "completeness": [
      "The fix corrects the macro value, so every user of EHT_OPERATION_IE_MIN_LEN / EHT_OPERATION_ELEM_MIN_LEN gets the stricter check. The material does not show the body of get_eht_operation_channel_width() or any other user of the macro, so it cannot show whether another EHT Operation parser does its own length arithmetic.",
      "The diff does not add a check that the element also holds EHT_OPER_INFO_MIN_LEN (3) bytes of operation information when EHT_OPER_INFO_PRESENT is set. The material does not show whether such a check already exists elsewhere.",
      "Only the header is changed. The diff adds no test; the commit message cites manual reproduction with ASan."
     ],
     "behavior_changes": [
      "EHT Operation elements with length 1 to 4 bytes now fail the minimum-length check in code that uses this macro, where before they were accepted. The material does not show how failing elements are then handled."
     ],
     "preconditions": [
      "wpa_supplicant parses a received EHT Operation element and reaches get_eht_operation_channel_width() (per the commit message).",
      "The element length is between 1 and 4 inclusive and the EHT_OPER_INFO_PRESENT bit (BIT(0)) is set in the EHT Operation Parameters.",
      "The exact calling paths are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 0ce102474997 in android-security-14.0.0_r29 changes EHT_OPERATION_IE_MIN_LEN from 1 to 5 in src/common/ieee802_11_defs.h. Only whitespace alignment differs from the Android 15 diff.",
      "15": "same fix: commit 234d284145fc in android-security-15.0.0_r17 changes EHT_OPERATION_IE_MIN_LEN from 1 to 5 in src/common/ieee802_11_defs.h.",
      "16": "same fix: commit 6579923889d6 in android-security-16.0.0_r8 makes the same change to EHT_OPERATION_IE_MIN_LEN at a different file offset (hunk at line 2677).",
      "17": "adapted fix: commit 14398e6a6663 in android-security-17.0.0_r1 changes the renamed macro EHT_OPERATION_ELEM_MIN_LEN from 1 to 5. The spec comment was already up to date there, so only one line changes."
     },
     "backport_notes": [
      "Constant change in the header. The only adaptation is the macro name (EHT_OPERATION_IE_MIN_LEN on 14/15/16, EHT_OPERATION_ELEM_MIN_LEN on 17). Confirm that the target tree's get_eht_operation_channel_width() actually checks the element length against this macro; otherwise the header change alone has no effect."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of get_eht_operation_channel_width() and the definition of struct ieee80211_eht_operation are not in the material; the mechanism comes from the commit message.",
      "Which callers reach the vulnerable function is not shown.",
      "Whether the macro has other users whose behavior changes with the new value is not shown.",
      "How elements that fail the check are handled is not shown.",
      "The per-major '4/6 lines of the patched region present' figures are not explained; all four branches are reported as resolved."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49913": {
     "cve": "CVE-2026-49913",
     "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now checks the mmap() result for the client-supplied source heap against MAP_FAILED instead of NULL. It also adds a ScopeGuard that calls munmap() on the mapping on every exit path after the check.",
     "component": "platform/hardware/interfaces, cas/aidl/default (android.hardware.cas AIDL default HAL, DescramblerImpl)",
     "weakness": "Incorrect error-return check on mmap(): MAP_FAILED was treated as a valid pointer, so an invalid pointer was used. The mapping also leaked on the non-error paths.",
     "root_cause": "In cas/aidl/default/DescramblerImpl.cpp, DescramblerImpl::descramble maps the source SharedBuffer heap into srcPtr. Per the code comment, the offset and size are controlled by the client. The failure check was `if (srcPtr == NULL)`. mmap() reports failure with MAP_FAILED, not NULL, so a failed mapping passed the check and execution continued with the invalid pointer MAP_FAILED. The commit message says this led to an arbitrary-address memory access. The commit message also says munmap() cleanup was added to prevent memory leaks, which shows the mapping was not released on later return paths.",
     "fix_analysis": [
      "cas/aidl/default/DescramblerImpl.cpp :: DescramblerImpl::descramble: `if (srcPtr == NULL)` is replaced by `if (srcPtr == MAP_FAILED)`. A failed mmap() now logs 'Failed to map src buffer.' and returns toStatus(BAD_VALUE) before the pointer is used.",
      "Same function: right after the check, the fix adds `auto sg = ::android::base::make_scope_guard([srcPtr, size = srcBuffer.heapBase.size]() { munmap(srcPtr, size); });`. The mapping is now released on every later return path, including the validateRangeForSize failure that follows and the normal path.",
      "The fix adds `#include <android-base/scopeguard.h>` for the guard. It is a single commit per branch, and the diff content is identical on all four branches."
     ],
     "completeness": [
      "Covers the one mmap() failure check for the source buffer in DescramblerImpl::descramble and adds munmap() cleanup from that point on.",
      "The diff only addresses srcPtr. The material does not show how the destination buffer is mapped or checked in this function, so whether a sibling mapping exists and is validated correctly cannot be judged.",
      "The guard calls munmap() with srcBuffer.heapBase.size. The mmap() call itself is outside the shown context, so the material does not confirm that the mapped length equals heapBase.size.",
      "The HIDL CAS default implementations are not touched by this commit. The material does not show whether they contain the same pattern."
     ],
     "behavior_changes": [
      "A failed mmap() of the client heap now returns toStatus(BAD_VALUE) instead of continuing with an invalid pointer.",
      "The source mapping is now unmapped at the end of every descramble() call that passes the MAP_FAILED check. This removes a per-call mapping leak in the HAL process."
     ],
     "preconditions": [
      "A client able to call descramble on the android.hardware.cas AIDL default HAL, with a SharedBuffer for which mmap() fails. Per the code comment, the offset and size are client controlled.",
      "Per index search, callers include MediaDescrambler.descramble (frameworks/base), the native_descramble JNI path and ATSParser::Stream::flushScrambled (frameworks/av). The JNI and ATSParser paths appear HIDL-based, so which framework paths reach the AIDL default HAL is not confirmed. The device must use the default AIDL CAS HAL implementation."
     ],
     "per_major": {
      "14": "Same fix: platform/hardware/interfaces 8858a5521ba4 in android-security-14.0.0_r29. Identical diff content in cas/aidl/default/DescramblerImpl.cpp, hunk @@ -100,10.",
      "15": "Same fix: platform/hardware/interfaces 9bdc5c54c702 in android-security-15.0.0_r17. Identical diff, hunk @@ -100,10.",
      "16": "Same fix: platform/hardware/interfaces 0b6f21543e54 in android-security-16.0.0_r8. Identical diff, hunk @@ -100,10.",
      "17": "Same fix: platform/hardware/interfaces d9f6b94ba0f0 in android-security-17.0.0_r1. Identical diff content, hunk shifted by one line (@@ -101,10)."
     },
     "backport_notes": [
      "Single self-contained commit that needs <android-base/scopeguard.h> (make_scope_guard). The diff has the same hunk on 14, 15 and 16 and a one-line offset on 17. Forks with a customized DescramblerImpl::descramble should find the `srcPtr == NULL` check after the mmap() and make sure no other mmap() result in the function is compared against NULL."
     ],
     "confidence": "high",
     "unknowns": [
      "The mmap() call itself and the handling of the destination buffer are outside the provided code context. The mapped length, and whether a similar check exists for the destination buffer, are not shown.",
      "Whether the HIDL CAS default HAL has the same NULL-vs-MAP_FAILED pattern is not shown in the material.",
      "Whether the default CAS HAL build already links libbase is not shown.",
      "How toStatus(BAD_VALUE) is surfaced to the binder client is not shown.",
      "Which framework paths reach the AIDL (as opposed to HIDL) descrambler is not confirmed.",
      "Which process or SELinux domain hosts the default CAS HAL on affected devices is not shown. The bulletin rates this High EoP."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55290": {
     "cve": "CVE-2026-55290",
     "summary": "ResStringPool::setTo in libandroidfw now rejects string pool chunks whose stringsStart or stylesStart exceed the chunk size regardless of stringCount, and re-derives the styleCount bounds check so the style offset array (styleCount * 4 bytes) must fit both within the data size and before stringsStart, with an overflow guard against SIZE_MAX.",
     "component": "platform/frameworks/base, libs/androidfw (ResourceTypes.cpp, ResStringPool parsing)",
     "weakness": "Missing/insufficient bounds check on header fields leading to out-of-bounds heap read (styleCount and stringsStart not validated when stringCount is zero)",
     "root_cause": "In libs/androidfw/ResourceTypes.cpp ResStringPool::setTo, stringsStart validation was skipped when stringCount was 0 (per the commit message), so stringsStart could be an arbitrary value such as 0xFFFFFFFF. The style section relied on stringsStart as the upper bound for the style offset array. The old check `mHeader->styleCount * sizeof(uint32_t) > (mHeader->stringsStart - styleOffsetsStart)` passed trivially when stringsStart was huge. The other old check, `(mEntryStyles - mHeader) > (int)size`, compared only the start of the style offset array against size, not its end. As a result, a chunk with stringCount == 0, a large styleCount and a huge stringsStart was accepted, which per the commit message led to an OOB heap read in ResStringPool::styleAt.",
     "fix_analysis": [
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo: right after `mSize = mHeader->header.size;` and before mEntries is computed, two new unconditional checks return BAD_TYPE if `mHeader->stringsStart > mSize` or `mHeader->stylesStart > mSize`. This closes the gap where stringsStart was not validated when stringCount == 0.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, inside the style block: the old start-only check `(mEntryStyles - mHeader) > (int)size` is replaced. The code now computes `styleOffsetsStart = mEntryStyles - mHeader` and rejects `styleCount > SIZE_MAX / sizeof(uint32_t)` (overflow guard). It then computes `styleOffsetsSize = styleCount * sizeof(uint32_t)` and rejects the chunk if `styleOffsetsSize > size || styleOffsetsStart > (size - styleOffsetsSize)`, so the whole style offset array must fit inside the data size. The subtraction form avoids overflow.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, same block: a second check rejects the chunk if `styleOffsetsStart > stringsStart || styleOffsetsSize > (stringsStart - styleOffsetsStart)`. This means the style offset array must end at or before stringsStart, and checking the ordering first prevents the subtraction from underflowing. The check is now effective because stringsStart has already been bounded by mSize.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo: two older checks placed after the `mStyles` / `stylesStart >= header.size` check are deleted, because the moved, stricter checks above replace them. These are the numeric_limits max overflow guard and the `styleCount * sizeof(uint32_t) > (stringsStart - styleOffsetsStart)` test. The existing `stylesStart >= header.size` check is kept.",
      "libs/androidfw/tests/ResourceTypes_test.cpp: adds two tests. ResStringPool_StyleCountUnboundedWhenStringCountIsZero uses stringCount 0, styleCount 100 and stringsStart 0xFFFFFFFF, and expects BAD_TYPE. ResStringPool_StyleCountCalculationOverflow uses styleCount 0x40000000 with stringsStart == stylesStart == sizeof(ResStringPool_header), and expects BAD_TYPE."
     ],
     "completeness": [
      "Covers the parsing entry point ResStringPool::setTo. stringsStart and stylesStart are now bounded by mSize for every pool, and the style offset array is bounded by both the data size and stringsStart using overflow-safe arithmetic.",
      "The fix is entirely in setTo; ResStringPool::styleAt is not changed in the diff. Protection relies on setTo rejecting the malformed chunk.",
      "The new checks compare against both `size` (caller-provided) and `mSize` (header.size). validate_chunk is called earlier (shown in context), but its exact guarantee about header.size versus size is not shown.",
      "The fix does not add validation of the individual style offset values in the array or of span data at stylesStart. Whether those are bounded elsewhere is not shown in the material."
     ],
     "behavior_changes": [
      "ResStringPool::setTo now returns BAD_TYPE (with an ALOGW) for any string pool whose stringsStart or stylesStart is greater than header.size, independent of stringCount.",
      "Pools with style entries are rejected if styleCount * 4 bytes starting at mEntryStyles would extend past the data size or past stringsStart.",
      "The overflow guard threshold changes from UINT32_MAX / 4 to SIZE_MAX / 4. The log message for the style-array check also changes: it now uses %zu and prints styleCount instead of the offset."
     ],
     "preconditions": [
      "A malformed chunk of type RES_STRING_POOL_TYPE is parsed with ResStringPool::setTo, either directly or via the ResStringPool constructor shown in context, which calls setTo.",
      "The chunk sets stringCount to 0, a styleCount larger than the number of style offsets actually present, and a stringsStart larger than the chunk. The added test ResStringPool_StyleCountUnboundedWhenStringCountIsZero exercises this case.",
      "ResStringPool::styleAt is then called (the read site named in the commit message)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 1eb9fc572c86 in android-security-14.0.0_r29. Hunks are identical to the Android 15 commit (only line numbers differ), including the two new tests.",
      "15": "same fix: platform/frameworks/base ed4eedc69050 in android-security-15.0.0_r17 (cherry-pick of 968cb0736ca6, Change-Id I3e1e4579fbda8ee89c5fea3945d523c6e5b72dc9).",
      "16": "same fix: platform/frameworks/base 01bff648b538 in android-security-16.0.0_r8. Hunks are identical to the Android 15 commit.",
      "17": "same fix: platform/frameworks/base 7e42d365a0c2 in android-security-17.0.0_r1. Hunks are identical to the Android 15 commit."
     },
     "backport_notes": [
      "This is a single self-contained commit touching only ResStringPool::setTo and its tests, with identical hunks on 14 through 17. The deletion hunk expects the earlier `numeric_limits<...>::max() / sizeof(uint32_t)` and `styleOffsetsStart` checks to be present. Trees without them will need manual adaptation."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of ResStringPool::styleAt (the OOB read site) is not in the material; the read is known only from the commit message.",
      "The stringCount > 0 block that previously validated stringsStart is not shown in the diff.",
      "Which processes parse untrusted string pools (reachability) is not shown; the index results provided are unrelated matches.",
      "Whether validate_chunk guarantees header.size <= size is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55294": {
     "cve": "CVE-2026-55294",
     "summary": "The fix changes ihevcd_get_tu_data_size() in libhevc's decoder to size the tu_map region with num_tu (luma plus chroma TUs) instead of num_luma_tu, so the TU data buffer is large enough for what the initialization code writes into it.",
     "component": "platform/external/libhevc, HEVC decoder (libhevcdec), decoder/ihevcd_utils.c",
     "weakness": "Incorrect buffer size calculation leading to heap buffer overflow (under-allocation)",
     "root_cause": "ihevcd_get_tu_data_size(WORD32 num_luma_samples) computes the total size of the TU data buffer by adding up several regions: a start-index table, a tu_map, a tu_t array, per-TU subblock/scan_idx bytes, and coefficient data. It derives num_luma_tu from num_luma_samples, sets num_chroma_tu to num_luma_tu >> 1, and sets num_tu to their sum. Every region except the start-index table (sized with num_ctb) and the tu_map was sized with num_tu. The tu_map region was sized with num_luma_tu * sizeof(UWORD8). The commit message says the initialization logic uses space for both luma and chroma TUs, so the returned size was smaller than what is later written. That made it possible to write past the end of the heap allocation.",
     "fix_analysis": [
      "decoder/ihevcd_utils.c:ihevcd_get_tu_data_size: the single line `tu_data_size += num_luma_tu * sizeof(UWORD8);` is replaced with `tu_data_size += num_tu * sizeof(UWORD8);`. This adds num_chroma_tu bytes to the tu_map size, so the allocation matches the combined luma and chroma usage.",
      "Nothing changes on the consumer/initialization side. The fix only enlarges the computed size so the existing layout fits. The commit also changes the file mode from 100755 to 100644, which has no functional effect."
     ],
     "completeness": [
      "The fix covers the one region (tu_map) that was sized inconsistently. The other four regions in the same function use num_tu or num_ctb and are unchanged.",
      "The fix relies on the commit message's statement that the layout code uses num_tu tu_map entries. The material does not include that consumer code, so it cannot confirm that every write into tu_map stays within num_tu.",
      "No new bounds check is added on the write side. Correctness depends on the size computation and the consumer staying in sync."
     ],
     "behavior_changes": [
      "The TU data size returned by ihevcd_get_tu_data_size() grows by num_chroma_tu bytes, that is (num_luma_samples / (MIN_TU_SIZE*MIN_TU_SIZE)) >> 1. No new error codes or API changes."
     ],
     "preconditions": [
      "libhevcdec must decode an HEVC bitstream, so that it allocates and fills the TU data buffer sized by ihevcd_get_tu_data_size().",
      "Per index search, libhevcdec is driven by the software HEVC decoder components C2SoftHevcDec (frameworks/av/media/codec2/components/hevc/C2SoftHevcDec.cpp) and SoftHEVC (frameworks/av/media/libstagefright/codecs/hevcdec/SoftHEVC.cpp), and by GoldfishHevcHelper on the emulator. These components pass their input buffers to the decoder as pv_stream_buffer.",
      "The material does not show which bitstream properties make tu_map usage exceed num_luma_tu entries."
     ],
     "per_major": {
      "14": "same fix: platform/external/libhevc d9977ef8294a in android-security-14.0.0_r29, identical one-line hunk in decoder/ihevcd_utils.c (different pre-image blob, same context).",
      "15": "same fix: platform/external/libhevc c43bbf882f97 in android-security-15.0.0_r17, identical one-line hunk.",
      "16": "same fix: platform/external/libhevc 5492b022e4d6 in android-security-16.0.0_r8, identical one-line hunk with the same blob ids as 15.",
      "17": "same fix: platform/external/libhevc 9aa38f2733f4 in android-security-17.0.0_r1, identical one-line hunk."
     },
     "backport_notes": [
      "This is a single, self-contained one-line change in ihevcd_get_tu_data_size(). It should apply cleanly wherever the function still computes num_luma_tu, num_chroma_tu and num_tu as shown. Forks that changed the TU buffer layout should check that their tu_map consumer uses num_tu entries before adopting the same sizing."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the initialization code that writes the tu_map (the consumer the commit message refers to), so the exact overflow write site is not shown.",
      "The commit lists two Cherrypick-From sources but contains only one hunk. The material does not show whether the second upstream commit carried other changes.",
      "The material does not show the input sources (apps, files, other) that reach these decoder components."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58822": {
     "cve": "CVE-2026-58822",
     "summary": "Adds early bitmap-size guards to FreeType's LCD rasterizer functions in src/smooth/ftsmooth.c: ft_smooth_raster_lcd rejects width/3 > 0x7FFF and ft_smooth_raster_lcdv rejects rows/3 > 0x7FFF, both by returning Raster_Overflow. This prevents the short truncation of span x-coordinates that led to a heap underflow write.",
     "component": "platform/external/freetype, smooth (anti-aliased) rasterizer, LCD / LCD_V rendering (src/smooth/ftsmooth.c)",
     "weakness": "Missing bounds check leading to integer truncation and heap out-of-bounds write (heap underflow)",
     "root_cause": "In src/smooth/ftsmooth.c, ft_smooth_raster_lcd sets up direct rendering with clip_box.xMax = bitmap->width and the span callback ft_smooth_lcd_spans. That callback writes at dst = dst_line + spans->x * 3 for each span. FT_Span.x is 16-bit (per the fix comment). ft_smooth_raster_lcd did not bound the bitmap width before rasterizing, so on a sufficiently wide bitmap the span x-coordinate was truncated to short. Per the commit message, this truncation leads to a heap underflow write: dst can point before the bitmap buffer. The commit also adds a height bound to ft_smooth_raster_lcdv for outlines that are 'too high'. The material does not show the exact mechanism in that path. The commit message describes both checks as similar to an existing guard in ft_smooth_raster_overlap.",
     "fix_analysis": [
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcd: before setting up FT_Raster_Params, adds `if ( bitmap->width / 3 > 0x7FFF ) return FT_THROW( Raster_Overflow );`. LCD bitmaps whose per-channel width exceeds the positive range of the 16-bit FT_Span.x are rejected before ft_smooth_lcd_spans writes any span.",
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcdv: adds the analogous `if ( bitmap->rows / 3 > 0x7FFF ) return FT_THROW( Raster_Overflow );` for the vertical-LCD path, which rejects outlines that are too high.",
      "Both checks are modeled on the existing guard in ft_smooth_raster_overlap (per the commit message). The span callback and the rasterizer itself are not changed. The fix only rejects oversized targets early."
     ],
     "completeness": [
      "Covers both LCD paths in ftsmooth.c: ft_smooth_raster_lcd (horizontal, width) and ft_smooth_raster_lcdv (vertical, rows). Per the commit message, ft_smooth_raster_overlap already had its own guard.",
      "The fix is only a size precondition. ft_smooth_lcd_spans still does no per-span bounds checking and relies on the clip box and the new size limit. Any other way for spans->x to exceed the clip box is not addressed by this change.",
      "The non-LCD smooth rendering path is not modified. The material does not show whether it needs a comparable guard.",
      "The lcd check bounds only width and the lcdv check bounds only rows. The material does not show whether the other dimension is constrained elsewhere for these paths."
     ],
     "behavior_changes": [
      "LCD rendering now fails with a Raster_Overflow error when bitmap->width / 3 > 0x7FFF, and LCD_V rendering fails the same way when bitmap->rows / 3 > 0x7FFF. These glyphs used to be rasterized; now the function returns before rendering.",
      "No change for bitmaps within the limit."
     ],
     "preconditions": [
      "The smooth renderer must render a glyph outline through ft_smooth_raster_lcd (LCD) or ft_smooth_raster_lcdv (LCD_V).",
      "The target bitmap must be large enough that bitmap->width / 3 (LCD) or bitmap->rows / 3 (LCD_V) exceeds 0x7FFF. That means an extremely wide or tall glyph outline.",
      "Per index search, on Android 15 FreeType is used by frameworks/rs/rsFont.cpp (FT_New_Memory_Face / FT_New_Face on caller-provided font data), and a hwui unit test uses android::FreeTypeFontMgr through Skia. The material does not show whether any of these callers request LCD render modes."
     ],
     "per_major": {
      "14": "same fix: commit 51c2994abfc5 in android-security-14.0.0_r29 adds the identical two guards. Only the hunk line offsets differ (137/204 vs 141/208). 8/8 patched lines present.",
      "15": "same fix: commit fc6e207660f9 in android-security-15.0.0_r17 (cherry-pick of d6d8fdf7c235, Change-Id If87af623a07ed59d30e31097c5507834500c47d0). 8/8 patched lines present.",
      "16": "same fix: commit 6ca3925a3c97 in android-security-16.0.0_r8. The diff is identical to Android 15 (same blob ids 9b0e8886c..6d4b9150a). 8/8 patched lines present.",
      "17": "same fix: commit 4321c90fc79e in android-security-17.0.0_r1. Identical hunks on a different base file (f0acc1ea4..06a1e1c6f). 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit that touches only src/smooth/ftsmooth.c. The two 3-line guards go at the top of ft_smooth_raster_lcd and ft_smooth_raster_lcdv, right after the local declarations. Google applied the same hunks to Android 14, 15, 16 and 17, with only the line offsets differing."
     ],
     "confidence": "high",
     "unknowns": [
      "The span-emission code in the rasterizer that truncates the x coordinate to short is not in the material. The truncation is known only from the commit message and the fix comment.",
      "How the rows bound in ft_smooth_raster_lcdv relates to the truncation is not shown.",
      "Whether any Android system component actually renders glyphs in LCD or LCD_V mode, and so whether the code is reachable in practice on device, is not shown.",
      "Whether the non-LCD smooth path is exposed to the same truncation is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58839": {
     "cve": "CVE-2026-58839",
     "summary": "Shrinks the stack-allocated read buffer in incfs MountRegistry's forEachLine() from 128 KiB to 32 KiB and adds a compile-time check that the caller's declared maximum line length fits in it, to avoid overrunning the thread stack when parsing mount info.",
     "component": "platform/system/incremental_delivery, incfs (libincfs MountRegistry)",
     "weakness": "Excessive stack allocation (stack exhaustion / stack overflow from an oversized local buffer)",
     "root_cause": "In incfs/MountRegistry.cpp, the static template function forEachLine() declared a local `char buffer[128 * 1024]` on the stack to pread() the mount info file. It is invoked from MountRegistry::Mounts::loadFrom(), which runs from MountRegistry::ensureUpToDate() whenever poll() on the mount info fd returns non-zero (events POLLERR|POLLPRI requested, or poll fails). Per the commit message, a 128 KiB stack frame can overflow the stack when the calling thread is already close to its stack limit. The buffer size was also not tied to the maximum line length the parser actually needs.",
     "fix_analysis": [
      "incfs/MountRegistry.cpp:forEachLine: the template gains a `size_t maxLineLength` parameter; kBufSize is reduced from 128*1024 to 32*1024 and is no longer `static`; a `static_assert(maxLineLength < kBufSize, ...)` makes it a compile error if a caller needs lines longer than the buffer; the buffer is declared `char buffer[kBufSize] __attribute__((uninitialized))`. The pread() loop still reads up to `std::end(buffer) - nextRead` bytes per call.",
      "incfs/MountRegistry.cpp:MountRegistry::Mounts::loadFrom: calls `forEachLine<maxLineSize>(fd, ...)` with `constexpr auto maxLineSize = 3 * PATH_MAX + 1024` (comment: each line holds 3 paths plus shorter fields), which satisfies the static_assert against the 32 KiB buffer."
     ],
     "completeness": [
      "The fix reduces, but does not remove, the large stack allocation: forEachLine() still places a 32 KiB buffer on the stack of whichever thread calls ensureUpToDate()/loadFrom(). The static_assert message in the code notes a rewrite with a vector would be needed if the line size requirement grows.",
      "The static_assert is a compile-time guard on the declared maxLineLength only; the shown material does not include the part of the loop that handles a runtime line longer than the buffer, so whether such input is handled safely is not visible here.",
      "The diff only updates the forEachLine() call in loadFrom(); whether other call sites exist is not shown in the material (any other instantiation would fail to compile without the new template argument)."
     ],
     "behavior_changes": [
      "Stack usage of the mount-info parsing path drops from roughly 128 KiB to 32 KiB per invocation.",
      "pread() is now issued with at most 32 KiB of buffer space per call, so large mount info files take more read iterations.",
      "Builds fail (static_assert) if a caller instantiates forEachLine with a maxLineLength of 32 KiB or more.",
      "The read buffer is explicitly marked uninitialized, so it is not zero-filled even when automatic variable initialization is enabled by the toolchain."
     ],
     "preconditions": [
      "MountRegistry::ensureUpToDate() must run: it is called from MountRegistry::copyMounts() and MountRegistry::reload(), and it reloads via Mounts::loadFrom() when poll() on the mount info fd (mMountInfo) returns non-zero or fails.",
      "The calling thread must already be near its stack limit for the 128 KiB frame to overflow (per the commit message); which process/thread and how an attacker influences this is not shown in the material.",
      "libincfs appears to be used by frameworks/base/services/incremental/IncrementalService.cpp per index search (IncrementalService runs inside system_server (general platform knowledge)), but the exact triggering path for this CVE is not shown."
     ],
     "per_major": {
      "14": "same fix: identical diff (b6d06e7ccfc2, same blob indices 376eaf2..eaf97dc) applied in android-security-14.0.0_r29.",
      "15": "same fix: 877702e09486 in android-security-15.0.0_r17, cherry-picked with Merged-In I8ac5de734544efff523b2014a7d11c9f9beb9e4a.",
      "16": "same fix: identical diff (67b14f0e00af) in android-security-16.0.0_r8.",
      "17": "not affected per the bulletin (not listed); 6/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit touching only incfs/MountRegistry.cpp; the identical diff was applied to 14, 15 and 16, so it should apply where forEachLine() still has the `static constexpr auto kBufSize = 128 * 1024; char buffer[kBufSize];` form. Requires a compiler supporting `__attribute__((uninitialized))` (clang) (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which process/thread and which caller sequence leads to the near-exhausted stack described in the commit message, and how an unprivileged actor influences it (bulletin only says High EoP).",
      "How forEachLine() handles a runtime line longer than the buffer (the remainder of the loop body is not in the material).",
      "Whether other forEachLine() call sites exist in the file.",
      "Why the buffer was marked __attribute__((uninitialized)).",
      "Whether the 32 KiB buffer is sufficient on all affected thread stack configurations; the commit provides no analysis beyond '32k should be enough'.",
      "Whether the full fix is present on Android 17 (only 6/8 lines of the patched region are reported)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58874": {
     "cve": "CVE-2026-58874",
     "summary": "Adds the missing subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony), and makes TelephonyPermissions.checkSubscriptionAssociatedWithUser tolerate a null destination address (frameworks/base).",
     "component": "platform/frameworks/opt/telephony (SmsController, IccSmsInterfaceManager) and platform/frameworks/base (telephony/common TelephonyPermissions)",
     "weakness": "Missing authorization check (caller user not verified against the subscription used to send a stored SMS)",
     "root_cause": "SmsController.sendStoredText and SmsController.sendStoredMultipartText checked the caller's package and then passed the request to IccSmsInterfaceManager for the requested subId. Unlike sendTextForSubscriber (whose tests in SmsControllerTest exercise isSubscriptionAssociatedWithUser, per index search), these two entry points never called TelephonyPermissions.checkSubscriptionAssociatedWithUser. A caller running as a user not associated with the subscription could therefore still have a stored message sent over that subscription. Separately, the 4-arg TelephonyPermissions.checkSubscriptionAssociatedWithUser declared destAddr @NonNull and passed it straight to tm.isEmergencyNumber. That does not suit the new caller, which looks the address up from a message URI and may get null.",
     "fix_analysis": [
      "frameworks/base, telephony/common/com/android/internal/telephony/TelephonyPermissions.java:checkSubscriptionAssociatedWithUser(Context,int,UserHandle,String): the destAddr annotation changes from @NonNull to @Nullable, and the emergency-number short-circuit becomes 'tm != null && destAddr != null && tm.isEmergencyNumber(destAddr)'. With a null address the emergency bypass is skipped and the method falls through to the 3-arg checkSubscriptionAssociatedWithUser(context, subId, callerUserHandle).",
      "frameworks/opt/telephony, src/java/com/android/internal/telephony/IccSmsInterfaceManager.java:loadTextAndAddress: visibility changes from private to public so SmsController can get the stored message's destination address (index 1 of the returned array) from the message URI. The query itself is unchanged and runs under cleared calling identity.",
      "frameworks/opt/telephony, src/java/com/android/internal/telephony/SmsController.java: a new private helper getDestAddress(IccSmsInterfaceManager, Uri) returns null if iccSmsIntMgr is null or loadTextAndAddress returns null. Otherwise it returns textAndAddress[1].",
      "frameworks/opt/telephony, SmsController.java:sendStoredText: after the existing caller log line, it reads Binder.getCallingUserHandle() and Binder.getCallingUid(), gets destAddr via getDestAddress, and calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, callingUser, destAddr). On false it calls TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, uid, callingPkg), logs, sends SmsManager.RESULT_USER_NOT_ALLOWED via sendErrorInPendingIntent(sentIntent, ...), and returns before touching iccSmsIntMgr.",
      "frameworks/opt/telephony, SmsController.java:sendStoredMultipartText: the same gate, using sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED).",
      "frameworks/opt/telephony, tests/telephonytests/.../SmsControllerTest.java: four new tests (sendStoredText_/sendStoredMultipartText_ associatedUser_sendsSms and notAssociatedUser_fails). They mock loadTextAndAddress and SubscriptionManager.isSubscriptionAssociatedWithUser and verify whether the IccSmsInterfaceManager call is made."
     ],
     "completeness": [
      "Both stored-message Binder entry points in SmsController (sendStoredText and sendStoredMultipartText) are gated. The gate runs before the iccSmsIntMgr null handling, so a non-associated caller now gets RESULT_USER_NOT_ALLOWED even when iccSmsIntMgr is null.",
      "The emergency-number exemption stays: if the stored message's address is an emergency number, TelephonyPermissions.checkSubscriptionAssociatedWithUser skips the association check by design.",
      "If getDestAddress returns null (no IccSmsInterfaceManager for subId, or the message URI returns no row), the emergency exemption is skipped and the plain 3-arg association check decides. The null case does not bypass the check.",
      "In the pre-fix frameworks/base code, the tm.isEmergencyNumber call was already inside try/catch(Exception), so an exception for a null destAddr would fall through to the 3-arg check. The material does not show how isEmergencyNumber behaves with null, so the base change should be treated as a required companion of the opt/telephony change.",
      "This fix does not touch other SmsController send paths (e.g. sendTextForSubscriber). Per index search, they already had association checks covered by tests."
     ],
     "behavior_changes": [
      "sendStoredText / sendStoredMultipartText now fail with SmsManager.RESULT_USER_NOT_ALLOWED, delivered through the sentIntent(s), when the calling user is not associated with the subscription and the address is not an emergency number.",
      "TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate may now be shown from these two paths.",
      "Each stored send now runs one extra ContentResolver query of the message URI (BODY, ADDRESS), made by SmsController under cleared identity before the existing IccSmsInterfaceManager processing.",
      "IccSmsInterfaceManager.loadTextAndAddress becomes a public method.",
      "TelephonyPermissions.checkSubscriptionAssociatedWithUser(..., String destAddr) accepts null without entering the emergency path."
     ],
     "preconditions": [
      "The caller reaches ISms.sendStoredText or ISms.sendStoredMultipartText in SmsController with a callingPkg that passes the existing package check, and with a subId whose subscription is not associated with the caller's user (multi-user or managed-profile setup).",
      "The stored message's destination address is not an emergency number. Otherwise the association check is skipped by design.",
      "The material does not show whether untrusted apps can reach these paths (permission requirements on the ISms interface)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/opt/telephony 2c10fa519945 in android-security-14.0.0_r29. It makes the same SmsController/IccSmsInterfaceManager/SmsControllerTest changes and also adds 'import android.os.UserHandle'. 7/8 lines of the patched region present. The material does not show whether the frameworks/base destAddr nullable change is on 14.",
      "15": "same fix: platform/frameworks/opt/telephony 3ed5e1fdd5da plus platform/frameworks/base d09bfc5ee8b4 in android-security-15.0.0_r17; 7/8 lines of the patched region present.",
      "16": "adapted fix: platform/frameworks/opt/telephony b77a21c16113 in android-security-16.0.0_r8. The 16 methods already had callingUser and pass callingUser.getIdentifier() to IccSmsInterfaceManager, so the patch hoists 'int uid' and reuses it in the SecurityException message. The tests use the 7-arg sendStoredText/sendStoredMultipartText signatures. The commit also adds a stray file, src/java/com/android/internal/telephony/SmsController_fixed_methods.txt, containing the patched methods. 7/8 lines present. The material does not show the frameworks/base change on 16.",
      "17": "not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 6/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Take the opt/telephony and frameworks/base commits together, because the new SmsController code can pass a null destAddr to the 4-arg check. Check that the target branch has the 4-arg TelephonyPermissions.checkSubscriptionAssociatedWithUser overload, TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate and SmsManager.RESULT_USER_NOT_ALLOWED.",
      "On branches where IccSmsInterfaceManager.sendStoredText takes a callingUserId (the Android 16 shape), follow the 16 variant. Drop the stray SmsController_fixed_methods.txt file if cherry-picking from 16."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the 3-arg TelephonyPermissions.checkSubscriptionAssociatedWithUser (or the code around it) honours INTERACT_ACROSS_USERS_FULL on these paths. The material only shows such a bypass being tested for sendTextForSubscriber.",
      "Which permissions guard ISms.sendStoredText/sendStoredMultipartText for third-party callers.",
      "How TelephonyManager.isEmergencyNumber behaves when passed null.",
      "Whether the frameworks/base destAddr change was applied to the 14 and 16 security branches.",
      "The concrete security impact behind the High EoP rating, beyond the missing check itself."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "16": {
   "counts": {
    "announced": 81,
    "certain": 79,
    "probable": 0,
    "not_published": 2,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2026-27280",
     "osv_id": "ASB-A-491543354",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Bumps external/dng_sdk from DNG SDK 1.7.1 build 2471 to build 2502. In OptimizeOrder (dng_pixel_buffer.cpp) the 32-bit pointer-rebase arithmetic is replaced with 64-bit arithmetic through SafeInt64Mult, step negation is checked, and a zero-count early return is added. dng_render_task::ProcessArea (dng_render.cpp) now requires the src and dst areas to be the same size and addresses its temporary mask and RGBTables buffers in source coordinates.",
      "confidence": "medium",
      "backport_notes": [
       "The change is a vendor drop of DNG SDK 1.7.1 build 2502 on top of build 2471, applied with an identical diff on 14, 15 and 16. It depends on SafeInt64Mult, SafeInt32Sub, ConvertUint32ToInt32, ThrowOverflow and DNG_REQUIRE already being available in the tree. The material does not show where these are defined.",
       "The AspectRatio -> BaseAspectRatio rename in dng_negative.h changes the API. Any out-of-tree callers of dng_negative::AspectRatio must be updated when this commit is taken."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 11,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/dng_sdk",
       "sha": "c4ffcdb3285d2086d728609a68d934576dac2261",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/dng_sdk"
     ]
    },
    {
     "cve": "CVE-2026-28590",
     "osv_id": "ASB-A-483126552",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The fix makes the SMP layer reject LE (and SMP-over-BR/EDR) pairing commands whose advertised maximum encryption key size is below the system minimum (7 octets by default). To do this it exports the existing BR/EDR minimum key size helper as btm_sec_get_min_enc_key_size() and calls it from smp_proc_pair_cmd and smp_br_process_pairing_command.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. On 14 and 15, btm_sec_get_min_enc_key_size() is a constant 7. The essential change is the two checks in smp_act.cc plus making the helper and the constants visible from btm_sec.h. On 14 the BR/EDR key size checks are in system/stack/btu/btu_hcif.cc, which needs an include of stack/btm/btm_sec.h.",
       "Each branch's fix also adds a btm_sec_get_min_enc_key_size mock (a mockcify struct on 15 and 16, a plain function on 14). Carry it along with the change."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "8b61335e49847e62aaa30bdacd2fcf80a1c411d8",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28604",
     "osv_id": "ASB-A-499053500",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport* across the blocking handshake. The thread now captures a weak-pointer map ID and the connection object, performs the handshake off-thread, then re-validates the transport on the fdevent looper via transport_from_callback_arg. The fix also adds a 3-second socket timeout to the daemon-side FdConnection::DoTlsHandshake.",
      "confidence": "high",
      "backport_notes": [
       "On Android 16, apply 1c97946b5ca6 (fdevent_check_looper + extract in transport_to_callback_arg/transport_from_callback_arg) before f487f473c82d to match its diff context. On 14/15, Google's UAF-fix commits sit on a different base (auth.cpp 0be3f995) whose context line is LOG(INFO) rather than VLOG(AUTH), so applying the 16 diff directly there may hit a context mismatch on that line. transport_to_callback_arg, transport_from_callback_arg and fdevent_run_on_looper must already exist in the target tree.",
       "The 17 version adds CHECK_NOT_LOOPER_THREAD() in FdConnection::DoTlsHandshake. The 14 to 16 versions do not include it, so leave it out on branches that lack that macro."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/adb",
       "sha": "1c97946b5ca6ddf4c1eb756ed82a92dde8934901",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/adb",
       "sha": "f487f473c82d5bfd90f26e7d7ee9cea0cafde353",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/modules/adb"
     ]
    },
    {
     "cve": "CVE-2026-28618",
     "osv_id": "ASB-A-492492028",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a check in libopenapv's dec_frm_prepare that returns OAPV_ERR_INVALID_ARGUMENT when the plane-0 width or height of the output image buffer supplied by the caller is smaller than the frame dimensions in the Frame PBU header, preventing a heap buffer overflow.",
      "confidence": "high",
      "backport_notes": [
       "The change is a self-contained 4-line insertion at the top of dec_frm_prepare in src/oapv.c with no dependencies. It is identical on 16 and 17; only the hunk offset differs. Forks with a modified dec_frm_prepare should place the check before imgb_addref so that the early return does not leak a reference."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libopenapv",
       "sha": "b40bceb14205deec830af9c538625fbf70114b9f",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libopenapv"
     ]
    },
    {
     "cve": "CVE-2026-28639",
     "osv_id": "ASB-A-508389703",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the libnfc-nci MIFARE Classic NDEF reader, rw_mfc_handle_read_op now sets failed = true in the branch that logs the SafetyNet length-inflation event 178725766, so the operation is marked failed instead of only being logged (commit: 'Terminate MFC NDEF read on length inflation attempt').",
      "confidence": "medium",
      "backport_notes": [
       "The file is in platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15 and in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) on Android 16/17. On every branch, the hunk is anchored on the existing android_errorWriteLog(0x534e4554, \"178725766\") else branch inside rw_mfc_handle_read_op and is a one-line addition.",
       "On forks, find the branch to patch by the 178725766 log call inside rw_mfc_handle_read_op."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "ca73224fb2c253c778262264cdb6adf32f70df8f",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28653",
     "osv_id": "ASB-A-508389055",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a pre-loop range check in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (libnfc-nci rw_t3t.cc). The check fails the command with NFC_STATUS_FAILED when first_block + block_count would exceed UINT16_MAX, which stops the 16-bit block_id loop counter from wrapping and writing block descriptors past the end of the command buffer.",
      "confidence": "medium",
      "backport_notes": [
       "The patch is two self-contained hunks in rw_t3t.cc and is the same on 14 to 17 apart from offsets. On 14/15 the file is src/nfc/tags/rw_t3t.cc in platform/system/nfc; on 16/17 it is libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc.",
       "When backporting, consider freeing p_cmd_buf (GKI_freebuf) before returning NFC_STATUS_FAILED, as the r8 update_cmd code does. The cherry-picked commit returns without freeing the command buffer."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "4648c160b82520be2ee17139515d9e3ef4e5bc4b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28662",
     "osv_id": "ASB-A-495652882",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a bounds check in p2p_process_prov_disc_bootstrap_req (src/p2p/p2p_pd.c) that rejects a P2P2 PBMA comeback cookie longer than the fixed-size dev->bootstrap_params->cookie buffer, and enables the UBSan \"bounds\" sanitizer on a wpa_supplicant cc_library in wpa_supplicant/Android.bp.",
      "confidence": "high",
      "backport_notes": [
       "The p2p_pd.c hunk is self-contained. It only requires that the target tree has p2p_process_prov_disc_bootstrap_req with the PBMA comeback branch and a fixed-size cookie array in bootstrap_params.",
       "The Android.bp hunk targets the cuttlefish-section cc_library that contains src/pasn/*.c. In forks with a different Android.bp layout, add the \"bounds\" entry to the equivalent sanitize.misc_undefined list, or leave it out, since it is hardening and not the fix itself."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "3f792f7b7afa4a4e5963733c1591e5f25b0f8481",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref b8d36c897dde",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/wpa_supplicant_8"
     ]
    },
    {
     "cve": "CVE-2026-33636",
     "osv_id": "ASB-A-496616027",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restricts the NEON palette-expansion loops in arm/palette_neon_intrinsics.c to full pixel chunks only, drops the post-loop count correction, and restores the destination pointer before handing the remaining tail pixels to the scalar fallback.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching only arm/palette_neon_intrinsics.c. It should apply readily where the two NEON functions still contain the `i < row_width` loop and the `if (i != row_width)` correction (the Android 17 version differs only in line offsets). Check that the scalar fallback in pngrtran.c expects the un-pre-adjusted dp, because the handoff pointer value changes with this patch."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libpng",
       "sha": "0b9147198ed62548a59b4b427c8b5d512be31249",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libpng"
     ]
    },
    {
     "cve": "CVE-2026-45515",
     "osv_id": "ASB-A-503554165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Corrects the frame_size argument passed to opus_decode() in the A2DP Opus decoder from a byte count to the number of samples per channel the output buffer can hold, adds a channel-count sanity check, and removes a loop that re-decoded the same packet once per frame.",
      "confidence": "high",
      "backport_notes": [
       "The fix depends on A2DP_OPUS_CODEC_OUTPUT_CHS and on the decoder being created as stereo, so check both in the target tree before applying. On Android 14 the logging uses LOG_ERROR/LOG_INFO with %d formats instead of log::error/log::info.",
       "The new unit test calls opus_encoder_create/opus_encode, so net_test_stack_a2dp_codecs_native must link libopus. The diff includes no Android.bp change, so check the test's dependencies on older branches."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "5eefb7cb0b0c73a98229f18b8228ae16c544663c",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-45531",
     "osv_id": "ASB-A-508645975",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens fsck.exfat boot-region validation in read_boot_region against integer overflow/underflow from attacker-controlled vol_length, clu_offset and clu_count fields, and widens DIV_ROUND_UP to 64-bit arithmetic.",
      "confidence": "medium",
      "backport_notes": [
       "The functional fix (c0e2d5cc6945: fsck/fsck.c, include/libexfat.h, test image) does not depend on the host_supported build commit. The build commit is only needed to build exfatprogs for host so its tests can run.",
       "On branches where exfat_err calls in read_boot_region are not gated by `if (verbose)` (as in the Android 14 variant 54cf500d4abd), drop the verbose guards."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/exfatprogs",
       "sha": "9b79c19de5cfcf09ae40306aa967e99c9398dbbf",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/external/exfatprogs",
       "sha": "c0e2d5cc6945c44781abf764b425cf05cd5c0d8d",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/external/exfatprogs"
     ]
    },
    {
     "cve": "CVE-2026-49879",
     "osv_id": "ASB-A-503544522",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In libnfc-nci Type 3 Tag NDEF handling, rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd now compute the block range bound and loop in uint32_t (block_id widened from uint16_t), the range check becomes first_block + count > 0x10000, and the command buffer is freed with GKI_freebuf on that error path.",
      "confidence": "medium",
      "backport_notes": [
       "The patch presupposes the earlier bounds check tagged android_errorWriteLog(0x534e4554, \"508389055\") already exists in both functions; on trees without it, add the full check (with GKI_freebuf) together with the uint32_t changes. The file lives at src/nfc/tags/rw_t3t.cc in platform/system/nfc for 14/15 and at libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc for 16/17; the hunks apply unchanged apart from path and line offsets."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "92d884176dcd6c93ac0fd48b97ef24751128ad1d",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-49882",
     "osv_id": "ASB-A-503553866",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In libnfc-nci rw_mfc.cc rw_mfc_handle_read_op, the fix saves ndef_length and ndef_start_pos before rw_nfc_decodeTlv on the first block read, treats a decoded ndef_length larger than the saved value as a failure, and rolls both fields back to the saved values so an inflated length no longer stays in the control block.",
      "confidence": "medium",
      "backport_notes": [
       "The fix is a single self-contained change in rw_mfc_handle_read_op with no new dependencies. The file is src/nfc/tags/rw_mfc.cc in platform/system/nfc for Android 14/15 and libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc for 16/17. When porting, only the verbose-log macro needs to match the branch."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "b79e5fd8cf140eb95b1cae6b52d35ee81eb2543e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-49884",
     "osv_id": "ASB-A-503557185",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (libnfc-nci rw_mfc.cc), the fix snapshots ndef_length and ndef_start_pos before rw_nfc_decodeTlv on the first Mifare Classic NDEF block read. A decoded ndef_length larger than the previous value is now treated as a decode failure, and both fields are rolled back so the inflated length does not stay in the control block.",
      "confidence": "medium",
      "backport_notes": [
       "A single self-contained hunk in rw_mfc_handle_read_op. On Android 14/15 the file lives in platform/system/nfc (src/nfc/tags/rw_mfc.cc) rather than packages/modules/Nfc/libnfc-nci, and the Android 14 variant uses DLOG_IF(INFO, nfc_debug_enabled) for the log line. The per-major facts show 7/8 patched-region lines present in each latest tag; the material does not explain the missing line."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "b79e5fd8cf140eb95b1cae6b52d35ee81eb2543e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-49919",
     "osv_id": "ASB-A-503556301",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In FreeType's COLR layer blending (src/sfnt/ttcolr.c, tt_face_colr_blend_layer), the scratch bitmap buffer allocation changes from FT_ALLOC(buf, rows * pitch) to FT_ALLOC_MULT(buf, rows, pitch), so an overflowing rows*pitch product is now detected in the allocator instead of producing an undersized buffer.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained change, applied identically on 14 through 17 with no other commit listed. If the local `size` variable stays declared but unused, an unused-variable warning may need handling, depending on the branch's warning flags."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "6770ef4d9fafaf3106068a6eb46f05362eed4b9b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-49921",
     "osv_id": "ASB-A-503554490",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds SDP_MAX_ATTR_LEN bounds enforcement across the Bluetooth SDP server: sdpu_build_partial_attrib_entry now sizes its serialization buffer dynamically and rejects oversized attributes and invalid continuation offsets, process_service_attr_req/process_service_search_attr_req reject attributes with payload above SDP_MAX_ATTR_LEN, and SDP_AddAttributeToRecord caps attribute length at SDP_MAX_ATTR_LEN on insertion.",
      "confidence": "high",
      "backport_notes": [
       "For branches that still have the `attr_len >= SDP_MAX_ATTR_LEN` rejection inside the 'add partially' branches of process_service_attr_req / process_service_search_attr_req, follow the Android 14 variant: remove those checks and add the p_attr->len > SDP_MAX_ATTR_LEN check after sdpu_get_attrib_entry_len. Otherwise a 400-byte payload (serialized length above 400) would be rejected, and the new write_read_max_attr_len_slicing test expects it to be served.",
       "sdpu_build_partial_attrib_entry now calls osi_free and allocates a variable size. Unit test fixtures that use test::mock::osi_allocator (as on Android 14) need osi_malloc/osi_free bodies set, as the 14 commit does."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "947e8bbe7dc6fedb2147bdd5a1df3379eb374757",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-49932",
     "osv_id": "ASB-A-513581684",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens PduParser.parseParts in frameworks/base telephony common MMS code by bounding the multipart/alternative recursion depth to 30, rejecting part dataLength values larger than the remaining stream bytes, and verifying a nested child body is non-null and non-empty before taking its first part.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the PduParser.java hunks are textually identical on Android 14 through 17 (Android 17 has a different pre-image blob). The new PduParserTest is a new file; whether its test target exists in a given fork is not shown.",
       "Forks that carry their own copy of the mmslib PduParser (for example packages/apps/Messaging/src/com/android/messaging/mmslib/pdu/PduParser.java, per index search) do not receive this fix automatically and would need to be reviewed separately."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a28fb127a57b3a832c8a6c70e6f7e44b130cd5a8",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55256",
     "osv_id": "ASB-A-513581389",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds null checks in PduParser.parsePartHeaders so that a null result from parseWapString for the Content-Disposition token-text, the filename parameter, or a text header name/value is no longer passed to PduPart setters or to new String(), preventing NullPointerExceptions while parsing an MMS PDU.",
      "confidence": "high",
      "backport_notes": [
       "The frameworks/base and development commits are independent and make the same three changes. The frameworks/base copy (telephony/common) is the shipping parser; the development copy only changes the ApiDemos sample."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/development",
       "sha": "8b9e6db92ce87408f5f406af903cdaff7aa24df6",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "01f51483eb2fdf207077e5f46044f674fa0d2b26",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/development",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55273",
     "osv_id": "ASB-A-524767048",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "aapt2's AnnotationProcessor now rewrites Javadoc terminators (\"*/\") and Java Unicode escape introducers (\"\\u\") in resource XML comments before emitting them into the Javadoc blocks of generated R.java, so a comment can no longer close the Javadoc block and inject Java source.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It adds only a static struct and function plus one call in AppendCommentLine, with no dependency on sAnnotationRules, and it applied with only context differences to the 16 tree (3 rules) and the 17 tree (4 rules). The new test uses AppendComment, StringOutputStream and Printer, which the existing test file already uses."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "74f5e6fa6a20b989c859887623269f66c258c114",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55285",
     "osv_id": "ASB-A-522374659",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a range check on the channel number the Secure Element returns in the MANAGE CHANNEL OPEN response, before that number is used as an index into mOpenedChannels. The check is added to the ten openLogicalChannel implementations the commit touches in the NXP secure_element HAL (pn8x 1.0/1.1, plus snxxx 1.0/1.1/1.2/aidl SecureElement and VirtualISO). The fix also makes pn8x closeChannel return immediately on an invalid channel number.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single self-contained commit; each openLogicalChannel hunk wraps four existing lines in the new check. If your tree's snxxx mOpenedChannels is sized differently, use the actual array capacity as the bound. The material shows the resize only for snxxx 1.2 and aidl."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/nxp/secure_element",
       "sha": "466c2e78559e61ca2206bcc07bbb44cab34b25a8",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/nxp/secure_element"
     ]
    },
    {
     "cve": "CVE-2025-48564",
     "osv_id": "ASB-A-403565650",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that same string to both IPackageManager.canForwardTo and a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload, sanitizes the received intent's selector before startActivityAsCaller, and keys the privileged skip-user-consent check on getLaunchedFromPackage() instead of getCallingPackage().",
      "confidence": "high",
      "backport_notes": [
       "Order matters: the frameworks/base commit must land first, because the IntentResolver module commit calls the new PackageManager.resolveActivityAsUser(Intent, String, int, int) overload. The IntentResolver copy of IntentForwarderActivity is patched by its own commit, separate from the frameworks/base copy.",
       "The 16.0.0_r8 / 17.0.0_r1 follow-up (Injector.getExecutorService/isDeviceProvisioned, test fixes) is not needed for the security fix, but it corrects the backwards assertNull assertions added in the r3 test and re-enables IntentForwarderActivityTest."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "28579dff4305f764302d85f95509671eafbf62ac",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "4e6cf5285d0b1725fb9141e810050cfdb3fb42fd",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "71d6dc10952832ff9f25430b3284fe6735cd375e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "7d8be5eb675389f731cabdd89f8e98f709d4662b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "0b02866fcf8f2565be5e0685fb43af0edd3ee74a",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48565",
     "osv_id": "ASB-A-407763772",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the intent's MIME type once and passes that same string to both the canForwardTo check and the target-user activity resolution (via a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload), uses getLaunchedFromPackage() for the privileged skip-consent check, and sanitizes the intent selector (package, component, nested selector) alongside the main intent.",
      "confidence": "high",
      "backport_notes": [
       "Apply in order: 28579dff4305 (adds the resolvedType overloads, changes the canForward signature, adds the selector sanitize calls), then 71d6dc109528 (Injector.getExecutorService/isDeviceProvisioned, test fixes), then 7d8be5eb6753. The last one touches only IntentForwarderActivity.java, applies on top of the state left by 71d6dc109528, and removes the selector sanitize lines added by the first commit. Any PackageManager subclass or Injector implementation in the tree must implement the new resolveActivityAsUser(Intent, String, ...) overloads; otherwise PackageManager subclasses hit the default UnsupportedOperationException.",
       "If the device ships the IntentResolver module copy of IntentForwarderActivity (packages/modules/IntentResolver, per index search), check that copy separately. It already has the resolvedType parameter, but the material does not show whether its sanitizeIntent() clears the selector's package, component and nested selector."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "28579dff4305f764302d85f95509671eafbf62ac",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "71d6dc10952832ff9f25430b3284fe6735cd375e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "7d8be5eb675389f731cabdd89f8e98f709d4662b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "0b02866fcf8f2565be5e0685fb43af0edd3ee74a",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 4,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2025-48566",
     "osv_id": "ASB-A-397216638",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the incoming intent's MIME type once and passes that fixed string through canForward/canForwardTo and a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload, instead of re-deriving the type from the intent at each step; it also uses getLaunchedFromPackage() for the skip-user-confirmation privilege check and sanitizes the received intent's selector alongside the received intent before relaunch.",
      "confidence": "high",
      "backport_notes": [
       "Apply the PackageManager and ApplicationPackageManager overloads first; IntentForwarderActivity and AbstractMultiProfilePagerAdapter depend on the new signatures and will not compile without them. Any other implementors of IntentForwarderActivity.Injector or callers of the static canForward must be updated. The follow-up commit is not needed for production logic but is needed to make IntentForwarderActivityTest pass and to re-enable it.",
       "The IntentResolver module copy (packages/modules/IntentResolver/.../IntentForwarderActivity.java) already has canForward/canForwardInner taking String resolvedType per index search; if a fork ships a modified module, verify it too (general platform knowledge that IntentResolver is a separate module)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r3",
      "date": "2025-12-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "28579dff4305f764302d85f95509671eafbf62ac",
       "tag": "android-security-16.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "71d6dc10952832ff9f25430b3284fe6735cd375e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 274,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/modules/IntentResolver"
     ]
    },
    {
     "cve": "CVE-2026-0008",
     "osv_id": "ASB-A-433252423",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "FaceEnroll in Settings now strips the MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE extra (alongside Intent.EXTRA_USER_ID) from the forwarded intent when the activity was launched by a package other than Settings itself.",
      "confidence": "high",
      "backport_notes": [
       "The functional change is one line plus one import; the rest of the FaceEnroll.kt hunk is formatting and can be dropped if it conflicts. It requires MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE and the existing launchedFromProvider / removeExtra(EXTRA_USER_ID) block to be present; Android 14 and 15 do not have FaceEnroll.kt at all."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "059c9678eebeefb97cb9cc075d526df546fe4b84",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 183,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-0010",
     "osv_id": "ASB-A-379695596",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces a fixed 16-byte stack buffer filled by sprintf(\"%lu\", (unsigned long)fd) with String8::format(\"%d\", fd) when BnDrmManagerService::onTransact stores the \"FileDescriptorKey\" value of a DrmInfoRequest in drmserver.",
      "confidence": "medium",
      "backport_notes": [
       "Single-hunk, self-contained change. Google merged the identical hunk on 14, 15 and 16, though the Android 14 pre-image blob differs from the one on 15/16. If the file has drifted, check that the hunk lands in the FileDescriptorKey branch of the DrmInfoRequest case and not in PROCESS_DRM_INFO."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r4",
      "date": "2026-03-02",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "ebdbf918dd87127eaeca15336c33e1d3c1ae7dd6",
       "tag": "android-security-16.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 183,
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-0054",
     "osv_id": "ASB-A-467684755",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a caller check to the binder exposed by SystemUI's WalletContextualLocationsService. Every incoming transaction is now rejected with a SecurityException unless the calling UID owns a privileged package that holds the android.app.role.SYSTEM_UI_INTELLIGENCE role.",
      "confidence": "high",
      "backport_notes": [
       "The change is one Kotlin file. On branches with the older binder formatting (as in Android 14), or with different imports, apply the logic rather than the hunk verbatim. STS tests are in a separate commit (I4c59ae71f66035d2aeeb9b9e4dd13d7911e5fae5) that is not in the material.",
       "After this fix, forks with no SYSTEM_UI_INTELLIGENCE role holder, or whose intended caller is not a privileged app, will have that caller rejected. Check that the intended caller meets both conditions before backporting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r7",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r7",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a8f3c0ccb472eb9c1b553ce832d374bae9d5a8aa",
       "tag": "android-security-16.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 92,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0065",
     "osv_id": "ASB-A-464526458",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The \"bound by foreground UID\" background activity launch (BAL) exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned window (new ActivityTaskManagerService.hasActiveVisibleNotPinnedWindow), while foreground-service start checks keep using the original any-visible-window predicate.",
      "confidence": "high",
      "backport_notes": [
       "The patch calls mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity(uid) without adding it. None of the shown 14/15/16 diffs add it, so confirm it exists in the target tree. The constructor signature change of BackgroundLaunchProcessController means every instantiation must be updated (WindowProcessController and the wmtests test class).",
       "On 16, apply 66c209d1f65f first, then the test-only 2f86ea93fcdd. On 14 and 15 the condition has a different shape (isCheckingForFgsStart is a direct parameter, there is no BalCheckConfiguration), and the 15 variant also deletes a flag check. Cherry-pick the branch-specific commits rather than the 16 one."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r7",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "66c209d1f65f07c06907b33b352c5409520b61c5",
       "tag": "android-security-16.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "2f86ea93fcdda3cbc23619d4dfd7cba1b4eb031a",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 92,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-0084",
     "osv_id": "ASB-A-473814675",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes the NFC HostEmulationManager unbind non-preferred HCE service connections right away in two cases, instead of keeping the binding alive until the next NFC transaction: when the preferred service is unset (the app leaves the foreground), and when the regular connection's binding dies.",
      "confidence": "medium",
      "backport_notes": [
       "Apply a90c62ca6e75 first: it adds unbindServiceIfNeededLocked(boolean force) and moves unbindInactiveServicesLocked() to the class level. 6bc72fe8b34e is a one-line change that depends on that overload. Both rely on the multi-binding infrastructure (mComponentNameToConnectionsMap, mUnbindInactiveServicesRunnable, isMultipleBindingSupported()).",
       "The changed paths only run when isMultipleBindingSupported() is true, so the runtime state of the multi-binding feature decides whether the fix has any effect."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r7",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "a90c62ca6e752a0807cab3bc26eab13d3acc0a3a",
       "tag": "android-security-16.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "6bc72fe8b34e499d4e7b30bb2c40f7bd5147d3e8",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 92,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28581",
     "osv_id": "ASB-A-479515555",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Telecom's CallIntentProcessor.fixInitiatingUserIfNecessary now decides whether a managed (work) profile has a dialer by asking RoleManager for a ROLE_DIALER holder in that user and checking with that user's PackageManager that the holder package exists and is enabled. Before, it only checked whether anything resolved ACTION_DIAL, via DefaultDialerManager.getInstalledDialerApplications.",
      "confidence": "medium",
      "backport_notes": [
       "Apply both commits in order: commit 1 introduces doesUserHaveDialerRoleHolder and rewires fixInitiatingUserIfNecessary, and commit 2 modifies that helper. Both changes are confined to CallIntentProcessor.java.",
       "On branches where Telecom has no FeatureFlags, use the Android 14 variant (UserUtil.isManagedProfile(context, user), UserManager.get(context)). The helper body from commit 1 is identical on 14, 15 and 16."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r7",
      "date": "2026-06-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "510bcce16c24ea02f10e9cec62579a12ab32cb65",
       "tag": "android-security-16.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "3d2e80ac1953bf15833d1eb639c20253baf8b170",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "first_commit_before_announcement_days": 92,
     "multi_tag_fix": true,
     "projects": [
      "platform/packages/services/Telecomm"
     ]
    },
    {
     "cve": "CVE-2026-28582",
     "osv_id": "ASB-A-487522353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ConfirmDeviceCredentialActivity now finishes immediately when it receives KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through any component other than the com.android.settings.ConfirmRemoteDeviceCredentialActivity alias, so the alias (and the checks attached to it) can no longer be skipped by targeting the underlying activity class directly.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained source change: insert the alias constant and the `if (remoteValidation) { ... finish(); return; }` block immediately after `remoteValidation` is computed in onCreate; the Robolectric test and ShadowLockPatternUtils.getCredentialTypeForUser shadow are optional and were omitted on Android 14. Ensure android.content.ComponentName is imported (the diff relies on it already being imported in these branches)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "3ee7e15a126f50207604d5db4bdd8a0f1a9b3cea",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28583",
     "osv_id": "ASB-A-486597445",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "validate_camera_metadata_structure in system/media camera_metadata.c now converts entry_capacity (a count of entries) to bytes by multiplying with sizeof(camera_metadata_buffer_entry_t), with an overflow guard, before checking that the entry region ends at or before data_start.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching only validate_camera_metadata_structure and two struct comments; the same hunks were applied on 14, 15 and 16 (pre-image blob e0c1cadb on 14 and 15, 1f09d5b1 on 16).",
       "Relies on metadata_size_t being 32-bit (the file has a _Static_assert for this) and on PRIu32 for the new ALOGE formats."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/media",
       "sha": "312249da098cf33bebb41c1df67797fa5606c770",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/media"
     ]
    },
    {
     "cve": "CVE-2026-28593",
     "osv_id": "ASB-A-388272624",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The LiveTV \"Send Feedback\" action in SettingsFragment now launches an explicit intent targeting com.google.android.feedback/.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent.",
      "confidence": "high",
      "backport_notes": [
       "SettingsFragment.java has the same pre-image blob (762a190c) on all four branches, so the patch applies verbatim. It is a single self-contained hunk whose only new dependency is the android.content.ComponentName import.",
       "The target component is hardcoded to com.google.android.feedback. Forks shipping LiveTV without that package may find Send Feedback no longer resolves and may want to adapt the component or guard the launch."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "d688b27298d4d316664c4859fe3273991dc223cd",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28596",
     "osv_id": "ASB-A-480975165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds OutOfMemoryError to the catch clause around the game-mode-config XML meta-data parsing in GameManagerService, so that an oversized XML resource resets the flags to defaults and logs an error instead of crashing the process hosting the service.",
      "confidence": "high",
      "backport_notes": [
       "Trivial, self-contained backport: add `| OutOfMemoryError` to the existing multi-catch in GameManagerService's meta-data parsing block. The catch line and the visible handler context are the same on 14, 15 and 16; only line offsets differ."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "581fd9950f41089e86c6b655c1b5fd8a44509b28",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28599",
     "osv_id": "ASB-A-487147249",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In ActivityManagerService.addCreatorToken, the targetPackage used for the isCreatorSameAsTarget shortcut no longer falls back to intent.getPackage() when the outer intent has no explicit component. In that case creator tokens are now attached to nested intents instead of being skipped because of a package constraint the caller controls and that a selector could override.",
      "confidence": "high",
      "backport_notes": [
       "This is a single-commit, single-line change with no dependencies shown. It applies where addCreatorToken contains the getPackage() fallback in the targetPackage computation.",
       "If a fork implemented the selector-aware logic from the commit message instead of the shipped null fallback, check that it still denies the shortcut when a selector is present. The AOSP diff simply drops the getPackage() fallback."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5108d2606324c1401abfa0110373052d62269f24",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28600",
     "osv_id": "ASB-A-490605007",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PaymentDefaultDialog.onCreate no longer reads a UserHandle from the incoming intent's Intent.EXTRA_USER extra; it always uses UserHandle.myUserId() as the userId passed to buildDialog.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in PaymentDefaultDialog.onCreate with no visible dependencies; the same diff (index d333b3da6ac..d556b6df929) was used on 15, 16 and 17, so a cherry-pick should fit branches that still contain the Intent.EXTRA_USER block."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "bf85ca6c87fcb12cee8c9e981d7a22fcfe346559",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28602",
     "osv_id": "ASB-A-466379087",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ClipboardService now reads CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS for the explicit userId in the access-notification check (getIntForUser instead of getInt on the system server context's resolver) and writes it with an explicit userId in setClipboardAccessNotificationsEnabledForUser (putIntForUser instead of putInt).",
      "confidence": "high",
      "backport_notes": [
       "The change is two mechanical substitutions (putInt to putIntForUser with userId; getInt to getIntForUser with userId). It applies wherever the notification check method has userId in scope. On 17-style code the method returns boolean, so keep the local return style.",
       "Settings.Secure.getIntForUser/putIntForUser are hidden system APIs that system_server has had for many releases, so no dependency commits are needed (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "80b1b1fe63b54d525ea51c1e2a65a3c40a7892fa",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28603",
     "osv_id": "ASB-A-491253353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the vetted copy of an app-supplied custom restrictions Intent now has all four FLAG_GRANT_*_URI_PERMISSION flags removed and its data and clipData cleared before Settings starts the activity with it.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The six added lines go directly after the setComponent/setPackage calls in assertSafeToStartCustomActivity. Only the line offset differs across branches (687/683/683/726).",
       "The robolectric test uses ReflectionHelpers to set mPackageManager and call the private method. It needs the SettingsRoboTests target and a three-argument RestrictionsResultReceiver(String, ..., boolean) constructor."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "3bf7abe6e14bbd4b4451a9896707646da1c94ace",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28606",
     "osv_id": "ASB-A-495543559",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AdapterService now removes the per-device entry from mBondAttemptCallerInfo whenever the bond state transitions to anything other than BOND_BONDING, so the CallerInfo recorded for a bonding attempt is no longer retained after bonding completes or fails.",
      "confidence": "medium",
      "backport_notes": [
       "The fix is a self-contained insertion. It needs mBondAttemptCallerInfo and the CallerInfo type to already exist in AdapterService. Place it in the bond-state-change handler after the block that resets phonebook/SIM access permissions. The 15/16 versions need no aconfig flag. If following the 17 lineage, the flagged code must already be present before applying 1b0d684402b2.",
       "The inserted `if` line has non-standard 6-space indentation in the 15/16 commits. This is cosmetic."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "07542331e7c48a93d755c9f13b1fa6d885e1b1ec",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28607",
     "osv_id": "ASB-A-489225275",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds caller permission checks (REQUEST_INSTALL_PACKAGES requested or INSTALL_PACKAGES granted) and the hide-non-system-overlays window flag to UnarchiveErrorActivity.onCreate, moves the helper lookups into PackageUtil, and on Android 17 moves the PIA v2 UnarchiveLaunch forwarding in UnarchiveActivity behind the same checks.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. The PackageUtil additions use PackageUtil.LOG_TAG and the NonNull annotation without adding them, so both must already exist in that file. On branches whose UnarchiveActivity carries the PIA v2 UnarchiveLaunch forwarding (17), take the 17 variant so the isVersionTwoEnabled block lands after the permission checks. The 15/16 diffs contain no such block in UnarchiveActivity."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2e158f2d9a81a0e906aa4bc5f45bfd016c972cfa",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28609",
     "osv_id": "ASB-A-485377744",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "announced-not-published",
     "detail": "platform/frameworks/av",
     "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": [],
      "projects": [
       {
        "project": "platform/frameworks/av",
        "fetched_ok_in_tags": 9,
        "ingested_tags": 9,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-28611",
     "osv_id": "ASB-A-497544808",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes the caller-UID vs. passed-in package name validation (mNfcPermissions.checkPackage) unconditional in six INfcAdapter binder methods of NfcService by removing the aconfig flag check_passed_in_package that previously gated it.",
      "confidence": "high",
      "backport_notes": [
       "On a tree without NfcPermissions.checkPackage (as on Android 15), the Android 15 commit is the self-contained form: add the AppOpsManager field and checkPackage helper to NfcPermissions, then insert the call as the first statement of each package-taking binder method. The Android 16 commit assumes the helper and the flagged calls already exist and only removes the Flags.checkPassedInPackage() guard. If the target tree's updateDiscoveryTechnology, setReaderMode and indicateDataMigration take a package name, consider covering them too.",
       "When deleting the flag from nfc_flags.aconfig, remove every Flags.checkPassedInPackage() reference. Unit tests that construct NfcService need an AppOpsManager from getSystemService: the Android 15 NfcPermissions gets it in its constructor, and the Android 16 test adds that mock."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "ebbca6f8125e8ca45805d570dc45eb2dbb9aa0ea",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28612",
     "osv_id": "ASB-A-475542441",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reorders activity start so that the intent-redirect checks (the EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN check and the IntentCreatorToken-based creator uid/package extraction) run before supervisor.resolveIntent/resolveActivity in both ActivityStarter and ActivityStartController, so that intent resolution can no longer change the intent's component and invalidate the creator token before it is checked.",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching two files. Both reorderings and the visibility change of ActivityStarter.logAndAbortForIntentRedirect must land together, because ActivityStartController now calls that method. The commit depends on the IntentCreatorToken / EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN logic already being present on the branch."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "f7398bb64526b0a0fd12a764be49f9d2d038727e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28613",
     "osv_id": "ASB-A-329233015",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Live TV's ChannelImpl.initAppLinkTypeAndIntent, the fix refuses to build a channel app link Intent when the resolved target activity is exported and declares an android:permission, so no app link is created for permission-protected activities.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained Java change. The pre-fix file blob (5be1179d) is the same in the 14, 15, 16 and 17 diffs, so the patch should apply as is. Forks that modified the isProtectedActivity block in ChannelImpl.initAppLinkTypeAndIntent need to insert the isPermissionProtected computation and its early return."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "86fb106012ec0fbc1361ddc587dffb6db24773fe",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28614",
     "osv_id": "ASB-A-486385459",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an isCallerValid() check to SystemUI's SlicePermissionActivity.onCreate so the slice permission dialog is only shown when the activity was actually launched by the SliceProvider's package, SystemUI itself, or the \"android\" system package; any other launcher causes finish().",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the Java change inserts the gate right after verifyCallingPkg() in SlicePermissionActivity.onCreate plus a new private method. The new Kotlin test depends on SysuiTestCase, @RunWithLooper, reflection into Activity private fields (mActivityInfo, mFragments, mApplication, mWindow) and android.window.OnBackInvokedDispatcher, so it may need adaptation on branches with different SystemUI test infrastructure; the production change does not depend on it.",
       "Activity.getLaunchedFromPackage() is a public API since API 34 (Android 14) and was hidden before that (general platform knowledge); confirm availability on the target branch before backporting."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "dba6ec2c05ff525ec280efc20cec497963c38162",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28617",
     "osv_id": "ASB-A-498792914",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On SDK V and later, WifiNetworkSuggestionsManager now clears the vendor data from every WifiConfiguration in an incoming suggestion batch before the suggestions are processed. Per the commit message, this keeps an app from putting a deeply nested PersistableBundle into persisted suggestion data.",
      "confidence": "medium",
      "backport_notes": [
       "Self-contained single-file change. It uses WifiConfiguration.setVendorData and com.android.modules.utils.build.SdkLevel, and SdkLevel is already imported in the file. Wifi is a mainline module, so the module version can matter as well as the platform release (general platform knowledge).",
       "Placement: insert the loop after the batch validation that returns STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED and before the convertLegacyFieldsToSecurityParamsIfNeeded loop."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Wifi",
       "sha": "4d8225a6d03d700b3297032aeae7154e6915ccce",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2026-28620",
     "osv_id": "ASB-A-477024345",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes the intent-redirection URI grant logic in ActivityStarter and ActivityStartController so that, when an intent's creator UID differs from the launching UID, URI grants are only issued if both the caller's and the creator's checkGrantUriPermissionFromIntent calls yield grants, instead of using or merging whichever party's grants were available; the NeededUriGrants.merge helper is removed.",
      "confidence": "high",
      "backport_notes": [
       "The fix presupposes the Intent Redirection Hardening code (IntentCreatorToken, intentCreatorUid/creatorUid, logAndThrowExceptionForIntentRedirect, and the creatorIntentGrants computation) already exists in ActivityStarter.java and ActivityStartController.java. Make sure all three sites are changed, including the non-flag else branch in ActivityStarter.java, before removing NeededUriGrants.merge, otherwise the build breaks."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "353a1fa497d3bb44984484fa1a490a7a634f551e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28623",
     "osv_id": "ASB-A-494698714",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Stops the device Bluetooth MAC address from being serialized in BleCsRangingCapabilities and BleRssiRangingCapabilities parcels. The Parcel constructors now always produce the placeholder \"00:00:00:00:00:00\", while the builder/constructor path used inside the ranging service keeps the real address.",
      "confidence": "high",
      "backport_notes": [
       "Google shipped this as a single commit per branch, with identical source diffs on 16 and 17. The parcel writer and reader change together, so both sides of the binder must run the patched classes."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Uwb",
       "sha": "4a97403180358ba1f788c484184b99ef5978af00",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Uwb"
     ]
    },
    {
     "cve": "CVE-2026-28624",
     "osv_id": "ASB-A-494585668",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the EXTRA_DATA pass-through from ConfirmDeviceCredentialActivity, so the activity no longer echoes a caller-supplied Intent back as its RESULT_OK result, and makes FingerprintSettings keep the gatekeeper password handle in its own state instead of reading it from the returned Intent.",
      "confidence": "high",
      "backport_notes": [
       "The FingerprintSettings change needs long-based GatekeeperPasswordProvider methods: requestGatekeeperHat(long, long, int) and removeGatekeeperPasswordHandle(long). On Android 16 the index shows a test calling requestGatekeeperHat(long, long, int) (per index search). The removeGatekeeperPasswordHandle(long) overload is only implied by the patch. Check both on any target before porting.",
       "Forks that added other callers of the removed Utils.launchBiometricPromptForMandatoryBiometrics(..., Intent data) overload must change those callers to keep their own state, because the EXTRA_DATA round trip no longer exists."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "192ccfdcab3abe2b318f5e9c4d3ee7797e66d247",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28626",
     "osv_id": "ASB-A-332883816",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SetupPassthroughActivity in LiveTv now checks, on SDK 34+ and via getLaunchedFromPackage(), that it was launched by CommonConstants.BASE_PACKAGE before forwarding the embedded setup Intent. If the check fails, it logs a warning and finishes without forwarding.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. The diffs show the same pre- and post-change blob hashes on 14 through 17, so it should apply cleanly. Activity.getLaunchedFromPackage() was added in API 34 (general platform knowledge), and the fix guards it with an SDK_INT >= 34 check, so forks must keep the gate and compile against SDK 34+."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "856a260c250479dec04ce56cbaace2890123a5e9",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 25c98c910675",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28631",
     "osv_id": "ASB-A-461500706",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now calls getWindow().setHideOverlayWindows(true) right after setContentView(R.layout.miniresolver), so other apps' overlay windows are hidden while the cross-profile mini resolver consent dialog is shown.",
      "confidence": "medium",
      "backport_notes": [
       "The security fix is the single setHideOverlayWindows(true) line placed right after setContentView(R.layout.miniresolver). On trees without buildMiniResolver (like Android 14), put it at the inline setContentView site. It does not depend on the test-refactor commit.",
       "If IntentForwarderActivityTest runs (for example, re-enabled as in 71d6dc109528) against code that has the hide-overlay line, the coretests manifest needs android.permission.HIDE_OVERLAY_WINDOWS. Per the commit message, the activity crashes in the test without it."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "71d6dc10952832ff9f25430b3284fe6735cd375e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "de08a320e58b61755fcc52af6c73505931a53356",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28633",
     "osv_id": "ASB-A-430284594",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In safe mode, VoiceInteractionManagerService no longer parses the recognition-service metadata of third-party (non-FLAG_SYSTEM) recognizers in RecognitionServiceInfo.getAvailableServices, nor the metadata of the already-configured recognizer. A large malicious metadata resource therefore cannot exhaust memory in system_server during a safe-mode boot through these paths.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It depends only on the existing mSafeMode field in VoiceInteractionManagerService and on FLAG_SYSTEM in ApplicationInfo. Forks that call RecognitionServiceInfo.getAvailableServices from other code in the same package must update those call sites for the new isSafeMode parameter."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a660d0c4648290b83f0570a521f772885ee6accb",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28634",
     "osv_id": "ASB-A-471796626",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a check in PhoneInterfaceManager.handleUssdRequest that the calling user is associated with the target subscription ID, throwing SecurityException otherwise.",
      "confidence": "high",
      "backport_notes": [
       "The source hunk is self-contained apart from calling TelephonyPermissions.checkSubscriptionAssociatedWithUser(mApp, subId, Binder.getCallingUserHandle()). Google's fixes use this call on 14 through 17. Where handleUssdRequest has no preceding enforceTelephonyFeatureWithException call, insert the check right after enforceCallPermission() and before Binder.clearCallingIdentity(), as Google did on 14.",
       "The new tests use replaceInstance and SubscriptionManagerService.getActiveSubIdList(boolean). Forks lacking these will need to adapt the test hunk locally."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "03cc6ee76027e902d411deca7f994b70e487c7e3",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/services/Telephony"
     ]
    },
    {
     "cve": "CVE-2026-28636",
     "osv_id": "ASB-A-484148949",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "announced-not-published",
     "detail": "platform/packages/apps/DocumentsUI",
     "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": [],
      "projects": [
       {
        "project": "platform/packages/apps/DocumentsUI",
        "fetched_ok_in_tags": 9,
        "ingested_tags": 9,
        "latest_status": "ok"
       }
      ]
     },
     "projects": [
      "platform/packages/apps/DocumentsUI"
     ]
    },
    {
     "cve": "CVE-2026-28638",
     "osv_id": "ASB-A-470260357",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now keeps 'XMP_' (BOX_XMP) boxes in IsoInterface's flattened box list, and XmpDataParser now redacts XML elements whose attributes, not only whose tag names, carry sensitive EXIF fields. As a result, GPS data in ISO/MP4 XMP metadata is redacted for apps without ACCESS_MEDIA_LOCATION.",
      "confidence": "high",
      "backport_notes": [
       "The Android 16/17 IsoInterface hunk assumes the selective-flatten refactor is already present (a switch in parseNextBox that feeds the flattened list). The Android 15 commit shows Google carrying that refactor along where it was missing.",
       "Pick the variant that matches the branch's XMP class: XmpDataParser.java (15+) or XmpInterface.java (14). The Android 14 variant also iterates over all XMP boxes, which the 15+ variants do not."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "4bdbc2fa9f530126131f81a6908de681473bd4f9",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-28642",
     "osv_id": "ASB-A-460778875",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In ActivityStarter (frameworks/base WindowManager), right after the ActivityRecord is built, the fix clears r.mLaunchTaskBehind when the background activity launch (BAL) verdict blocks the start. Per the fix comment, this keeps a blocked launch from being made visible through the launch-behind path.",
      "confidence": "high",
      "backport_notes": [
       "The fix is a single hunk right after ActivityRecord.Builder(...).build() in ActivityStarter. The only adaptation is how BAL is represented: use balVerdict.blocks() where it exists, or balCode == BAL_BLOCK as in the Android 14 commit."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2b8a07bf74377204aafe83ba650ffb51b2069c3e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28644",
     "osv_id": "ASB-A-471797575",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ActivityTaskManagerService.startNextMatchingActivity() now forwards the original caller's identity (launchedFromUid/launchedFromPackage/launchedFromFeatureId) to the next resolved activity only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID. Otherwise the calling identity is reset to the intermediary's own uid and package.",
      "confidence": "high",
      "backport_notes": [
       "The core change is self-contained in startNextMatchingActivity(). It only needs ActivityStarter's setCallingUid/setCallingPackage/setCallingFeatureId builder setters, UserHandle.isSameApp/getAppId and Process.SYSTEM_UID. On branches without .setUserId(userId), use the Android 14 variant of the chain. The new tests also need the ActivityBuilder.setIntent() addition in WindowTestsBase.java and mockitoSession/spyStatic support in wmtests."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e95170c6378e15695d5f8a3cfd39dfb76d8ff500",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28650",
     "osv_id": "ASB-A-493714520",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowState.setHiddenWhileSuspended now evaluates the window's base type (getBaseType()) instead of mAttrs.type, so sub-windows whose top parent is a system alert window or toast are hidden/shown when the owning package is suspended/unsuspended.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, two-line functional change. The only per-branch adaptation is the test helper (createWindow on 14/15, newWindowBuilder on 16/17). Google's fix commits call getBaseType() on every branch, and on 16 the sibling setForceHideNonSystemOverlayWindowIfNeeded already uses it."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b61562e1abe69d2e970541b97151a1187bba98fd",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28652",
     "osv_id": "ASB-A-492683684",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds enforceRangingPermission() to five out-of-band (OOB) ranging Binder entry points in RangingServiceImpl (oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed, registerOobSendDataListener). Before the fix these methods passed calls to RangingServiceManager without any permission check.",
      "confidence": "high",
      "backport_notes": [
       "One file, five one-line insertions that reuse the existing enforceRangingPermission() helper. It should apply cleanly to any tree where RangingServiceImpl has these five OOB methods. Uwb is a mainline module (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Uwb",
       "sha": "618d9adfcd654a69838f81539957d07b2b650503",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Uwb"
     ]
    },
    {
     "cve": "CVE-2026-28655",
     "osv_id": "ASB-A-477278752",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks RemoteViews parceling so that nested RemoteViews refer by int id to an ApplicationInfoCache parceled at the root, instead of each node writing a full ApplicationInfo. It also removes RemoteViews' use of Parcel.allowSquashing and the ReadWriteHelper trick that forced eager Bundle unparceling in ReflectionAction.",
      "confidence": "medium",
      "backport_notes": [
       "Apply in the Android 16 order. First the ApplicationInfoCache rewrite (0e9656e2222e), which depends on the existing Map-based ApplicationInfoCache, the HierarchyRootData/configureAsChild plumbing and updateAppInfo. Then the allowSquashing removal (6fe346abe6fb). The writer and reader sides of the parcel format change together.",
       "On Android 14, writeToParcel is the public writeToParcel(Parcel,int) without intentsToIgnore, and ReflectionAction uses `value` instead of `mValue` (as the Android 14 diff shows). Adjust the hunks to match."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "0e9656e2222eb68271c36f930b7733025fbd5c1b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "6fe346abe6fb8c7e37a64e46240963504a697d3b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28656",
     "osv_id": "ASB-A-463364901",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the AppOpsManager-based toggling of OP_SYSTEM_ALERT_WINDOW/OP_TOAST_WINDOW user restrictions around sensitive autofill and device-admin UIs with a per-window Window#setHideOverlayWindows(true) / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS attribute so non-system overlay windows cannot be shown on top of those windows.",
      "confidence": "high",
      "backport_notes": [
       "This is a multi-project fix: frameworks/base (autofill), Settings, TvSettings and Car/Settings must each be patched. For Car/Settings, land 770d737414c8 together with 6ee22d7bc352; otherwise the multivalent tests still assert AppOps setUserRestriction calls that production code no longer makes, and they will fail.",
       "On Android 14 the autofill constructor signatures differ; use c441211a26c9 as the adapted version."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c9290fc206ca8e1e024c4408d2fc79fec059a29d",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "6ee22d7bc352b7ac7a9dde5b713469e10b96bc53",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "770d737414c800a7ed203e17be53705a88f074af",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "f6b6ecb4b6e1c2781ddafe2a8ae9cd492a2862b9",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "71c0d7a9967f94c571932047346e2beacb5ed5e3",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 5,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/apps/Car/Settings",
      "platform/packages/apps/Settings",
      "platform/packages/apps/TvSettings"
     ]
    },
    {
     "cve": "CVE-2026-28658",
     "osv_id": "ASB-A-507575871",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountsDb.findMetaAuthUid now gets the account type from a meta_key by removing the fixed \"auth_uid_for_type:\" prefix with substring. Before, it split the key on \":\" and took element [1], which cut short any type that contains \":\".",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, self-contained change to AccountsDb.java with no dependencies visible in the material. The diff is identical on 14 through 16 and differs only in offsets on 17. It should carry over to any tree that still has META_KEY_DELIMITER and the TextUtils.split(...)[1] parse in findMetaAuthUid."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8c2ce8c567a2bb81a2c1a79eefcb1715b95133ab",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28660",
     "osv_id": "ASB-A-498588487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes LauncherAppsService.getAllSessions pass the real caller's UID into a new PackageInstallerService.getAllSessions(userId, filterCallingUid) overload. SessionInfo objects are then generated and filtered for the launcher-side caller instead of for system_server after Binder.clearCallingIdentity().",
      "confidence": "high",
      "backport_notes": [
       "Single commit touching two files. The hunks are small and identical in content across 15, 16 and 17. If a fork has other internal callers that clear identity before calling getAllSessions(int), they should be switched to the new two-argument overload as well."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3d614f9e98a324ed4e7b1fd378d67993eef3db97",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28663",
     "osv_id": "ASB-A-492867206",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "LauncherAppsService.buildIntentSenderForUser now passes ActivityOptions with PendingIntentCreatorBackgroundActivityStartMode set to MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE when it creates the system-owned PendingIntent that getAppMarketActivityIntent returns. The creator's background-activity-start privilege therefore no longer applies when the app sending the IntentSender is not visible.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk in buildIntentSenderForUser. It needs ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode and the MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE constant on the target branch. The ALLOW_IF_VISIBLE constant is newer than the setter, so older branches may need a different mode or a backport of the constant (general platform knowledge).",
       "If the target branch lacks the buildIntentSenderForUser helper, apply the same options Bundle to the PendingIntent.getActivityAsUser call behind getAppMarketActivityIntent."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "05c5903d27b8d1a0cc4074721ffb25ea28eafa33",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28664",
     "osv_id": "ASB-A-489675413",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "RuntimeImage::WriteImageToDisk in ART now fchmod()s the runtime-generated app image (.art) file descriptor to 0444 right after the file is created, and fails image generation if that call does not succeed, so the on-disk image is no longer left writable.",
      "confidence": "high",
      "backport_notes": [
       "One self-contained hunk plus one include, applied unchanged on 14, 15, 16 and 17. Place the fchmod right after the image-file creation error path and before full_data is built, so no data is ever written to a writable file.",
       "ART is delivered as a mainline module (com.android.art) on recent releases, so forks that ship their own ART module build should check that build as well (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/art",
       "sha": "a57f914df7ee689af75f54a04b90928adf0d3121",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2026-28668",
     "osv_id": "ASB-A-501541998",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that a realloc rejected by the Android allocation limit returns nullptr without freeing the caller's original pointer, matching realloc semantics.",
      "confidence": "high",
      "backport_notes": [
       "One commit that removes two lines in LimitRealloc. It should apply to any branch where malloc_limit.cpp has the 'LimitFree(old_mem);' call in the CheckLimit failure branch. The test hunk sits at a different line in each branch (818 on 17, 1279-1291 on 14-16) but its content is identical."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/bionic",
       "sha": "50cbdffd6f7e6e24327120c0ef5133abff8bbc14",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/bionic"
     ]
    },
    {
     "cve": "CVE-2026-28671",
     "osv_id": "ASB-A-395640294",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider.update's file-movement path now deletes a stale Files row at the computed destination path, or checks write permission on a live one, before Os.rename. On a UNIQUE(_data) conflict in the following database update, it deletes the conflicting row at the active rename destination and retries in the same transaction.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit. It adds three private helpers and one parameter and depends only on existing helpers (queryForSingleItem, enforceCallingPermission, clearLocalCallingIdentity/restoreLocalCallingIdentity, helper.runWithTransaction, Files.TABLE). The diff adds no imports, so Objects, BaseColumns, FileNotFoundException and Nullable must already be imported in a fork's MediaProvider.java."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "94374dc411516d283d7c493e56adc5fcbcb6d609",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45517",
     "osv_id": "ASB-A-471796999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds structural (scheme/authority/path pattern) and user-id validation of photopicker pre-selection URIs in PickerUriResolver.processUrisForSelection and MediaQueryForPreSelection before they are permission-checked and used to build picker database queries.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix. When porting, the set of match constants in isValidPickerUri must follow what LocalUriMatcher defines on the target branch (the 15 fix omits PICKER_TRANSCODED_ID), and the MediaQueryForPreSelection hunk was applied only on 16 and 17, where that v2 class is patched."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "32217e55e510079fa464cc6f05eefb952f364119",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45518",
     "osv_id": "ASB-A-393377017",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an OPTION_CHECK_BRACKETS mode to SQLiteTokenizer that rejects SQL fragments with unbalanced or prematurely closed parentheses, and makes ContactsProvider's SelectionBuilder.addClause() run every clause (including the caller-supplied base selection) through that check before concatenating it.",
      "confidence": "high",
      "backport_notes": [
       "Apply the frameworks/base SQLiteTokenizer change first: the ContactsProvider change references SQLiteTokenizer.OPTION_CHECK_BRACKETS and passes a null Consumer, both of which only exist after that commit, and the `&=` to `&` correction is needed for the new bit to survive tokenization. SQLiteTokenizer lives in the platform framework rather than a mainline module, so the framework and ContactsProvider must be updated together (general platform knowledge).",
       "The SQLiteTokenizer diff applied with identical hunks on 14, 16 and 17 (same blob ids 7e7c3fb976c7 -> 3ebeb179a5ba), so it should cherry-pick cleanly onto any tree with that file version."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 0,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "91cf2b3195ed429f1af4526d41a32bfe6496ba72",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "3c11261b2a6b692c6b46bac534befec3ff655d96",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/providers/ContactsProvider"
     ]
    },
    {
     "cve": "CVE-2026-45519",
     "osv_id": "ASB-A-471797062",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds structural checks (content scheme, MediaStore authority, LocalUriMatcher picker path pattern) and a user-ID check to photopicker preselection URIs (EXTRA_PICKER_PRE_SELECTION_URIS), run before the existing permission check in both PickerUriResolver.processUrisForSelection and MediaQueryForPreSelection.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, no dependency ordering. The 16/17 version needs the LocalUriMatcher constants PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID and the v2 MediaQueryForPreSelection class. Google's Android 15 variant uses two match IDs and patches PickerUriResolver only.",
       "The check compares against UserHandle.myUserId() of the MediaProvider process. The v2 test setup adds a mock for mMockContext.getUser()."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "32217e55e510079fa464cc6f05eefb952f364119",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45520",
     "osv_id": "ASB-A-490286986",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricsSettingsBase now only honors the EXTRA_USER_ID and EXTRA_KEY_GK_PW_HANDLE intent extras when the hosting SettingsActivity was launched by the Settings package itself; external launches default to the current user and go through the choose-or-confirm lock flow.",
      "confidence": "high",
      "backport_notes": [
       "The fix depends on SettingsActivity.getInitialCallingPackage(), and the test depends on ShadowPasswordUtils.setCallingAppPackageName()/reset(). Both must exist on the target branch; the patch does not add them on 14 through 17. On a fork where this fragment can be hosted by an activity that is not a SettingsActivity, the extras will be dropped for that host as well."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "23e5e4077bc9d1c79736cf212ba788ab433c4838",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-45521",
     "osv_id": "ASB-A-491748298",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.openProxyFileDescriptor now records the Binder calling UID, and AppFuseBridge.openFile rejects the request (the same way it handles an invalid mount) when that UID differs from the UID that created the AppFuse mount scope.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit. Both files must change together because the openFile signature changes. The fixed check relies on MountScope having a populated uid field; the material confirms this only through the Android 16 code context. The AppFuseBridge.java pre-fix and post-fix blob hashes are the same on 14 through 17, so that hunk should apply cleanly. Only the StorageManagerService.java hunk offsets move."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "63f908ebd9ee2cf831e53fd546e25e188e3d371b",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45525",
     "osv_id": "ASB-A-470265726",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes MediaProvider's ISO/MP4 box parser keep parsing when an XMP box exceeds the allocation limit, keeps the oversized box's file offsets, and has XmpDataParser mark the whole XMP range for redaction when the XMP bytes could not be loaded.",
      "confidence": "high",
      "backport_notes": [
       "Trees that have allocateBuffer in IsoInterface and XmpData.extractXmpData in XmpDataParser (15+) can take the 16 diff as-is. Trees still on XmpInterface with inline allocation (14-era) should use the Android 14 variant, which also adds the 1 MiB limit and the null-rawXmp handling in XmpInterface.process.",
       "The test needs the binary resource tests/res/raw/gps_test_xmp_big.mp4 (about 4.3 MB) and a stageMp4File/stageFile helper in the test class."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "cedfc5086b8d7914d3f77183c8ee166cb89a96c7",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45527",
     "osv_id": "ASB-A-503550257",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reorders convertCleanApertureToRect() in HeifCleanAperture.cpp so the crop-rect bounds check is done in int64_t before the int32_t additions that compute right and bottom. This prevents a signed-integer overflow (IntSan abort) in media.extractor on crafted CleanAperture values.",
      "confidence": "high",
      "backport_notes": [
       "This is a single self-contained commit, and the diff applies identically on 15, 16 and 17 (same pre-image index f0a086703e). A fork must already have the imageW/imageH > INT32_MAX check and the centerX.n/centerY.n < 0 check above the patched block, because the new int64_t check relies on them to keep the later int32_t additions safe. The mp4 extractor lives in the media extractor module (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "9456667afa012cfa93619337839e48abcc5b9842",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-45528",
     "osv_id": "ASB-A-504573577",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp on behalf of the target package and its uid. Before, it called PendingIntent.getActivity on a package Context derived from the system server's own Context, with the calling identity cleared.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, single-hunk change. Its only new dependency is ActivityManagerInternal.getPendingIntentActivityAsApp(requestCode, intent, flags, bundle, packageName, uid), which Google's fix calls on all four majors. Forks that still call createPackageContext (not AsUser) match the 14/15 variant of the hunk; the replacement block is the same everywhere."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "84d85dbe121f48c2bcaff10b69745bbcf1051c02",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45529",
     "osv_id": "ASB-A-418776187",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsSmsProvider.query() now applies the per-user subscription filter (selectionBySubIds) to five URI paths: simple conversations, conversation recipients, conversation subject, search and searchSuggest. It returns an empty cursor when the calling user has no associated subscriptions.",
      "confidence": "high",
      "backport_notes": [
       "The patch relies on selectionBySubIds and emptyCursor already being defined earlier in MmsSmsProvider.query(). The diffs use them without adding them on all four branches. A target tree without that existing per-user subscription code needs it first.",
       "On trees with the restricted-RCS/OTP changes (Android 17 style), use the 17 variant: extend the existing getTextSearchQuery signature instead of replacing it, and keep the new URI_SEARCH_SUGGEST check after the otpFilter check."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "190c0a3b8e446f616614fbfc995b10ebe598f0e9",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2026-49895",
     "osv_id": "ASB-A-503551046",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Raises the minimum accepted EHT Operation element length constant in src/common/ieee802_11_defs.h from 1 to 5 so the length check in get_eht_operation_channel_width() accounts for the fixed oper_params and basic_eht_mcs_nss_set[4] fields that precede the operation information.",
      "confidence": "high",
      "backport_notes": [
       "Single one-line constant change. On branches older than Android 17 the macro is EHT_OPERATION_IE_MIN_LEN; on 17 it is EHT_OPERATION_ELEM_MIN_LEN. Check that the target tree's struct ieee80211_eht_operation contains basic_eht_mcs_nss_set[4] before oper_info and that get_eht_operation_channel_width() checks the length against this macro. Otherwise the constant change alone does not close the read."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "6579923889d69e50a8f22c66d7fc45a3f63047ce",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/wpa_supplicant_8"
     ]
    },
    {
     "cve": "CVE-2026-49913",
     "osv_id": "ASB-A-505452999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now tests the mmap() result for MAP_FAILED instead of NULL and unmaps the source heap via a ScopeGuard on every return path after a successful map.",
      "confidence": "high",
      "backport_notes": [
       "The patch is a self-contained single-hunk change applied identically on 14 through 17. Its only new dependency is android-base/scopeguard.h, so check that the cas default HAL target can use it in your tree.",
       "At each security tag, the per-major facts report 7 of 8 patched-region lines present, yet the resolver marks all four majors as resolved. The material does not explain the missing line, so when confirming a tree, check that both the MAP_FAILED check and the scope guard are present."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/interfaces",
       "sha": "0b6f21543e548c901c52147a6e82c79038407445",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/interfaces"
     ]
    },
    {
     "cve": "CVE-2026-55290",
     "osv_id": "ASB-A-499091487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ResStringPool::setTo in libandroidfw now checks stringsStart and stylesStart against the chunk size unconditionally. It also checks that the styleCount*4 style-offset array fits within the buffer and ends before stringsStart, with overflow and underflow guards. Malformed string pools that could lead to an out-of-bounds read in ResStringPool::styleAt are now rejected with BAD_TYPE.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The diff content is identical across 14 through 17 apart from line offsets. The delete hunk expects the numeric_limits overflow check and the unguarded stringsStart - styleOffsetsStart check to be present. Forks that have modified ResStringPool::setTo need to adapt the patch by hand.",
       "The new tests are self-contained mock-struct tests (stated test command: atest libandroidfw_tests --host)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "01bff648b538e752ebc914bbeca250373f4922c5",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55294",
     "osv_id": "ASB-A-505000651",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes ihevcd_get_tu_data_size() in libhevc's decoder/ihevcd_utils.c so the tu_map region of the TU data buffer is sized by num_tu (luma plus chroma TUs) instead of num_luma_tu, to match how the buffer is later consumed.",
      "confidence": "high",
      "backport_notes": [
       "The hunk is a single line in ihevcd_get_tu_data_size() and is identical on 14 through 17. It should apply cleanly to any libhevc tree where the tu_map term still reads `num_luma_tu * sizeof(UWORD8)`. If the tree carries vendor modifications, check that its consumer layout matches."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libhevc",
       "sha": "5492b022e4d6242e28274cf058c1cf4c5ef1d26e",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libhevc"
     ]
    },
    {
     "cve": "CVE-2026-58822",
     "osv_id": "ASB-A-506109174",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds size guards to FreeType's ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c that reject bitmaps whose width/3 (LCD) or rows/3 (LCD_V) exceeds 0x7FFF with Raster_Overflow, preventing a 16-bit truncation of span x-coordinates that led to a heap underflow write in ft_smooth_lcd_spans.",
      "confidence": "high",
      "backport_notes": [
       "Single commit adding 8 lines, with no dependencies shown. It needs ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c. Google applied the same lines unchanged on 14, 15, 16 and 17. Apply by context, not by line number, because offsets differ across branches."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "6ca3925a3c974876b5be9396928af3dd37ab1414",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-58839",
     "osv_id": "ASB-A-466091304",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Shrinks the stack-allocated read buffer in the incfs MountRegistry line parser (forEachLine in incfs/MountRegistry.cpp) from 128 KiB to 32 KiB, adds a compile-time check that the caller's declared maximum line length fits, and marks the buffer uninitialized, so the parser does not overrun the calling thread's stack when that stack is already close to full.",
      "confidence": "high",
      "backport_notes": [
       "The pre-fix file blob (376eaf2) is the same on 14, 15 and 16, so the single-file diff should apply cleanly. It needs PATH_MAX (limits.h) and clang's `__attribute__((uninitialized))`, both available in the platform toolchain (general platform knowledge). The material has no fork comparison, so no per-file verdict for forks can be given."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/system/incremental_delivery",
       "sha": "67b14f0e00af03a6e5eb128d4a49c1a73198aebf",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/system/incremental_delivery"
     ]
    },
    {
     "cve": "CVE-2026-58874",
     "osv_id": "ASB-A-459460951",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony), loading the stored message's destination address so the emergency-number exemption applies, and makes TelephonyPermissions.checkSubscriptionAssociatedWithUser tolerate a null destAddr (frameworks/base).",
      "confidence": "high",
      "backport_notes": [
       "Carry both the opt/telephony change and the frameworks/base @Nullable/null-guard change. On branches where IccSmsInterfaceManager.sendStoredText takes no userId and SmsController does not already compute callingUser, follow the 14/15 variant: declare callingUser/uid locally and add the UserHandle import if it is missing.",
       "Do not carry over SmsController_fixed_methods.txt. It is a non-source file in the 16 commit."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-16.0.0_r8",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "98be98fe443d77a9e38cfba016ff65de73388982",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "b77a21c161132834a4b8f900dc42d82fb091ca6a",
       "tag": "android-security-16.0.0_r8",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/frameworks/opt/telephony"
     ]
    }
   ],
   "tags": [
    "android-security-16.0.0_r8"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "512973352"
     ],
     "commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "b812bef955e987556c8afaa316bfe482c51c64d9",
       "subject": "Fix NAN frequency list handling in AIDL interfaces.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "503553199"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "0031ce0a88910516602696243a8f3735b5beddc2",
       "subject": "Fix race conditions in CryptoHal plugin usage",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "503541238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "ccc63274119beea61d9dd81cfb60026b30c1d5a5",
       "subject": "Fix MediaBuffer size-inflation off-by-32 bug",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "505827492"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "2cd4f2d9a1b0c3199bb6aa1dc3d0e98543af8d7f",
       "subject": "Fix OOB in DynamicsProcessing MBC band processing",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "396471524",
      "482172329"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "6ff99fe0d5b1dabc1e06b65c73c371337278d2a8",
       "subject": "[media] Fix heap-buffer-overflow in AudioAttributes unmarshalling",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "485020640"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "f3bd2850bafd6014b1c287cc9b00fd5f7a0e71e4",
       "subject": "[media] Fix heap out-of-bounds write in MatroskaSource::read",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "525133623"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a5fd26078dbabcb9693d1db6713fc0ef0f6de4b0",
       "subject": "[25Q2] Mitigate BAL bypass via Companion Device Manager",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "349318333"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "9444f519c4e55123c11a7ce92a960bb38486dbab",
       "subject": "Fix a regression in ECM mode setting after package install",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "472955222"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e8ef27e17d89f80884421c4374048b7d9daaad64",
       "subject": "Sanitize labels in GrantCredentialsPermissionActivity",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "492093013"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a0bd9f4b3d53ef2942c605f733d384f9abc2664c",
       "subject": "Truncate long device admin descriptions",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "1075d04ab9dcc0281644278614a6a1d8d33934a8",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "d125be8375f98f8ca2b3bb54b8c08ccbf764d042",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "4760b7d12a10793d92d8ddcf4186c0592c051402",
       "subject": "Use loadDescriptionSafe for DeviceAdmin descriptions.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "429417238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "431c43bd57cc0c02656219c28d59232632d6d095",
       "subject": "Limit knownActivityEmbeddingCerts in manifest",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "505067993"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c47530a0f8f4dfae097af773651ccf069a23156b",
       "subject": "Use byte count rather than string length for NMS component name check",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "472968114"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "e5560bd1d0c2c972e9222c03157c89ca1110e31f",
       "subject": "SystemUi UsbDialog: fix label vulnerability",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "495409704"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "35058d93a551f115a314a1b52e9a55c4745153d8",
       "subject": "Fix path traversal and missing ownership check in LocaleManagerService",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "490083213"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b159c903dc1d67c43c914cd2d821cd3f5b8b9884",
       "subject": "Add Task-level input sink to block cross-task tap pass-through",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      },
      {
       "project": "platform/frameworks/base",
       "sha": "8c66816921858622c44100697e73864f14b422fa",
       "subject": "Move input sink on top of the Activity when needed",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "498857374"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "0209580ab87ad06b7b1f0649af2ed66be985cdaf",
       "subject": "Check notification permission in getAuthToken(notifyOnAuthFailure).",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "451602444"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cf0b6cd90ce0caf21fae7a58157fb8420013a174",
       "subject": "Add size validation and trimming for ShortcutInfo objects.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "478725173"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "2e7f56e02d86189b3e90d25e03d47cb2bf315d26",
       "subject": "Hide media on smartspace view when setting is disabled",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "501447778"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "794ac2765d102a1cabede63a54d54502b21400ea",
       "subject": "[LocalImageResolver] Fix security check bypass",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "bulletin_history": [
      {
       "bulletin": "2026-09",
       "cve": "CVE-2026-28666",
       "severity": "Critical",
       "versions": [
        "14",
        "15",
        "16",
        "16-qpr2",
        "17"
       ],
       "status": "removed",
       "removed_seen": "2026-10-13",
       "removed_in_version": "1.3"
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "465446148"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "fb6a81e33d54baecf5b05ad81b4522433f710b94",
       "subject": "Autofill SaveUi URL validation and FLAG_IMMUTABLE",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "466379326"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cabd39ad7bd18387802dcb9f2fd27a251d9966f2",
       "subject": "Security bug fix: Prevent USB data changes for non-owner users during lockdown mode.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "495646742"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5e717959bef10e606ae0f0bd09a9c12e49795f27",
       "subject": "Restrict VirtualDeviceImpl methods to device owner.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "463362836"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "f2a29f4351f8b833f3e2472377c30f920bde4492",
       "subject": "RESTRICT AUTOMERGE [ExternalStorageProvider] Revoke URI permissions by path",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "21361e1dc45fbb4a21d37683147298178441b241",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions for specific document paths",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "4d92f79329063d8ea8c486a8063cee4a707a7108",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions on file path updates",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "262884935"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "585e7556fcec4c1cc1e76ad767412c158a64b5df",
       "subject": "Fix URI grant persistence bypass",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "475232460"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8785e148ca825ce49b998b08bbe2c0c004fa3608",
       "subject": "RESTRICT AUTOMERGE Filter ignorable Unicode codepoints in ExternalStorageProvider",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "378087531"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bc676d257ca0bd51c915d628b2bd9a640ae57151",
       "subject": "Fix entryadapter usage",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "517377278"
     ],
     "commits": [
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "cdc2bb368625130eef0a3842bd5beb5eef11c16c",
       "subject": "Fix ArrayIndexOutOfBoundsException in SIMRecords due to invalid EF_CFIS/EF_CFF",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "522375782"
     ],
     "commits": [
      {
       "project": "platform/hardware/nxp/nfc",
       "sha": "8e5737ab990887d55d57a804d58b213a7f77ff9b",
       "subject": "Fix Use-After-Free in NXP NFC HAL timer teardown",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "394083030"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "7f7505117ed8ff3c41576935294fc76cebcf6773",
       "subject": "Check getLaunchedFromPackage for CE Storage",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "482024712"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/ManagedProvisioning",
       "sha": "13d3d66532b19367b8b734976c0cd2e3637dbb3a",
       "subject": "Prevent Setup Wizard from starting provisioning if the device is already provisioned.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "471797085"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "afbec373ef100a4638f6a645ab87c70d58086988",
       "subject": "Fix confused deputy in Bluetooth settings dashboard",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "488559440"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "5db0f9425d8289934bb1e5a3063ee7d21e3f72af",
       "subject": "Sanitize package labels in SettingsApplication",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "465140520"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "cec4b7bfe86db3769ec28dae48bdca0ba2e10be2",
       "subject": "GATT: Fix notifications sent to remote device without security checks",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "522373202"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "fec825e4855c44898d4c46675a36ba49c59745d3",
       "subject": "Fix cross-thread UAF in AvrcpService updates",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "497102083",
      "497851840",
      "510038786"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "a74f9439828323590680d7a7810fe6a9e11ef0b0",
       "subject": "[RESTRICT AUTOMERGE] Enforce incoming CTKD security requirements",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "413510838",
      "415689442"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "23f057ce56f41bddad820a6453cf951d12a97b8c",
       "subject": "Handle concurrency issues in SDP record creation",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "444443479"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/IntentResolver",
       "sha": "661c048f08fbb32d5593feaf75100708cdc84e1e",
       "subject": "Sanitize alternate intents.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "522366268"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "0779a96c20c7308b5e94b4b984eacf191c99b293",
       "subject": "Fix integer underflow and OOB access in libnfc-nci",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "503550880"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "38cc06501f91fbcaebb397fc6ca81bf1b4d5c11e",
       "subject": "Fix integer underflow in rw_ci_data_cback",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "503545851"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "3cfc747e26514ae58ddfe4ebdc594c575352f766",
       "subject": "Fix heap buffer overflow in nfa_t4tnfcee_store_rx_buf",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "486763102"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "7a3c86a5cc2dbdc76484548813d2783988c3250f",
       "subject": "Restrict max size for Note.NOTE field",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "482947423"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "a3ba6b22ea3d0b10329ef353c1f43f7b12454ab3",
       "subject": "RESTRICT AUTOMERGE Fix ZWSP path bypass in DownloadProvider",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "498162425"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "e28ef02f33a90f6f997d8ce5fc30b42ce78bf7c7",
       "subject": "RESTRICT AUTOMERGE Fix DownloadProvider completed download security bypass",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "490768196"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "2d651e871fc1d0131c2f5c6d555ffcd5ef3ccc6d",
       "subject": "RESTRICT AUTOMERGE Fix path traversal vulnerability in DownloadStorageProvider",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "463363325"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "f108d16cda1066ee1c62885037f58b4841ca5c4f",
       "subject": "RESTRICT AUTOMERGE Use parameterised queries for photo picker search request queries",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "494725584"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "a8c60526426ce94d4eb259726166fa093f1db934",
       "subject": "TelephonyProvider: Fix SQL injection in projection and sortOrder",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "503542266"
     ],
     "commits": [
      {
       "project": "platform/packages/services/BuiltInPrintService",
       "sha": "883f3a1848ea3709344cb8c900f3207b86baf8b7",
       "subject": "Limit media-supported values",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "508585261"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "c36565c8706e14f9a33a6273ba663bf0a9a6f230",
       "subject": "Fix MMI check bypass via leading whitespace in tel URI.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "504612754"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "125c1454cbea68b8154f72d10f27e096fcd65fa1",
       "subject": "Check caller in CSW#queryRemoteConnectionServices",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "356604577"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "a87be58dc77a725a93f1697bb1baeb5e9e6f31f9",
       "subject": "Resolve cross account user ringtone validation.",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "517377772"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "3cf52f33f13e22fa4eff334fc498e48f5fa7c8bf",
       "subject": "Fix StackOverflowError in SatelliteEntitlementController",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "490916793"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "73bc9bf6eb4cf4abd10c11b97ac151aa04374b64",
       "subject": "[Telephony] Secure contact URI access in Call Forwarding",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    },
    {
     "bug_ids": [
      "477022787"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "d394116062393d971ffa1d2744f672699feb8b39",
       "subject": "Prevent SDK Sandbox from bypassing isSystemApp check",
       "tag": "android-security-16.0.0_r8",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-16.0.0_r8"
     ],
     "tag": "android-security-16.0.0_r8"
    }
   ],
   "coverage": {
    "n_ingested_tags": 9,
    "first_ingested_tag": "android-security-16.0.0_r1",
    "latest_ingested_tag": "android-security-16.0.0_r9",
    "window_base_tag": "android-16.0.0_r2",
    "window_start": "2025-06-10",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {
    "CVE-2026-27280": {
     "cve": "CVE-2026-27280",
     "summary": "Bumps external/dng_sdk from DNG SDK 1.7.1 build 2471 to build 2502. In OptimizeOrder (dng_pixel_buffer.cpp) the 32-bit pointer-rebase arithmetic is replaced with 64-bit arithmetic through SafeInt64Mult, step negation is checked, and a zero-count early return is added. dng_render_task::ProcessArea (dng_render.cpp) now requires the src and dst areas to be the same size and addresses its temporary mask and RGBTables buffers in source coordinates.",
     "component": "platform/external/dng_sdk (Adobe DNG SDK: dng_pixel_buffer, dng_render)",
     "weakness": "Integer overflow / wraparound in pointer offset computation, and inconsistent buffer-area indexing. This is a bundled vendor update, and the material does not identify which flaw is tied to the CVE.",
     "root_cause": "In source/dng_pixel_buffer.cpp, OptimizeOrder rebased the source and destination pointers for negative steps with (int32)(countN - 1) * stepN * (int32)pixelSize. This was evaluated entirely in 32-bit signed arithmetic and could overflow. A count of 0 made the (count - 1) term degenerate. The spread estimates sRange and dRange were accumulated in uint32 and could wrap, and a step of INT32_MIN was negated without a check. In source/dng_render.cpp, dng_render_task::ProcessArea computed addresses in its temporary buffers (the RGBTables mask buffer, the background-mask loop and its width, and the per-row RGBTables buffer) from a mix of dstArea and srcArea values. It never checked that the two areas were the same size, and it converted buffer.fArea.W () to int32 with a plain cast.",
     "fix_analysis": [
      "source/dng_pixel_buffer.cpp:OptimizeOrder: adds an early return when count0, count1 or count2 is 0.",
      "source/dng_pixel_buffer.cpp:OptimizeOrder: sRange and dRange are now computed in uint64, with each Abs_int32 (step) and (count - 1) cast to uint64.",
      "source/dng_pixel_buffer.cpp: adds three static helpers. ComputeRebaseDelta computes SafeInt64Mult of int64 (count) - 1, step and pixelSize. OffsetPointer offsets a const or non-const pointer by an int64 byte delta. NegateStepChecked calls ThrowOverflow if step == INT32_MIN and otherwise returns -step. All six negative-step branches (dStep0/1/2 when dRange >= sRange, sStep0/1/2 otherwise) now use these helpers for sPtr, dPtr and both steps.",
      "source/dng_render.cpp:dng_render_task::ProcessArea: adds DNG_REQUIRE (srcArea.Size () == dstArea.Size (), \"area size mismatch\"). It defines const uint32 cols = srcArea.W () (the same value as the old srcCols) and colsSigned = ConvertUint32ToInt32 (cols), and uses them in all per-row calls and loops. The real width change is in the background-mask loop, which previously took its width from dstArea.W ().",
      "source/dng_render.cpp:dng_render_task::ProcessArea: buffer.fPlaneStep = int32 (buffer.fArea.W ()) becomes ConvertUint32ToInt32 (buffer.fArea.W ()). The two tempMaskBuffer.DirtyPixel calls and the background-mask row loop (row bounds, ConstPixel_real32 and DirtyPixel_real32) now use srcArea instead of dstArea. The per-row RGBTables rect is renamed srcRowArea. The final copy into dstBuffer still uses dstArea.l, matching the new header comment.",
      "source/dng_render.cpp:dng_render_task::ProcessArea: the ProfileGainTableMap selection and the exposureWeightGain computation are moved out of the per-row loop. The selection logic is unchanged.",
      "source/dng_point.h: adds a unary operator- for dng_point, implemented with SafeInt32Sub (0, v/h), and one for dng_point_real64. source/dng_big_table.cpp:dng_image_table::PutCompressedStream uses it in place of dng_point (0, 0) - fImage->Bounds ().TL ().",
      "source/dng_camera_profile.cpp: SetFourColorBayer, SetProfileGainTableMap and both SetMaskedRGBTables overloads now call ClearFingerprint ().",
      "Non-functional changes: AspectRatio is renamed to BaseAspectRatio in dng_negative.h, dng_negative.cpp and dng_render.cpp; dng_flags.h bumps the build to 2502; README.version is updated; dng_utils.cpp adds a printf format attribute on Mac/iPhone only; a comment typo is fixed in dng_globals.h."
     ],
     "completeness": [
      "OptimizeOrder is converted uniformly: all six negative-step branches use the new helpers for both pointers and both steps, and both range sums are widened to uint64.",
      "The rebased delta is applied to the pointers without checking that the result stays inside the buffer behind sPtr/dPtr. Correctness still depends on callers passing consistent count, step and pixelSize values.",
      "In ProcessArea, DNG_REQUIRE only checks that srcArea and dstArea have the same size. It does not check that the temporary buffers (fTempBuffer, fMaskBuffer, fRGBTablesTempBuffer) are large enough for cols. Their sizing is not shown in the diff.",
      "The final per-row copy into dstBuffer uses dstArea.l and loops over cols. With the new size requirement, cols equals dstArea.W ().",
      "The binary dng_point operator- that the old PutCompressedStream code used is not shown, so it is not visible whether it was already overflow-checked.",
      "The commit is a bulk vendor update (2471 -> 2502) that also bundles unrelated changes (fingerprint invalidation, renames, format attribute). The material does not say which change is the one assigned to this CVE."
     ],
     "behavior_changes": [
      "OptimizeOrder returns immediately without reordering when count0, count1 or count2 is 0.",
      "OptimizeOrder calls ThrowOverflow when a step it negates equals INT32_MIN. The rebase product is now computed in int64 through SafeInt64Mult, whose overflow behavior is not shown.",
      "dng_render_task::ProcessArea fails a DNG_REQUIRE when srcBuffer.fArea and dstBuffer.fArea differ in size.",
      "The camera profile setters (SetFourColorBayer, SetProfileGainTableMap, SetMaskedRGBTables) now clear the cached fingerprint.",
      "The ProfileGainTableMap lookup and exposureWeightGain are computed once per ProcessArea call instead of once per row.",
      "The reported SDK build string changes from \"1.7.1 (2471)\" to \"1.7.1 (2502)\"."
     ],
     "preconditions": [
      "A DNG image reaches the dng_sdk render pipeline (dng_render::Render -> dng_render_task::ProcessArea), or reaches pixel-buffer operations that call OptimizeOrder with negative steps or extreme counts, steps or pixel sizes.",
      "The material does not show which Android 16 processes link external/dng_sdk and feed it untrusted files. Per index search, the matches in frameworks/base/core/jni/android_hardware_camera2_DngCreator.cpp show a TiffWriter-based writer and no call into the patched dng_sdk functions."
     ],
     "per_major": {
      "14": "same fix: platform/external/dng_sdk 52ff589c0c55 in android-security-14.0.0_r29, identical diff.",
      "15": "same fix: platform/external/dng_sdk a23ee6aeb0ee in android-security-15.0.0_r17, identical diff.",
      "16": "same fix: platform/external/dng_sdk c4ffcdb3285d in android-security-16.0.0_r8 (this commit).",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 6 of 8 lines of the patched region are present, the same ratio as the fixed 14/15/16 tags."
     },
     "backport_notes": [
      "The change is a vendor drop of DNG SDK 1.7.1 build 2502 on top of build 2471, applied with an identical diff on 14, 15 and 16. It depends on SafeInt64Mult, SafeInt32Sub, ConvertUint32ToInt32, ThrowOverflow and DNG_REQUIRE already being available in the tree. The material does not show where these are defined.",
      "The AspectRatio -> BaseAspectRatio rename in dng_negative.h changes the API. Any out-of-tree callers of dng_negative::AspectRatio must be updated when this commit is taken."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which of the bundled changes (OptimizeOrder arithmetic, ProcessArea area handling, dng_point negation, profile fingerprint) Google assigned to CVE-2026-27280. The commit message only says 'Update to DNG SDK 1.7.1 2502'.",
      "The concrete malformed-input condition that triggers the flaw, and the process in which the bulletin's Critical EoP applies.",
      "How SafeInt64Mult, SafeInt32Sub and the single-argument ConvertUint32ToInt32 behave on overflow (throw or return).",
      "How fTempBuffer, fMaskBuffer and fRGBTablesTempBuffer are sized in dng_render_task::Start, which decides whether the new size requirement alone is enough.",
      "Whether Android 17 already carries the 2502 code."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 11,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28590": {
     "cve": "CVE-2026-28590",
     "summary": "The fix makes the SMP layer reject LE (and SMP-over-BR/EDR) pairing commands whose advertised maximum encryption key size is below the system minimum (7 octets by default). To do this it exports the existing BR/EDR minimum key size helper as btm_sec_get_min_enc_key_size() and calls it from smp_proc_pair_cmd and smp_br_process_pairing_command.",
     "component": "platform/packages/modules/Bluetooth, system/stack (SMP pairing / BTM security)",
     "weakness": "Missing validation of a peer-supplied security parameter (encryption key size) in LE pairing",
     "root_cause": "In system/stack/smp/smp_act.cc, smp_proc_pair_cmd parsed the peer's maximum encryption key size (p_cb->peer_enc_size) from the pairing command. It failed pairing with SMP_ENC_KEY_SIZE only when p_cb->cert_failure was SMP_ENC_KEY_SIZE. Nothing checked that peer_enc_size met the 7-octet minimum required by the Core specification. Per the commit message, the minimum key size was enforced for BR/EDR but not for LE. In btm_sec.cc that enforcement was the file-static get_min_enc_key_size(), used in read_encryption_key_size_complete_after_encryption_change and read_encryption_key_size_complete_after_key_refresh. smp_br_process_pairing_command also had no key size check at the point where the fix adds one.",
     "fix_analysis": [
      "system/stack/btm/btm_sec.h: moves the constants MIN_KEY_SIZE = 7, MIN_KEY_SIZE_DEFAULT = MIN_KEY_SIZE and MAX_KEY_SIZE = 16 into the header and declares a new public helper, uint8_t btm_sec_get_min_enc_key_size().",
      "system/stack/btm/btm_sec.cc: the former file-static get_min_enc_key_size() becomes btm_sec_get_min_enc_key_size(). On Android 16 it still reads android::sysprop::bluetooth::Gap::min_key_size(), defaults to 7, clamps the value to [7, 16] and caches it in a static. read_encryption_key_size_complete_after_encryption_change and read_encryption_key_size_complete_after_key_refresh now call the renamed helper, so the BR/EDR path does not change behaviour.",
      "system/stack/smp/smp_act.cc:smp_proc_pair_cmd: after reading peer_enc_size from the pairing command, it now fails pairing when p_cb->cert_failure == SMP_ENC_KEY_SIZE or p_cb->peer_enc_size < btm_sec_get_min_enc_key_size(). The failure path logs a warning and raises SMP_AUTH_CMPL_EVT with status SMP_ENC_KEY_SIZE. The check runs before smp_command_has_invalid_parameters and before the role-dependent handling that follows.",
      "system/stack/smp/smp_act.cc:smp_br_process_pairing_command: adds the same check for the SMP-over-BR/EDR pairing command. When peer_enc_size is below the minimum, it raises SMP_BR_AUTH_CMPL_EVT with status SMP_ENC_KEY_SIZE.",
      "system/test/mock/mock_stack_btm_sec.cc/.h: adds a mockcify mock for btm_sec_get_min_enc_key_size, with return_value defaulting to MIN_KEY_SIZE_DEFAULT."
     ],
     "completeness": [
      "Covers both SMP functions that parse a peer pairing command: smp_proc_pair_cmd (LE) and smp_br_process_pairing_command (BR/EDR).",
      "Only the peer-advertised maximum key size (peer_enc_size) is validated. This change does not check the local p_cb->loc_enc_size.",
      "The BR/EDR checks run after encryption (read_encryption_key_size_complete_after_encryption_change and _after_key_refresh) are unchanged apart from the helper rename. This commit adds no equivalent key size read after encryption on LE links.",
      "On Android 16 the minimum can still be configured through the Gap min_key_size sysprop, but it is clamped to [7, 16], so it cannot be set below the spec minimum."
     ],
     "behavior_changes": [
      "LE pairing with a peer whose pairing command advertises a maximum encryption key size below the minimum (7 by default) now fails with SMP status SMP_ENC_KEY_SIZE. Before the fix, pairing failed on this status only when p_cb->cert_failure was SMP_ENC_KEY_SIZE.",
      "SMP-over-BR/EDR pairing commands with a key size below the minimum now fail with SMP_ENC_KEY_SIZE via SMP_BR_AUTH_CMPL_EVT.",
      "A new log::warn line, 'Encryption key size {} smaller than the minimum {}', is written on rejection.",
      "On Android 16, the Gap min_key_size sysprop (clamped to 7..16) now also governs SMP pairing, not only the BR/EDR check after encryption.",
      "Interop: LE peers that advertise a maximum key size below the minimum can no longer pair."
     ],
     "preconditions": [
      "Bluetooth is enabled and there is an LE connection with the remote device (or, for the BR path, a BR/EDR connection using SMP over BR/EDR).",
      "The remote device sends a pairing command whose maximum encryption key size field is below 7 (or below the configured minimum on Android 16).",
      "The code context treats pairing from a non-bonded device as new device pairing and allows it to continue, so no prior bonding is needed to reach the check."
     ],
     "per_major": {
      "14": "adapted fix (3475f39c4c16, android-security-14.0.0_r29). The SMP checks in smp_proc_pair_cmd and smp_br_process_pairing_command are the same but log with SMP_TRACE_WARNING. btm_sec_get_min_enc_key_size() is added to btm_sec.cc and returns a constant MIN_KEY_SIZE_DEFAULT (no sysprop). MIN_KEY_SIZE is removed from btu_hcif.cc. MIN_KEY_SIZE, MIN_KEY_SIZE_DEFAULT and MAX_KEY_SIZE are added to btm_sec.h as uint8_t. The two read_encryption_key_size_complete_* functions in system/stack/btu/btu_hcif.cc now call the helper, and btu_hcif.cc gains an include of stack/btm/btm_sec.h. Incidental edits: gd/hci/controller.cc (kMinEncryptionKeySize comment and a local variable) and a const qualifier on btm_proc_sp_req_evt. The mock is a plain function that returns MIN_KEY_SIZE_DEFAULT.",
      "15": "adapted fix (f161543f33de, android-security-15.0.0_r17). The SMP changes are identical. btm_sec_get_min_enc_key_size() returns a constant MIN_KEY_SIZE (no sysprop). The constants are added to btm_sec.h and the old uint8_t MIN_KEY_SIZE is removed from btm_sec.cc. The two read_encryption_key_size_complete_* functions in btm_sec.cc now call the helper. A mockcify mock is added with return_value 7.",
      "16": "same fix (8b61335e4984, android-security-16.0.0_r8). The existing sysprop-backed get_min_enc_key_size() is renamed to the exported btm_sec_get_min_enc_key_size() and keeps the Gap min_key_size clamp to [7, 16]. The SMP checks and mocks are as described above.",
      "17": "not affected (per bulletin). At android-security-17.0.0_r1, 5/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single commit. On 14 and 15, btm_sec_get_min_enc_key_size() is a constant 7. The essential change is the two checks in smp_act.cc plus making the helper and the constants visible from btm_sec.h. On 14 the BR/EDR key size checks are in system/stack/btu/btu_hcif.cc, which needs an include of stack/btm/btm_sec.h.",
      "Each branch's fix also adds a btm_sec_get_min_enc_key_size mock (a mockcify struct on 15 and 16, a plain function on 14). Carry it along with the change."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show whether smp_command_has_invalid_parameters already range-checks peer_enc_size.",
      "The material does not describe the security consequence behind the Critical EoP rating.",
      "The material does not show what sets p_cb->cert_failure.",
      "The bulletin's affected-versions list includes 17-next while Android 17 is not listed; the material does not show whether android-security-17.0.0_r1 already carries the upstream change."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28604": {
     "cve": "CVE-2026-28604",
     "summary": "Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport* across the blocking handshake. The thread now captures a weak-pointer map ID and the connection object, performs the handshake off-thread, then re-validates the transport on the fdevent looper via transport_from_callback_arg. The fix also adds a 3-second socket timeout to the daemon-side FdConnection::DoTlsHandshake.",
     "component": "platform/packages/modules/adb, adbd daemon authentication / TLS transport (daemon/auth.cpp, transport.cpp)",
     "weakness": "Use-after-free (raw pointer captured by a detached thread across a blocking asynchronous operation; transport state also mutated off the fdevent thread)",
     "root_cause": "In daemon/auth.cpp, adbd_auth_tls_handshake(atransport* t) spawned a detached std::thread that captured the raw pointer t. The thread blocked in t->connection()->DoTlsHandshake(rsa_pkey, &auth_key), which had no socket timeout on the daemon side. Afterwards it dereferenced t (t->IsTcpDevice(), t->auth_key, adbd_wifi_secure_connect(t), adbd_auth_verified(t), t->Kick()). Nothing kept the atransport alive or checked its liveness, so if the transport was destroyed in the meantime (e.g. socket closed, device unplugged) the thread used freed memory. The transport state updates also ran on the background thread rather than the fdevent looper thread. The fix's comments state that these updates must run on the looper thread.",
     "fix_analysis": [
      "Commit 1c97946b5ca6 (daemon/auth.cpp) documents the global 'transports' map (uint32_t -> weak_ptr<atransport>). It adds fdevent_check_looper() to transport_to_callback_arg() and transport_from_callback_arg(), asserting that both run on the fdevent thread. It also refactors transport_from_callback_arg() to use std::map::extract, so the entry is always removed from the map when looked up. Previously the code did find, then erase only after a successful get(), leaving the entry in place when get() returned null.",
      "Commit f487f473c82d (daemon/auth.cpp:adbd_auth_tls_handshake) obtains an opaque ID via transport_to_callback_arg(t) and copies t->connection() into a local 'connection'. The detached thread captures only [arg, connection], calls connection->DoTlsHandshake(rsa_pkey, &auth_key) and records the bool result. It then posts a lambda with fdevent_run_on_looper that calls transport_from_callback_arg(arg). If that returns nullptr, the result is dropped (logged at ERROR if the handshake had succeeded). Otherwise, t->Kick() is called on failure, and on success the previous IsTcpDevice()/auth_key/adbd_wifi_secure_connect or adbd_auth_verified/adbd_notify_framework_connected_key logic runs, now on the looper thread.",
      "Commit f487f473c82d (transport.cpp:FdConnection::DoTlsHandshake) splits the function into an ADB_HOST client variant and a daemon server variant using #if ADB_HOST/#else. The daemon variant uses adb_setsockopt to set SO_RCVTIMEO and SO_SNDTIMEO on fd_ to 3 seconds before tls_->DoHandshake(), logging failures via PLOG(ERROR), and resets both to 0 afterwards. Per the commit message, the timeout is meant to prevent Denial of Service by a malicious or slow client."
     ],
     "completeness": [
      "The daemon path adbd_auth_tls_handshake is fully converted: no raw atransport* is held in the background thread, and every transport access inside the posted lambda follows a looper-thread liveness check.",
      "During the handshake, the connection object stays alive only through the value-captured copy of t->connection(). The material does not show the return type of atransport::connection(), so the diff cannot confirm that this capture keeps the FdConnection alive.",
      "The 3-second handshake timeout applies only to the non-ADB_HOST (daemon) variant of FdConnection::DoTlsHandshake. The host/client variant has no timeout.",
      "Per index search (android-16.0.0_r4 snapshot), the host-side adb_auth_tls_handshake in client/auth.cpp still captures a raw atransport* in a detached thread and calls t->Kick() after DoTlsHandshake. Neither commit touches it (see potential_variants).",
      "Other users of the transports map (e.g. the user authorization popup flow mentioned in the comments) only gain the fdevent_check_looper() assertions and the extract refactor. Their logic is unchanged."
     ],
     "behavior_changes": [
      "Each socket read or write during a daemon TLS handshake now times out after 3 seconds (SO_RCVTIMEO/SO_SNDTIMEO) instead of blocking indefinitely. The timeouts are cleared after the handshake.",
      "A handshake result for a transport destroyed in the meantime is ignored instead of touching the transport. If the handshake had succeeded, adbd logs 'TLS handshake succeeded for deleted transport, ignoring' at ERROR level.",
      "Transport state updates and Kick() after the handshake now run on the fdevent looper via fdevent_run_on_looper.",
      "transport_to_callback_arg and transport_from_callback_arg now assert (fdevent_check_looper) that they are called on the fdevent thread. transport_from_callback_arg always removes the map entry, including when the weak_ptr has expired.",
      "FdConnection::DoTlsHandshake is now two separate functions selected by ADB_HOST. The host variant ignores its auth_key parameter."
     ],
     "preconditions": [
      "Device-side adbd must reach adbd_auth_tls_handshake, i.e. an adb transport performing the TLS handshake. The IsTcpDevice()/adbd_wifi_secure_connect branch points to the wireless debugging / TCP TLS path. The exact caller in transport.cpp is not shown in the material.",
      "The atransport must be destroyed while the detached handshake thread is still running or has just finished. Before the fix, the daemon handshake had no socket timeout.",
      "The bulletin rates this Critical / RCE. The material does not describe the attacker position further."
     ],
     "per_major": {
      "14": "Same fix, different context: Google's commit c7995e05cc7b in android-security-14.0.0_r29 makes identical adbd_auth_tls_handshake and DoTlsHandshake changes. The base auth.cpp (index 0be3f995) differs from 16, and the context log line is LOG(INFO) instead of VLOG(AUTH). The material does not show whether the hardening commit (fdevent_check_looper/extract) was also applied. Resolver: 7/8 patched lines present.",
      "15": "Same fix, different context: Google's commit 119250e41944 in android-security-15.0.0_r17 is identical to the 14 diff (same base index 0be3f995, LOG(INFO)). Hardening commit status not shown. Resolver: 7/8 patched lines present.",
      "16": "Same fix: Google's commits 1c97946b5ca6 (hardening), then f487f473c82d (UAF fix), in android-security-16.0.0_r8. Resolver: 7/8 patched lines present.",
      "17": "Adapted fix: Google's commit 12700491ebb3 in android-security-17.0.0_r1 is the same change plus CHECK_NOT_LOOPER_THREAD() at the top of both DoTlsHandshake variants. Resolver: 7/8 patched lines present."
     },
     "backport_notes": [
      "On Android 16, apply 1c97946b5ca6 (fdevent_check_looper + extract in transport_to_callback_arg/transport_from_callback_arg) before f487f473c82d to match its diff context. On 14/15, Google's UAF-fix commits sit on a different base (auth.cpp 0be3f995) whose context line is LOG(INFO) rather than VLOG(AUTH), so applying the 16 diff directly there may hit a context mismatch on that line. transport_to_callback_arg, transport_from_callback_arg and fdevent_run_on_looper must already exist in the target tree.",
      "The 17 version adds CHECK_NOT_LOOPER_THREAD() in FdConnection::DoTlsHandshake. The 14 to 16 versions do not include it, so leave it out on branches that lack that macro."
     ],
     "confidence": "high",
     "unknowns": [
      "The caller of adbd_auth_tls_handshake in transport.cpp and the exact packet or sequence that triggers it are not shown.",
      "The return type of atransport::connection() is not shown, so it is unclear whether the captured 'connection' is a shared_ptr that keeps the FdConnection alive.",
      "Whether the hardening commit 1c97946b5ca6 was also cherry-picked to the 14 and 15 security branches.",
      "What fdevent_check_looper() does on a violation (abort or log) is not shown.",
      "Whether client/auth.cpp:adb_auth_tls_handshake has been changed after the indexed android-16.0.0_r4 snapshot."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28618": {
     "cve": "CVE-2026-28618",
     "summary": "Adds a check in libopenapv's dec_frm_prepare that returns OAPV_ERR_INVALID_ARGUMENT when the plane-0 width or height of the output image buffer supplied by the caller is smaller than the frame dimensions in the Frame PBU header, preventing a heap buffer overflow.",
     "component": "platform/external/libopenapv, APV decoder (src/oapv.c)",
     "weakness": "Missing bounds/dimension validation leading to heap buffer overflow",
     "root_cause": "The caller allocates the decoder's output image buffer (oapv_imgb_t *imgb) from the AU_INFO dimensions. dec_frm_prepare in src/oapv.c, however, takes the frame dimensions from the Frame PBU header (ctx->fh.fi.frame_width / frame_height) and sets ctx->w / ctx->h from them without checking that imgb is large enough. Per the commit message, a Frame PBU that declares larger dimensions than AU_INFO leads to a heap buffer overflow on that buffer. The material does not show which function performs the out-of-bounds access.",
     "fix_analysis": [
      "src/oapv.c:dec_frm_prepare: at function entry, before ctx->imgb is assigned and before imgb_addref is called, compare imgb->w[0] against ctx->fh.fi.frame_width and imgb->h[0] against ctx->fh.fi.frame_height. If either buffer dimension is smaller, return OAPV_ERR_INVALID_ARGUMENT, so nothing is decoded into the undersized buffer.",
      "Because the check comes before imgb_addref, the early return does not leak a reference on the image buffer.",
      "Single-commit fix. The Android 17 commit (7c797fcb63c8) contains the identical hunk at a slightly different line offset."
     ],
     "completeness": [
      "Covers a mismatch between the Frame PBU frame_width/frame_height and the luma (plane 0) dimensions of the supplied imgb.",
      "Only imgb->w[0] and imgb->h[0] are checked. Chroma plane dimensions (w[1], w[2], h[1], h[2]) are not validated directly. Whether that matters depends on how the caller derives chroma plane sizes, which the material does not show.",
      "The check compares against the unaligned frame_width/frame_height, while ctx->w and ctx->h are rounded up to OAPV_MB_W / OAPV_MB_H. The material does not show whether the block-to-image functions clip to the imgb dimensions, so the effect of the alignment slack cannot be determined from the diff.",
      "The fix does not check that the color/chroma format of imgb (OAPV_CS_GET_FORMAT(imgb->cs)) matches ctx->fh.fi.chroma_format_idc. That path is untouched."
     ],
     "behavior_changes": [
      "dec_frm_prepare now returns OAPV_ERR_INVALID_ARGUMENT when the Frame PBU dimensions exceed the plane-0 dimensions of the supplied output buffer, and such frames are no longer decoded."
     ],
     "preconditions": [
      "An APV bitstream processed by libopenapv's decoder in which a Frame PBU header declares a frame_width or frame_height larger than the dimensions used to allocate the output buffer (AU_INFO).",
      "Per index search, frameworks/av/media/codec2/components/apv/C2SoftApvDec.cpp creates the decoder via oapvd_create, so the Codec2 software APV decoder is a likely entry point. The full call path to dec_frm_prepare is not shown."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin, and platform/external/libopenapv is not present in android-security-14.0.0_r29",
      "15": "not affected: not listed by the bulletin, and platform/external/libopenapv is not present in android-security-15.0.0_r17",
      "16": "same fix: commit b40bceb14205 in android-security-16.0.0_r8, 3/3 lines of the patched region present",
      "17": "same fix: commit 7c797fcb63c8 in android-security-17.0.0_r1 with the identical hunk, 3/3 lines of the patched region present"
     },
     "backport_notes": [
      "The change is a self-contained 4-line insertion at the top of dec_frm_prepare in src/oapv.c with no dependencies. It is identical on 16 and 17; only the hunk offset differs. Forks with a modified dec_frm_prepare should place the check before imgb_addref so that the early return does not leak a reference."
     ],
     "confidence": "high",
     "unknowns": [
      "How the caller (e.g. C2SoftApvDec) sizes the chroma planes of imgb relative to plane 0, and whether the block-to-image functions clip writes to the imgb dimensions.",
      "Which function performs the out-of-bounds access, and whether it is a read or a write.",
      "How the OAPV_ERR_INVALID_ARGUMENT return propagates through the public decode API to callers such as C2SoftApvDec.",
      "Whether the 16-qpr2 and 17-next branches listed by the bulletin carry the same commit (not shown in the material)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28639": {
     "cve": "CVE-2026-28639",
     "summary": "In the libnfc-nci MIFARE Classic NDEF reader, rw_mfc_handle_read_op now sets failed = true in the branch that logs the SafetyNet length-inflation event 178725766, so the operation is marked failed instead of only being logged (commit: 'Terminate MFC NDEF read on length inflation attempt').",
     "component": "platform/packages/modules/Nfc (Android 16/17) and platform/system/nfc (Android 14/15), libnfc-nci tag reader/writer for MIFARE Classic (rw_mfc.cc)",
     "weakness": "Improper handling of a detected error condition leading to a heap out-of-bounds write (per commit message)",
     "root_cause": "In rw_mfc_handle_read_op, an else branch that handles an NDEF length inflation attempt (the commit title says so, and it logs android_errorWriteLog(0x534e4554, \"178725766\")) only emitted the SafetyNet log and did not mark the operation as failed. According to the commit message, this allowed a heap out-of-bounds write. The guarding condition of that branch and the exact pre-fix flow after it are not shown in the material. The r8 code context shows a related check (p_mfc->ndef_length > saved_len after rw_nfc_decodeTlv, on work_offset == 0) that logs the same tag, but its structure does not match the diff pre-image.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: in the else branch that already logs the length inflation attempt (android_errorWriteLog(0x534e4554, \"178725766\")), add `failed = true;` so the operation is marked failed. In the shown context, the TLV-decode failure path does the same.",
      "The fix is a single added line. It adds no new validation; it turns the existing detection of the condition into a failure of the operation."
     ],
     "completeness": [
      "The change covers only the logged length-inflation else branch in rw_mfc_handle_read_op. In the shown r8 context, the combined rw_nfc_decodeTlv-failure / ndef_length > saved_len path already sets failed = true and rolls back ndef_length and ndef_start_pos.",
      "The diff itself does not roll back p_mfc->ndef_length. The material does not establish whether the pre-fix code on every branch restored the length.",
      "What `failed` triggers afterwards (completion callback, state reset, stopping further reads) is not shown. In the shown context, the following work_offset >= ndef_length check and the rw_mfc_readBlock call do not test failed."
     ],
     "behavior_changes": [
      "An NDEF read from a MIFARE Classic tag that reaches the length-inflation branch is now marked failed (failed = true) instead of only being logged. The SafetyNet log entry for 178725766 is still emitted.",
      "No API, format, or configuration change."
     ],
     "preconditions": [
      "The device performs an NDEF read on a MIFARE Classic tag (RW_MFC_STATE_READ_NDEF) via libnfc-nci, which means a tag or tag emulator in physical proximity.",
      "The tag response triggers the length-inflation condition (an NDEF length larger than the one established earlier, as indicated by the commit title and the 178725766 log)."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc e884267aee53 (src/nfc/tags/rw_mfc.cc, hunk at line 1057), one-line `failed = true;` in the same else branch; 2/2 lines present at android-security-14.0.0_r29",
      "15": "same fix: platform/system/nfc e22f0f7b91dd (src/nfc/tags/rw_mfc.cc, hunk at line 1054), identical one-line change; 2/2 lines present at android-security-15.0.0_r17",
      "16": "same fix: platform/packages/modules/Nfc ca73224fb2c2 (libnfc-nci/src/nfc/tags/rw_mfc.cc, hunk at line 1289); 2/2 lines present at android-security-16.0.0_r8",
      "17": "same fix: platform/packages/modules/Nfc 9f4efdd67acb (libnfc-nci/src/nfc/tags/rw_mfc.cc, hunk at line 1378); 2/2 lines present at android-security-17.0.0_r1"
     },
     "backport_notes": [
      "The file is in platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15 and in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) on Android 16/17. On every branch, the hunk is anchored on the existing android_errorWriteLog(0x534e4554, \"178725766\") else branch inside rw_mfc_handle_read_op and is a one-line addition.",
      "On forks, find the branch to patch by the 178725766 log call inside rw_mfc_handle_read_op."
     ],
     "confidence": "medium",
     "unknowns": [
      "The r8 code context (combined `!rw_nfc_decodeTlv(data) || ndef_length > saved_len` check with roll-back and failed = true) does not match the diff pre-image (a separate else branch holding only the log call). The exact pre-fix control flow around the patched line and that branch's guarding condition are not shown.",
      "How `failed` is consumed later in rw_mfc_handle_read_op (callback event, status, state transition, stopping further block reads) is not in the material.",
      "Where p_ndef_buffer is allocated and how its size relates to ndef_length is not shown.",
      "The two Cherrypick-From references suggest two internal commits were combined; the material shows only the single-line result."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28653": {
     "cve": "CVE-2026-28653",
     "summary": "Adds a pre-loop range check in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd (libnfc-nci rw_t3t.cc). The check fails the command with NFC_STATUS_FAILED when first_block + block_count would exceed UINT16_MAX, which stops the 16-bit block_id loop counter from wrapping and writing block descriptors past the end of the command buffer.",
     "component": "platform/packages/modules/Nfc (Android 16/17) and platform/system/nfc (Android 14/15), libnfc-nci Type 3 Tag (T3T) reader/writer, NDEF CHECK/UPDATE command construction",
     "weakness": "Integer wrap of a 16-bit loop counter leading to an unbounded heap out-of-bounds write (missing bounds check)",
     "root_cause": "In rw_t3t.cc, rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd both build a T3T command in a GKI buffer (p_cmd_buf, written through pointer p). Each then loops `for (block_id = first_block; block_id < (first_block + count); block_id++)` and streams a 2- or 3-byte block descriptor per iteration via UINT8_TO_STREAM/UINT16_TO_STREAM. The commit title calls block_id a uint16 loop counter. When first_block + count exceeds 0xFFFF, block_id wraps before it reaches the bound, so the loop keeps streaming descriptors past the end of the heap command buffer (the commit message calls this an unbounded heap out-of-bounds write). The sum was not checked against the 16-bit range. In update_cmd the per-command count is capped by p_cb->ndef_attrib.nbw; the material does not show where the starting block values come from.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: the check `if (ndef_blocks_to_write > UINT16_MAX - first_block_to_write)` is added after the UPDATE header (opcode, IDm, service list, block count) is streamed and the timeout is computed. It is equivalent to first_block_to_write + ndef_blocks_to_write > 0xFFFF. When it triggers, the function logs LOG(ERROR) \"Heap out-of-bounds\", calls android_errorWriteLog(0x534e4554, \"508389055\") and returns NFC_STATUS_FAILED before the block-descriptor loop.",
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: the matching check `if (cur_blocks_to_read > UINT16_MAX - first_block_to_read)` is inserted after the block count is streamed, with the same log, the same android_errorWriteLog call and the same NFC_STATUS_FAILED return before the loop.",
      "Both checks subtract from UINT16_MAX instead of adding, so the comparison itself cannot overflow. This commit leaves the loop and its counter unchanged.",
      "The update_cmd code at android-security-16.0.0_r8 differs from this commit. The check there is `(uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000`, GKI_freebuf(p_cmd_buf) is called before the error return, and the loop bound is cast to uint32_t. This is a likely reason the resolver reports 7/8 patched lines on 16."
     ],
     "completeness": [
      "Covers both T3T NDEF multi-block paths that have the wrapping loop: UPDATE (rw_t3t_send_next_ndef_update_cmd) and CHECK (rw_t3t_send_next_ndef_check_cmd).",
      "The check is only a guard. It does not change the type of block_id or the loop condition, so it works only because it is equivalent to sum > 0xFFFF, which holds in the cherry-picked commit.",
      "In the commit as shown, the new error return comes after p_cmd_buf has been partially written, and the buffer is not freed. The r8 update_cmd code adds GKI_freebuf(p_cmd_buf) on that path, which suggests the original form leaked the buffer. The material does not show whether check_cmd got the same follow-up.",
      "The r8 update_cmd check uses `> 0x10000`, so it lets a sum of exactly 0x10000 through, which the original UINT16_MAX form rejected. Whether that is safe depends on the declared type of block_id, which the material does not show."
     ],
     "behavior_changes": [
      "Building an NDEF UPDATE or CHECK command now fails with NFC_STATUS_FAILED when first_block + block_count > 0xFFFF, so the NDEF read or write to that tag fails instead of entering the wrapping write loop.",
      "On the rejected path there is a new LOG(ERROR) line \"<func>: Heap out-of-bounds\" and an android_errorWriteLog event (tag 0x534e4554, bug 508389055).",
      "No change for block ranges that stay within 16 bits."
     ],
     "preconditions": [
      "The NFC stack performs an NDEF read (CHECK) or write (UPDATE) on a Type 3 tag, reaching rw_t3t_send_next_ndef_check_cmd or rw_t3t_send_next_ndef_update_cmd.",
      "The starting block plus the per-command block count must exceed 0xFFFF. In update_cmd the count depends on p_cb->ndef_attrib.nbw."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc bea12a1b3e25 in android-security-14.0.0_r29, file src/nfc/tags/rw_t3t.cc, same two hunks (offsets 833/975). The resolver reports 7/8 patched lines present at that tag; the material does not show the reason.",
      "15": "same fix: platform/system/nfc 089a1a4123db in android-security-15.0.0_r17, src/nfc/tags/rw_t3t.cc, same hunks (offsets 829/971). 7/8 lines present.",
      "16": "same fix: platform/packages/modules/Nfc 4648c160b825 in android-security-16.0.0_r8, libnfc-nci/src/nfc/tags/rw_t3t.cc (offsets 837/979). The update_cmd code at r8 has a revised check (uint32_t comparison against 0x10000, GKI_freebuf(p_cmd_buf)), which likely accounts for the 7/8 lines present.",
      "17": "same fix: platform/packages/modules/Nfc e55c93242c56 in android-security-17.0.0_r1, libnfc-nci/src/nfc/tags/rw_t3t.cc (offsets 847/989). 7/8 lines present."
     },
     "backport_notes": [
      "The patch is two self-contained hunks in rw_t3t.cc and is the same on 14 to 17 apart from offsets. On 14/15 the file is src/nfc/tags/rw_t3t.cc in platform/system/nfc; on 16/17 it is libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc.",
      "When backporting, consider freeing p_cmd_buf (GKI_freebuf) before returning NFC_STATUS_FAILED, as the r8 update_cmd code does. The cherry-picked commit returns without freeing the command buffer."
     ],
     "confidence": "medium",
     "unknowns": [
      "The declared type of block_id, and whether the follow-up that produced the r8 code (uint32_t cast, `> 0x10000`, GKI_freebuf) also widened it.",
      "Whether rw_t3t_send_next_ndef_check_cmd got the same GKI_freebuf follow-up (only update_cmd is shown at r8).",
      "Why 14, 15 and 17 show 7/8 lines present.",
      "Which tag-side values and which state-machine path lead to first_block + count exceeding 0xFFFF, and where the starting block values come from.",
      "Attacker position and user-interaction requirements are not stated in the material.",
      "Which callers invoke these functions (the index search returned no caller chunk), and whether other paths in rw_t3t.cc have a comparable 16-bit loop."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28662": {
     "cve": "CVE-2026-28662",
     "summary": "Adds a bounds check in p2p_process_prov_disc_bootstrap_req (src/p2p/p2p_pd.c) that rejects a P2P2 PBMA comeback cookie longer than the fixed-size dev->bootstrap_params->cookie buffer, and enables the UBSan \"bounds\" sanitizer on a wpa_supplicant cc_library in wpa_supplicant/Android.bp.",
     "component": "platform/external/wpa_supplicant_8, P2P (Wi-Fi Direct) P2P2 provision discovery / pairing bootstrapping (src/p2p/p2p_pd.c)",
     "weakness": "Missing bounds check (attacker-controlled length checked against frame length but not against a fixed-size heap buffer)",
     "root_cause": "In p2p_process_prov_disc_bootstrap_req, when a Provision Discovery Request carries a PBMA attribute and status P2P_SC_COMEBACK, the code reads a one-byte cookie_len from the frame. It only checked that the attribute was long enough (msg->pbma_info_len < 1 + cookie_len) and never compared cookie_len with the size of the fixed-size cookie array in dev->bootstrap_params. The commit message describes the result as a heap overflow triggered by a malicious Wi-Fi Direct peer sending an oversized PBMA cookie length. In the shown code, cookie_len is next used in the comparison with dev->bootstrap_params->cookie_len and in os_memcmp. The material does not show which access actually overflows.",
     "fix_analysis": [
      "src/p2p/p2p_pd.c:p2p_process_prov_disc_bootstrap_req: right after the existing 'Truncated PBMA' check, adds `if (cookie_len > sizeof(dev->bootstrap_params->cookie)) { p2p_dbg(p2p, \"Too long PBMA cookie\"); return; }`. The frame-supplied cookie_len is now limited to the capacity of the stored cookie buffer before `cookie = pos` and before the os_memcmp against dev->bootstrap_params->cookie. sizeof does not dereference dev->bootstrap_params, so the check is safe even when bootstrap_params is NULL. The later `!dev->bootstrap_params` check still handles that case.",
      "wpa_supplicant/Android.bp: adds \"bounds\" to sanitize.misc_undefined of the cc_library that compiles src/pasn/pasn_initiator.c, pasn_responder.c and pasn_common.c. The commit message calls this the libpasn target, and it sits in the cuttlefish section of the file. The entry joins the existing unsigned-integer-overflow, signed-integer-overflow and cfi settings. This is defense in depth and does not by itself fix the P2P bug."
     ],
     "completeness": [
      "The length check covers the PBMA comeback branch (status == P2P_SC_COMEBACK) of p2p_process_prov_disc_bootstrap_req, which is the only use of cookie_len shown in the material.",
      "The os_memcmp is also guarded by `dev->bootstrap_params->cookie_len != cookie_len`. The material does not show where bootstrap_params->cookie_len is set or whether that code limits it, so it cannot be judged whether the new check is the only guard or a redundant one.",
      "The non-comeback 'PBMA request' branch (WPA_GET_LE16(pos)) is not changed. The shown context does not indicate a length problem there.",
      "The sanitizer change touches only one cc_library in the cuttlefish section of wpa_supplicant/Android.bp. The non-cuttlefish build section and hostapd are not modified in this diff, even though the title mentions hardening hostapd.",
      "No fix is shown for any counterpart in response processing. The material does not include such code, so nothing can be concluded about it."
     ],
     "behavior_changes": [
      "A PBMA comeback Provision Discovery Request whose cookie length exceeds sizeof(dev->bootstrap_params->cookie) is now dropped with the debug log 'Too long PBMA cookie' instead of being processed. The function returns without sending a response, the same way the existing 'Truncated PBMA' case does.",
      "The library built from the modified cc_library in wpa_supplicant/Android.bp now has UBSan bounds checking enabled. This turns detected out-of-bounds array accesses into aborts (general platform knowledge)."
     ],
     "preconditions": [
      "wpa_supplicant includes p2p_process_prov_disc_bootstrap_req (P2P2 support). The patched region is not present at the Android 14 and 15 security tags (0/5 lines).",
      "P2P (Wi-Fi Direct) is active and the device receives a Provision Discovery Request from a peer. The peer has to pass the device lookup shown at the top of the function (p2p_get_device(p2p, sa)).",
      "The frame contains a PBMA attribute with pbma_info_len > 2 and a Status attribute equal to P2P_SC_COMEBACK, and no action frame transmission is pending (p2p->send_action_in_progress is false).",
      "Per index search, the Android 16 framework (packages/modules/Wifi) exposes P2P2 pairing bootstrapping through SupplicantP2pIfaceHalAidlImpl.provisionDiscoveryWithParams. That is the outbound path, not the patched inbound handler."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin. android-security-14.0.0_r29 has 0/5 lines of the patched region.",
      "15": "not affected: not listed by the bulletin. android-security-15.0.0_r17 has 0/5 lines of the patched region.",
      "16": "same fix: commit 3f792f7b7afa in android-security-16.0.0_r8 (matched by Change-Id Ic3c75644dbb7b3ea278335e977ad428ae14d9709), with the same p2p_pd.c and Android.bp hunks. 5/5 patched lines present.",
      "17": "same fix: commit b8d36c897dde in android-security-17.0.0_r1, with identical hunks (the Android.bp hunk starts at line 696 instead of 683). 5/5 patched lines present."
     },
     "backport_notes": [
      "The p2p_pd.c hunk is self-contained. It only requires that the target tree has p2p_process_prov_disc_bootstrap_req with the PBMA comeback branch and a fixed-size cookie array in bootstrap_params.",
      "The Android.bp hunk targets the cuttlefish-section cc_library that contains src/pasn/*.c. In forks with a different Android.bp layout, add the \"bounds\" entry to the equivalent sanitize.misc_undefined list, or leave it out, since it is hardening and not the fix itself."
     ],
     "confidence": "high",
     "unknowns": [
      "Where dev->bootstrap_params->cookie_len and cookie are set, and whether that code limits the length. The material does not show the struct definition or that code.",
      "The exact size of the cookie array in bootstrap_params.",
      "Which access actually overflows (the os_memcmp read or something else). The shown code only exposes the os_memcmp use of cookie_len.",
      "Whether P2P2 is enabled in all Android 16 device builds (the bulletin lists 16, 16-qpr2, 17 and 17-next as affected).",
      "Whether the P2P2 code is entirely absent from Android 14/15 trees, beyond the patched region not being present.",
      "Which hostapd changes, if any, the title refers to. The diff touches only wpa_supplicant/Android.bp and src/p2p/p2p_pd.c."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-33636": {
     "cve": "CVE-2026-33636",
     "summary": "Restricts the NEON palette-expansion loops in arm/palette_neon_intrinsics.c to full pixel chunks only, drops the post-loop count correction, and restores the destination pointer before handing the remaining tail pixels to the scalar fallback.",
     "component": "platform/external/libpng, ARM NEON palette expansion (arm/palette_neon_intrinsics.c)",
     "weakness": "Missing bounds check on a fixed-chunk loop leading to out-of-bounds read and write (buffer overflow)",
     "root_cause": "png_do_expand_palette_rgba8_neon and png_do_expand_palette_rgb8_neon iterate over the row in fixed chunks (pixels_per_chunk: 4 pixels for RGBA8 via vld1q_lane_u32/vst1q_u32, 8 pixels for RGB8 via vst3_u8) using `for (i = 0; i < row_width; i += pixels_per_chunk)`. When row_width is not a multiple of pixels_per_chunk, the last iteration starts with less than a full chunk remaining but still loads a full chunk of palette indices from `sp = *ssp - i` and stores a full chunk to `dp = *ddp - (i << 2)` (or `- ((i << 1) + i)`). This reads and writes past the valid region of the row buffer. The post-loop `if (i != row_width) i -= pixels_per_chunk;` only corrected the returned count after the out-of-bounds accesses had already happened. In addition, the pre-adjustment of *ddp (by pixels_per_chunk*sizeof(element) - 1 bytes) was never undone before the pointers were handed back to the scalar code in pngrtran.c.",
     "fix_analysis": [
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: the loop condition changes to `i + pixels_per_chunk <= row_width`, so an iteration runs only when a full 4-pixel chunk remains. NEON code no longer touches the final partial chunk.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: the `if (i != row_width) i -= pixels_per_chunk;` correction is removed, because i can no longer overshoot row_width.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: before the final `*ssp = *ssp - i; *ddp = *ddp - (i << 2);` the code adds `*ddp = *ddp + (pixels_per_chunk * 4 - 1);` to undo the earlier pre-adjustment. Per the new code comment, this makes the dp handed to the scalar fallback in pngrtran.c point to the correct position.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgb8_neon: same treatment with the 8-pixel chunk: loop condition `i + pixels_per_chunk <= row_width`, post-loop correction removed, and `*ddp = *ddp + (pixels_per_chunk * 3 - 1);` added before the pointer decrement.",
      "The returned value i (number of pixels processed by NEON) is now always a multiple of pixels_per_chunk and at most row_width. The remaining row_width - i tail pixels are left to the scalar path."
     ],
     "completeness": [
      "Both NEON palette expansion entry points in the file (RGBA8 and RGB8) are fixed with the same pattern. No other loops in arm/palette_neon_intrinsics.c appear in the diff.",
      "The fix relies on the scalar fallback in pngrtran.c correctly processing the remaining row_width - i pixels using the handed-back ssp/ddp. That caller code is not in the material, so its correctness cannot be verified from the diff.",
      "png_riffle_palette_neon in the same file is not modified. The material shows no issue there.",
      "Rows shorter than one chunk now get zero NEON iterations and the function returns 0, leaving the whole row to the scalar path."
     ],
     "behavior_changes": [
      "The NEON loops no longer run a final partial-chunk iteration. The tail pixels (row_width mod 4 for RGBA8, row_width mod 8 for RGB8) are left entirely to the scalar code in pngrtran.c.",
      "The *ddp value returned to the caller now differs from the old code by (pixels_per_chunk*4 - 1) or (pixels_per_chunk*3 - 1) bytes, because the pre-adjustment is undone before handoff.",
      "No new error paths or API changes."
     ],
     "preconditions": [
      "libpng built with PNG_ARM_NEON_IMPLEMENTATION == 1 (ARM/ARM64 NEON path compiled in), per the #if guard in arm/palette_neon_intrinsics.c.",
      "Decoding a palette (PNG_COLOR_TYPE_PALETTE) image with palette-to-RGB/RGBA expansion enabled, e.g. via png_set_palette_to_rgb as done in frameworks/base/libs/androidfw/Png.cpp readPng, per index search.",
      "Row width in pixels not a multiple of the NEON chunk size (4 for the RGBA8 path, 8 for the RGB8 path)."
     ],
     "per_major": {
      "14": "same fix: platform/external/libpng 843457e59be7 in android-security-14.0.0_r29, identical diff to the Android 16 commit (same file blob 92c7d6f9f -> bdd15849d).",
      "15": "same fix: platform/external/libpng fa7e33a0a0cc in android-security-15.0.0_r17, identical diff to the Android 16 commit.",
      "16": "same fix: platform/external/libpng 0b9147198ed6 in android-security-16.0.0_r8 (cherry-pick, Change-Id I56e19b3e1a226de21e91e400124e36021357689a).",
      "17": "same fix: platform/external/libpng 7d98050ef8d9 in android-security-17.0.0_r1. Same logical change, only line offsets differ (the file header lacks the leading blank line, blob 3068e9b6e -> f3355bef5)."
     },
     "backport_notes": [
      "Single self-contained commit touching only arm/palette_neon_intrinsics.c. It should apply readily where the two NEON functions still contain the `i < row_width` loop and the `if (i != row_width)` correction (the Android 17 version differs only in line offsets). Check that the scalar fallback in pngrtran.c expects the un-pre-adjusted dp, because the handoff pointer value changes with this patch."
     ],
     "confidence": "high",
     "unknowns": [
      "The caller side in pngrtran.c (how the scalar fallback uses ssp/ddp and the returned count) is not in the material, so it cannot be confirmed whether the old pre-adjusted dp was compensated there.",
      "Whether output pixel data for the row tail was correct before the fix is not shown.",
      "The material does not show which Android process or image-decoding path the bulletin's Critical EoP rating is based on.",
      "Whether the unpatched NEON path was actually compiled into the affected builds of each release (PNG_ARM_NEON_IMPLEMENTATION setting) is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45515": {
     "cve": "CVE-2026-45515",
     "summary": "Corrects the frame_size argument passed to opus_decode() in the A2DP Opus decoder from a byte count to the number of samples per channel the output buffer can hold, adds a channel-count sanity check, and removes a loop that re-decoded the same packet once per frame.",
     "component": "platform/packages/modules/Bluetooth, system/stack/a2dp (A2DP Opus vendor codec decoder)",
     "weakness": "Heap buffer overflow caused by a wrong size unit passed to the decoder (bytes instead of samples per channel), i.e. an incorrect bounds argument.",
     "root_cause": "In system/stack/a2dp/a2dp_vendor_opus_decoder.cc, a2dp_vendor_opus_decoder_decode_packet() called opus_decode() with frame_size = A2DP_OPUS_DECODE_BUFFER_LENGTH. According to the commit message this constant is the buffer size in bytes, while opus_decode() expects frame_size as samples per channel for the int16_t output buffer. The commit treats the output as fixed at 2 channels (A2DP_OPUS_CODEC_OUTPUT_CHS), so the buffer holds only A2DP_OPUS_DECODE_BUFFER_LENGTH / (2 * sizeof(int16_t)) samples per channel. An Opus packet with a large frame duration could therefore overflow decode_buf. The added test uses a 60 ms packet of 2880 samples per channel. The old code also looped numFrames times over the whole packet, decoding the same packet again on each pass, and computed the callback length from the packet's channel count (numChannels) instead of the output channel count.",
     "fix_analysis": [
      "system/stack/a2dp/a2dp_vendor_opus_decoder.cc:a2dp_vendor_opus_decoder_decode_packet: both opus_decode() calls (the normal decode and the NULL/0 concealment retry) now pass A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t)) as frame_size. This limits the decoder to the number of samples per channel that decode_buf can hold for stereo output.",
      "Same function: adds an early reject when opus_packet_get_nb_channels() returns anything other than 1 or 2. Because numChannels is declared uint32_t, a negative return value also fails this test. The function logs 'Invalid numChannels' and returns false.",
      "Same function: removes the for (frame < numFrames) loop and the re-read of numChannels inside it. The packet is now decoded exactly once and decode_callback is called once.",
      "Same function: frame_len passed to decode_callback is now ret_val * A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(decode_buf[0]) instead of ret_val * numChannels * sizeof(decode_buf[0]).",
      "Same function: the two log::error lines that dumped packet metrics (including the manually parsed num_frames = pBuffer[0] & 0xf) are replaced by one log::info line.",
      "system/stack/test/a2dp/a2dp_opus_unittest.cc: adds decode_packet_60ms_overflow_repro. The test encodes a real 60 ms stereo packet with libopus, prepends the 1-byte A2DP header and passes it to decoder_iface_->decode_packet."
     ],
     "completeness": [
      "Both opus_decode() calls in the function (the normal decode and the concealment retry) now use the same corrected bound.",
      "The bound is derived from A2DP_OPUS_CODEC_OUTPUT_CHS and A2DP_OPUS_DECODE_BUFFER_LENGTH. The allocation of decode_buf and the value of that constant are not shown in the material, so correctness depends on the commit message's statements that the constant is a byte length and that the output has 2 channels.",
      "frame_len now assumes 2 output channels whatever the packet's channel count. This is correct only if the OpusDecoder is created with A2DP_OPUS_CODEC_OUTPUT_CHS channels, and the init path is not shown.",
      "The new numChannels check is not used in the frame_size bound, which comes only from A2DP_OPUS_CODEC_OUTPUT_CHS. The diff shows it as a packet-validity check.",
      "No other decoder (SBC, AAC, LDAC, aptX, LC3) is touched by this change. The material does not show whether they share the pattern."
     ],
     "behavior_changes": [
      "Opus packets for which opus_packet_get_nb_channels() returns something other than 1 or 2 are now dropped with an error log, and decode_packet returns false.",
      "Packets containing multiple Opus frames are decoded once instead of numFrames times, so decode_callback is called once per packet.",
      "The length reported to decode_callback is always computed for 2 output channels. For mono-coded packets this differs from the old numChannels-based value.",
      "Per-packet metric logging goes from two log::error lines to a single log::info line."
     ],
     "preconditions": [
      "Opus-encoded A2DP media packets reach a2dp_vendor_opus_decoder_decode_packet, meaning the device is decoding the Opus vendor codec.",
      "The packet decodes to more samples per channel than decode_buf can hold, for example a 60 ms frame at 48 kHz as in the added test.",
      "The decoder control block is initialised with a decode callback. The test does this through InitializeDecoder."
     ],
     "per_major": {
      "14": "adapted fix: commit 4f7ed05b1660 in android-security-14.0.0_r29. Same logic, but it uses the printf-style LOG_ERROR/LOG_INFO macros and reflows the frameSize/frameLen lines. In the test file, #include <stdio.h> is replaced by <opus.h> (base/logging.h is already present).",
      "15": "adapted fix: commit b837ddeb7125 in android-security-15.0.0_r17. Same logic with log::error/log::info, and the frameSize/frameLen lines are reflowed. The test adds <stdio.h> and <opus.h>.",
      "16": "same fix: commit 5eefb7cb0b0c in android-security-16.0.0_r8, the analysed diff.",
      "17": "same fix: commit 3b78dbef3ec6 in android-security-17.0.0_r1, with the same hunks as Android 16."
     },
     "backport_notes": [
      "The fix depends on A2DP_OPUS_CODEC_OUTPUT_CHS and on the decoder being created as stereo, so check both in the target tree before applying. On Android 14 the logging uses LOG_ERROR/LOG_INFO with %d formats instead of log::error/log::info.",
      "The new unit test calls opus_encoder_create/opus_encode, so net_test_stack_a2dp_codecs_native must link libopus. The diff includes no Android.bp change, so check the test's dependencies on older branches."
     ],
     "confidence": "high",
     "unknowns": [
      "The numeric value of A2DP_OPUS_DECODE_BUFFER_LENGTH and the allocation of decode_buf (a2dp_vendor_opus_decoder_init) are not shown.",
      "The commit message and a code comment say the OpusDecoder is created with A2DP_OPUS_CODEC_OUTPUT_CHS channels, but the init code is not in the material.",
      "The receive path into a2dp_vendor_opus_decoder_decode_packet, the Bluetooth role (sink) and the codec negotiation conditions are not shown. The index search returned only the header and the unit test."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45531": {
     "cve": "CVE-2026-45531",
     "summary": "Hardens fsck.exfat boot-region validation in read_boot_region against integer overflow/underflow from attacker-controlled vol_length, clu_offset and clu_count fields, and widens DIV_ROUND_UP to 64-bit arithmetic.",
     "component": "platform/external/exfatprogs, fsck.exfat (fsck/fsck.c, include/libexfat.h)",
     "weakness": "Integer overflow / underflow leading to insufficient bounds validation (missing bounds check on boot sector fields)",
     "root_cause": "In fsck/fsck.c:read_boot_region the sanity checks on the exFAT boot sector used overflow-prone arithmetic. The volume-size check multiplied the 64-bit vol_length by EXFAT_SECTOR_SIZE(bs) before comparing it to bd->size, so a large vol_length could wrap and pass the check. Nothing checked that clu_offset is smaller than vol_length, so subtracting clu_offset from vol_length could underflow (per the fix's comment). Nothing capped clu_count at the spec maximum EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5). In include/libexfat.h, DIV_ROUND_UP computed (__i + __d - 1) in the operand's native type, so large inputs could wrap before the division. The material does not show how later fsck code used the unvalidated values.",
     "fix_analysis": [
      "Commit 9b79c19de5cf (ANDROID: Add support for building exfat tests.) changes Android.bp, fsck/Android.bp and lib/Android.bp. It adds host_supported: true to libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat, so these can be built for host (for tests), and fixes indentation of the exfat2img entry. It is a build-only change.",
      "Commit c0e2d5cc6945, fsck/fsck.c:read_boot_region: replaces `vol_length * EXFAT_SECTOR_SIZE(bs) > bd->size` with `vol_length > (bd->size >> bs->bsx.sect_size_bits)`. The scaling moves to the device-size side as a shift instead of a multiply on the untrusted value, so the comparison cannot wrap.",
      "fsck/fsck.c:read_boot_region: adds a new check that rejects the volume (goto err) when le32_to_cpu(bs->bsx.clu_offset) >= le64_to_cpu(bs->bsx.vol_length), to prevent underflow when clu_offset is subtracted from vol_length.",
      "fsck/fsck.c:read_boot_region: adds a new check that rejects the volume when le32_to_cpu(bs->bsx.clu_count) > EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5). Both new checks sit before the existing clu_count * EXFAT_CLUSTER_SIZE(bs) check.",
      "include/libexfat.h: DIV_ROUND_UP now casts __i to uint64_t, so the addition runs in 64 bits. This affects every DIV_ROUND_UP user in exfatprogs.",
      "Adds the regression image tests/clu_count_overflow/exfat.img.tar.xz."
     ],
     "completeness": [
      "Covers overflow in the vol_length size check, underflow of vol_length - clu_offset, out-of-spec clu_count, and wrap inside DIV_ROUND_UP (for all users, since the macro is shared).",
      "Leaves unchanged the check right after the new ones, `le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > bd->size`. clu_count is capped only at 0xFFFFFFF5, and EXFAT_CLUSTER_SIZE(bs) is an int shift up to 32 MB. Unlike the vol_length check, this product was not rewritten as a shift. Whether that matters depends on later use of clu_count, which the material does not show.",
      "Only read_boot_region in fsck is changed. The material does not show whether other exfatprogs tools (mkfs, tune, label, dump, exfat2img) validate these fields.",
      "The build commit only enables host builds and does not change device behavior."
     ],
     "behavior_changes": [
      "fsck.exfat now rejects images (read_boot_region goes to err) where clu_offset >= vol_length or clu_count > 0xFFFFFFF5. In verbose mode it prints the new messages 'cluster offset (%u) is larger than volume length' and 'too large cluster count: %u, max allowed: %u'.",
      "The vol_length check is now evaluated as vol_length > (bd->size >> sect_size_bits). Images that used to pass because of wraparound are now rejected.",
      "DIV_ROUND_UP is now computed in 64 bits for all callers in exfatprogs. Results for large inputs that used to wrap now differ."
     ],
     "preconditions": [
      "fsck.exfat runs against an exFAT volume whose boot sector (vol_length, clu_offset, clu_count, sect_size_bits, sect_per_clus_bits) is attacker-controlled, for example removable media.",
      "The image must pass the earlier checks in read_boot_region: sector size 512 to 4K, cluster size <= 32 MB, fs_version 1.0, num_fats == 1.",
      "On Android, fsck.exfat is typically invoked by vold when mounting public exFAT volumes (general platform knowledge)."
     ],
     "per_major": {
      "14": "Adapted fix: commit 54cf500d4abd in android-security-14.0.0_r29 applies the same three read_boot_region changes (around line 709) and the DIV_ROUND_UP change. Its exfat_err calls are not wrapped in `if (verbose)`. The host_supported build commit is not shown for 14. The resolver reports 7/8 patched-region lines present.",
      "15": "Resolved in android-security-15.0.0_r17. The material shows only the build commit 2957a37c1d7f (host_supported). The resolver reports 8/8 patched-region lines present, which suggests the fsck.c/libexfat.h change is present, but that commit is not shown.",
      "16": "Same fix: both commits (9b79c19de5cf build support and c0e2d5cc6945 functional fix) are in android-security-16.0.0_r8, and the code context shows the patched lines. The resolver nevertheless reports only 6/8 patched-region lines present.",
      "17": "Resolved in android-security-17.0.0_r1. The material shows only the build commit a6faf5dfd40a. The resolver reports 8/8 patched-region lines present."
     },
     "backport_notes": [
      "The functional fix (c0e2d5cc6945: fsck/fsck.c, include/libexfat.h, test image) does not depend on the host_supported build commit. The build commit is only needed to build exfatprogs for host so its tests can run.",
      "On branches where exfat_err calls in read_boot_region are not gated by `if (verbose)` (as in the Android 14 variant 54cf500d4abd), drop the verbose guards."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which downstream computation (the clu_offset subtraction, DIV_ROUND_UP users, buffer sizing) caused the impact rated Critical. The diff shows only the validation hardening.",
      "Whether the Android 14 branch also received the host_supported build commit.",
      "Whether the functional fsck.c commit exists as a separate commit on 15 and 17. Only the build commits are shown there.",
      "Why the resolver reports 6/8 lines present on Android 16 when the fix commit is in android-security-16.0.0_r8.",
      "The types of bd->size and of le32_to_cpu's result, and so the exact width of the remaining clu_count * EXFAT_CLUSTER_SIZE comparison."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49879": {
     "cve": "CVE-2026-49879",
     "summary": "In libnfc-nci Type 3 Tag NDEF handling, rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd now compute the block range bound and loop in uint32_t (block_id widened from uint16_t), the range check becomes first_block + count > 0x10000, and the command buffer is freed with GKI_freebuf on that error path.",
     "component": "platform/packages/modules/Nfc (Android 16/17) and platform/system/nfc (Android 14/15), libnfc-nci Type 3 Tag reader/writer (rw_t3t.cc)",
     "weakness": "Integer width/overflow handling in a block-range loop (uint16_t loop variable vs. 16-bit+16-bit sum) with a resource leak (unfreed GKI command buffer) on the bounds-check error path",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, block_id was a uint16_t, while the loop limit (first_block_to_write + ndef_blocks_to_write, or first_block_to_read + cur_blocks_to_read) is the sum of two uint16_t values. The pre-existing guard (count > UINT16_MAX - first_block) rejected any sum of 65536 or more, so behind it the uint16_t block_id could not wrap. It also rejected the legitimate case where the last block ID is 65535. On that error path the p_cmd_buf already allocated by rw_t3t_get_cmd_buf() was not released. The fix commit describes the issue as a potential integer overflow in Type 3 Tag block operations; a uint16_t block_id would only fail to terminate if the sum were allowed to reach 0x10000, which is why the relaxed guard and the uint32_t widening go together.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: block_id is moved from a uint16_t declaration to the uint32_t declaration line (uint32_t timeout, block_id), so the loop variable can represent 65536 and terminate against a 32-bit limit.",
      "rw_t3t_send_next_ndef_update_cmd: the guard is rewritten as ((uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000), i.e. computed in uint32_t and allowing block IDs up to 65535 inclusive; GKI_freebuf(p_cmd_buf) is added before the LOG(ERROR) / android_errorWriteLog(0x534e4554, \"508389055\") error path so the command buffer is no longer leaked.",
      "rw_t3t_send_next_ndef_update_cmd: the for-loop bound becomes block_id < ((uint32_t)first_block_to_write + ndef_blocks_to_write), matching the check.",
      "rw_t3t_send_next_ndef_check_cmd: identical treatment: block_id moved to uint32_t (uint32_t ndef_bytes_remaining, block_id), guard changed to ((uint32_t)first_block_to_read + cur_blocks_to_read > 0x10000) with GKI_freebuf(p_cmd_buf) added, and loop bound cast to uint32_t.",
      "Single commit touching only rw_t3t.cc; no header or caller changes."
     ],
     "completeness": [
      "Both NDEF block-iteration paths in rw_t3t.cc (update and check) are fixed symmetrically.",
      "The fix only touches these two functions; other Type 3 Tag command builders in rw_t3t.cc (e.g. rw_t3t_send_update_ndef_attribute_cmd) are not changed and are not shown in the material, so whether they have a comparable loop is not visible.",
      "The new check permits first_block + count == 0x10000 (last block ID 65535), which the old check rejected; termination then relies on block_id being uint32_t, so both parts of the change must be applied together.",
      "The return value and remaining error handling on the bounds-check path are not shown in the diff hunk."
     ],
     "behavior_changes": [
      "Block ranges ending exactly at block ID 65535 (first + count == 65536) are now accepted and encoded instead of being rejected as Heap out-of-bounds.",
      "On the bounds-check failure path the GKI command buffer is now freed, removing a buffer-pool leak; the LOG(ERROR) and android_errorWriteLog(0x534e4554, \"508389055\") reporting are unchanged.",
      "No API, timeout or protocol encoding changes for in-range blocks are visible in the diff."
     ],
     "preconditions": [
      "Device is in NFC reader/writer mode with an activated Type 3 Tag and an NDEF operation in progress; from the function names, rw_t3t_send_next_ndef_update_cmd appears to serve NDEF writes and rw_t3t_send_next_ndef_check_cmd NDEF reads (callers not found per index search).",
      "The block range (first_block_to_write/first_block_to_read and the per-command block count) comes from state held in p_cb; how these values are populated is not shown in the material.",
      "Bulletin rates this as Critical EoP; no further trigger details are provided in the material."
     ],
     "per_major": {
      "14": "same fix, applied in platform/system/nfc commit c1fa4599a13e at src/nfc/tags/rw_t3t.cc (android-security-14.0.0_r29); identical hunks, only line offsets differ.",
      "15": "same fix, applied in platform/system/nfc commit 78dd34a6a026 at src/nfc/tags/rw_t3t.cc (android-security-15.0.0_r17); identical hunks.",
      "16": "same fix, platform/packages/modules/Nfc commit 92d884176dcd at libnfc-nci/src/nfc/tags/rw_t3t.cc (android-security-16.0.0_r8), cherry-pick of 241c49442390.",
      "17": "same fix, platform/packages/modules/Nfc commit 442e948e4be2 at libnfc-nci/src/nfc/tags/rw_t3t.cc (android-security-17.0.0_r1); identical hunks."
     },
     "backport_notes": [
      "The patch presupposes the earlier bounds check tagged android_errorWriteLog(0x534e4554, \"508389055\") already exists in both functions; on trees without it, add the full check (with GKI_freebuf) together with the uint32_t changes. The file lives at src/nfc/tags/rw_t3t.cc in platform/system/nfc for 14/15 and at libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc for 16/17; the hunks apply unchanged apart from path and line offsets."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the pre-existing UINT16_MAX guard was present in the originally vulnerable baseline or was itself added shortly before this change; the material shows only the final diff against a tree that already had it.",
      "How first_block_to_write/first_block_to_read and the block counts are derived from tag data, and therefore the exact attacker-influenced input.",
      "The return value set on the bounds-check error path and how callers handle it.",
      "Why the bulletin rates this Critical EoP given that the visible pre-fix guard already prevented the uint16_t wrap; the material gives no exploitation details."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49882": {
     "cve": "CVE-2026-49882",
     "summary": "In libnfc-nci rw_mfc.cc rw_mfc_handle_read_op, the fix saves ndef_length and ndef_start_pos before rw_nfc_decodeTlv on the first block read, treats a decoded ndef_length larger than the saved value as a failure, and rolls both fields back to the saved values so an inflated length no longer stays in the control block.",
     "component": "platform/packages/modules/Nfc (Android 14/15: platform/system/nfc), libnfc-nci Mifare Classic tag reader/writer (rw_mfc.cc), running in com.android.nfc",
     "weakness": "Heap out-of-bounds write (CWE-787): a tag-controlled NDEF TLV length larger than the pre-decode ndef_length was kept in the control block without rollback",
     "root_cause": "In rw_mfc_handle_read_op, on the first block of an NDEF read (p_mfc->work_offset == 0), rw_nfc_decodeTlv(data) parses the TLV from the tag response and updates p_mfc->ndef_length and p_mfc->ndef_start_pos. The old code saved ndef_length into a local saved_length. When saved_length < ndef_length it skipped the copy into p_ndef_buffer, logged SafetyNet id 178725766 and set failed. It never restored the control block, so the inflated p_mfc->ndef_length and the decoded ndef_start_pos stayed in p_mfc. The copy loop into p_mfc->p_ndef_buffer[p_mfc->work_offset] is bounded only by p_mfc->ndef_length, and the following `work_offset >= ndef_length` check also reads p_mfc->ndef_length. The commit describes the result as a heap buffer overflow (OOB write) in com.android.nfc during Mifare Classic NDEF reads. The exact path from the retained length to the write is not shown in the material.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: removes the function-scope local saved_length. Inside the work_offset == 0 branch, it now saves p_mfc->ndef_length (saved_len, uint16_t) and p_mfc->ndef_start_pos (saved_pos, uint8_t) just before calling rw_nfc_decodeTlv(data).",
      "The failure condition becomes `!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len`. In either case the code logs 'FAILED finding TLV', calls android_errorWriteLog(0x534e4554, \"178725766\") only for the inflation case, restores p_mfc->ndef_length = saved_len and p_mfc->ndef_start_pos = saved_pos, and sets failed = true. The key change is the rollback: the control block no longer keeps a decoded length larger than the value it held before decoding.",
      "The copy guard is simplified to `if (!failed)`. The old `else { android_errorWriteLog(...); failed = true; }` branch is removed because the check and the logging moved into the TLV block. The copy loop `while ((offset < len) && (p_mfc->work_offset < p_mfc->ndef_length))` has the same logic and is only reformatted.",
      "Because offset = p_mfc->ndef_start_pos is assigned after the rollback, a failed decode now uses the pre-decode start position."
     ],
     "completeness": [
      "Within the shown hunk, the copy loop into p_ndef_buffer is still bounded by p_mfc->ndef_length. After the work_offset == 0 decode, that value can no longer be larger than its pre-decode value. Whether rw_nfc_decodeTlv is called on other paths is not shown.",
      "The check is strictly `>`: a decoded length equal to or smaller than the saved length is accepted. This matches the old `saved_length >= ndef_length` semantics.",
      "The diff does not touch rw_nfc_decodeTlv or the code that first sets ndef_length and allocates p_ndef_buffer. The material does not show whether those paths validate the length consistently.",
      "The rest of rw_mfc_handle_read_op (the completion check and next-block read scheduling after this hunk) is not in the diff. The commit message says control flow was updated to prevent further block reads on failure. In the shown hunk this happens through `failed` being set together with the rollback. No separate control-flow hunk is visible."
     ],
     "behavior_changes": [
      "A Mifare Classic tag whose first-block TLV declares an NDEF length larger than the previously held ndef_length still fails the read, but the control block is now restored to its previous ndef_length/ndef_start_pos instead of keeping the inflated values.",
      "SafetyNet log android_errorWriteLog(0x534e4554, \"178725766\") is now emitted only for the inflation case. It is no longer emitted when rw_nfc_decodeTlv simply fails.",
      "No change for well-formed tags whose decoded length is less than or equal to the saved length."
     ],
     "preconditions": [
      "NFC is enabled on the device and com.android.nfc is performing an NDEF read of a Mifare Classic tag (RW_MFC path). This requires physical proximity to a tag or an emulated tag.",
      "The first block read response (p_mfc->work_offset == 0) carries an NDEF TLV whose decoded length is larger than the ndef_length the control block held before decoding. rw_mfc_handle_read_op is a static function in rw_mfc.cc, and per index search no callers outside the Nfc component were found."
     ],
     "per_major": {
      "14": "same fix, applied as platform/system/nfc 7eaf8e16d9ad (src/nfc/tags/rw_mfc.cc) in android-security-14.0.0_r29. The logic is identical; only the log macro differs (DLOG_IF(INFO, nfc_debug_enabled) instead of LOG(VERBOSE)).",
      "15": "same fix, applied as platform/system/nfc 5dd24f8b4a6d (src/nfc/tags/rw_mfc.cc) in android-security-15.0.0_r17; identical hunk.",
      "16": "same fix, platform/packages/modules/Nfc b79e5fd8cf14 (libnfc-nci/src/nfc/tags/rw_mfc.cc) in android-security-16.0.0_r8, a cherry-pick of 50df1ba6f310.",
      "17": "same fix, platform/packages/modules/Nfc 19fc329133fe (libnfc-nci/src/nfc/tags/rw_mfc.cc) in android-security-17.0.0_r1; identical hunk at a later line offset."
     },
     "backport_notes": [
      "The fix is a single self-contained change in rw_mfc_handle_read_op with no new dependencies. The file is src/nfc/tags/rw_mfc.cc in platform/system/nfc for Android 14/15 and libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc for 16/17. When porting, only the verbose-log macro needs to match the branch."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show rw_nfc_decodeTlv, how p_ndef_buffer is allocated and sized, or the code after the hunk (completion check and next-block scheduling). The exact sequence by which the retained inflated ndef_length led to the out-of-bounds write is therefore inferred from the diff and commit message, not shown.",
      "Whether rw_nfc_decodeTlv is called, or ndef_length is changed, on other paths is not shown.",
      "Whether the second bug id (503553866) covers a distinct issue is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49884": {
     "cve": "CVE-2026-49884",
     "summary": "In rw_mfc_handle_read_op (libnfc-nci rw_mfc.cc), the fix snapshots ndef_length and ndef_start_pos before rw_nfc_decodeTlv on the first Mifare Classic NDEF block read. A decoded ndef_length larger than the previous value is now treated as a decode failure, and both fields are rolled back so the inflated length does not stay in the control block.",
     "component": "platform/packages/modules/Nfc (libnfc-nci, Mifare Classic reader/writer, libnfc-nci/src/nfc/tags/rw_mfc.cc); platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15",
     "weakness": "Heap buffer overflow (out-of-bounds write) per the commit message. The copy bound p_mfc->ndef_length could be inflated by TLV decoding of tag data, and the old code did not roll it back (missing rollback of a length field).",
     "root_cause": "In rw_mfc_handle_read_op, on the first block read (p_mfc->work_offset == 0), rw_nfc_decodeTlv(data) updates p_mfc->ndef_length and p_mfc->ndef_start_pos. The copy loop writes p[offset] into p_mfc->p_ndef_buffer[p_mfc->work_offset] while work_offset < ndef_length, so ndef_length is the loop bound. The old code compared saved_length >= p_mfc->ndef_length and on mismatch set failed = true and called android_errorWriteLog. It never restored the inflated ndef_length / ndef_start_pos in p_mfc, so the inflated value stayed in the control block and was used by later logic such as the trailing `if (p_mfc->work_offset >= p_mfc->ndef_length)`. saved_length was also re-snapshotted on every block read, so it did not preserve the pre-decode value across reads. The commit message describes the result as a heap buffer overflow in com.android.nfc during Mifare Classic NDEF reads (b/503557185).",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: the function-scope `saved_length` (snapshotted on every block read) is removed. Inside the `p_mfc->work_offset == 0` branch, local `saved_len = p_mfc->ndef_length` and `saved_pos = p_mfc->ndef_start_pos` are captured immediately before rw_nfc_decodeTlv(data).",
      "The failure condition becomes `!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len`, so a decode that succeeds but grows ndef_length is handled the same way as a decode failure. The android_errorWriteLog(0x534e4554, \"178725766\") log is kept and is emitted only for the inflation case.",
      "On failure, the fix runs `p_mfc->ndef_length = saved_len; p_mfc->ndef_start_pos = saved_pos;` and sets failed = true. The control block therefore no longer keeps an inflated length, and the copy loop bound and the later `work_offset >= ndef_length` comparison use the pre-decode values.",
      "The copy guard is simplified to `if (!failed)`, and the separate `saved_length >= ndef_length` else-branch is deleted. After the branch, `offset = p_mfc->ndef_start_pos` now reads the rolled-back start position on failure."
     ],
     "completeness": [
      "Covers the only place in the diff where rw_nfc_decodeTlv changes ndef_length/ndef_start_pos during a read (the work_offset == 0 branch in rw_mfc_handle_read_op). The rollback restores both fields.",
      "The check only rejects growth (ndef_length > saved_len). A decode that shrinks ndef_length is accepted. Whether that is safe depends on how p_ndef_buffer is sized, which the material does not show.",
      "The copy loop itself is unchanged and is still bounded by ndef_length, not by an explicit buffer-size field. The code that sizes p_ndef_buffer and the handling of `failed` after this hunk are not in the material, so the diff alone cannot confirm that further block reads stop on failure, as the commit message states.",
      "Other Mifare Classic paths (format, write, detect NDEF) in rw_mfc.cc are not touched by this commit. The material does not show whether they have comparable logic."
     ],
     "behavior_changes": [
      "A first-block TLV decode that reports a larger NDEF length than the previous ndef_length is now treated as a read failure (failed = true), and the original ndef_length / ndef_start_pos are restored. Previously failed was set but the inflated values stayed in place.",
      "The android_errorWriteLog(0x534e4554, \"178725766\") event is now logged only for the inflation case inside the first-read branch, not from the generic else-branch.",
      "No API or return-code changes are visible. The \"FAILED finding TLV\" log message (LOG(VERBOSE), DLOG_IF on Android 14) now also fires for the inflation case."
     ],
     "preconditions": [
      "The NFC stack must be performing a Mifare Classic NDEF read handled by rw_mfc_handle_read_op (commit message: Mifare Classic NDEF reads in com.android.nfc), with the tag supplying the data.",
      "The patched path is the first block read of the NDEF read operation (p_mfc->work_offset == 0), where rw_nfc_decodeTlv parses the TLV from tag data and can change ndef_length relative to its pre-decode value.",
      "rw_mfc_handle_read_op is declared static in rw_mfc.cc. Per index search, no other chunk mentioning it appeared among the top results, but that search may be incomplete."
     ],
     "per_major": {
      "14": "Same fix, applied to platform/system/nfc src/nfc/tags/rw_mfc.cc (commit 7eaf8e16d9ad, android-security-14.0.0_r29). The logic is identical; only the log macro differs (DLOG_IF(INFO, nfc_debug_enabled) instead of LOG(VERBOSE)).",
      "15": "Same fix, applied to platform/system/nfc src/nfc/tags/rw_mfc.cc (commit 5dd24f8b4a6d, android-security-15.0.0_r17). Identical to the 16 diff apart from line offsets.",
      "16": "Same fix: this commit, b79e5fd8cf14 in platform/packages/modules/Nfc, libnfc-nci/src/nfc/tags/rw_mfc.cc (android-security-16.0.0_r8), cherry-picked from 50df1ba6f310.",
      "17": "Same fix in platform/packages/modules/Nfc libnfc-nci/src/nfc/tags/rw_mfc.cc (commit 19fc329133fe, android-security-17.0.0_r1). Identical hunk at different line offsets."
     },
     "backport_notes": [
      "A single self-contained hunk in rw_mfc_handle_read_op. On Android 14/15 the file lives in platform/system/nfc (src/nfc/tags/rw_mfc.cc) rather than packages/modules/Nfc/libnfc-nci, and the Android 14 variant uses DLOG_IF(INFO, nfc_debug_enabled) for the log line. The per-major facts show 7/8 patched-region lines present in each latest tag; the material does not explain the missing line."
     ],
     "confidence": "medium",
     "unknowns": [
      "How p_ndef_buffer is sized, and therefore by how much an inflated ndef_length could exceed it, is not in the material.",
      "How `failed` is handled after the shown hunk (whether further block reads are actually prevented, as the commit message claims) is not in the material.",
      "What rw_nfc_decodeTlv does and which tag-supplied fields drive ndef_length are not shown.",
      "Why only 7/8 lines of the patched region are present in each latest tag is not explained.",
      "Whether SafetyNet ID 178725766 refers to an earlier related fix is not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49919": {
     "cve": "CVE-2026-49919",
     "summary": "In FreeType's COLR layer blending (src/sfnt/ttcolr.c, tt_face_colr_blend_layer), the scratch bitmap buffer allocation changes from FT_ALLOC(buf, rows * pitch) to FT_ALLOC_MULT(buf, rows, pitch), so an overflowing rows*pitch product is now detected in the allocator instead of producing an undersized buffer.",
     "component": "platform/external/freetype, sfnt driver, COLR (color glyph layer) table handling",
     "weakness": "Integer overflow in an allocation size computation, which can lead to an undersized heap buffer (heap buffer overflow)",
     "root_cause": "In tt_face_colr_blend_layer, the size of a scratch buffer was computed as `size = rows * pitch;` with an unchecked multiplication and then passed to FT_ALLOC. If the product wraps, the allocation can succeed with a buffer smaller than rows*pitch bytes, while the code that follows (which uses p = dstSlot->bitmap.buffer and q over the new buffer) presumably still assumes the full size. That would cause a heap buffer overflow. The commit message states that, after the change, the overflow is detected in ft_mem_qrealloc.",
     "fix_analysis": [
      "src/sfnt/ttcolr.c:tt_face_colr_blend_layer: the two lines `size = rows * pitch;` and `if ( FT_ALLOC( buf, size ) )` are replaced by `if ( FT_ALLOC_MULT( buf, rows, pitch ) )`. FT_ALLOC_MULT passes the two factors separately to the memory layer, where the commit message says the overflow is detected (ft_mem_qrealloc). The allocation fails and the function returns the error instead of continuing with an undersized buffer.",
      "No other change: the code that follows is unchanged. The fix relies on buf either being at least rows*pitch bytes or the function returning early."
     ],
     "completeness": [
      "Covers only the single rows*pitch allocation in tt_face_colr_blend_layer. The diff does not change how rows and pitch are derived or validated against the destination bitmap.",
      "The code context provided does not show the rest of the function (the loop using p and q) or any other size computations in ttcolr.c, so the material does not show whether similar allocations exist elsewhere.",
      "The change is identical in content across all four Android branches, so coverage does not differ between majors."
     ],
     "behavior_changes": [
      "When rows*pitch would overflow, tt_face_colr_blend_layer now returns the allocation error from FT_ALLOC_MULT / ft_mem_qrealloc instead of proceeding with a wrapped-size buffer.",
      "No change when rows*pitch does not overflow: the allocation size is the same."
     ],
     "preconditions": [
      "A font with a COLR table must be loaded and a color glyph rendered such that tt_face_colr_blend_layer is invoked.",
      "rows and pitch must be large enough that their product overflows the size type. The material does not show how they are derived.",
      "Per index search, platform code calls FreeType on caller-supplied font data, e.g. frameworks/rs/rsFont.cpp Font::init calls FT_New_Memory_Face / FT_New_Face. The material does not show whether such paths reach the COLR blend code."
     ],
     "per_major": {
      "14": "same fix: platform/external/freetype ab9caf444825 in android-security-14.0.0_r29, identical FT_ALLOC_MULT change at the same hunk (@@ -1803).",
      "15": "same fix: platform/external/freetype 9f67316aa41d in android-security-15.0.0_r17, identical diff and blob ids to the 16 commit.",
      "16": "same fix: platform/external/freetype 6770ef4d9faf in android-security-16.0.0_r8 (the analysed commit).",
      "17": "same fix: platform/external/freetype 65f3c6ba20e7 in android-security-17.0.0_r1, identical change with the hunk shifted two lines (@@ -1805)."
     },
     "backport_notes": [
      "Single self-contained change, applied identically on 14 through 17 with no other commit listed. If the local `size` variable stays declared but unused, an unused-variable warning may need handling, depending on the branch's warning flags."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the rest of tt_face_colr_blend_layer: how rows and pitch are computed, the copy loop, and whether `size` is still declared or used. The exact overflow consequence is therefore inferred from the commit message and the allocation pattern.",
      "Upstream FreeType issue #1389, referenced in the commit, is not included in the material.",
      "The material does not show whether untrusted font input reaches this function on Android."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49921": {
     "cve": "CVE-2026-49921",
     "summary": "Adds SDP_MAX_ATTR_LEN bounds enforcement across the Bluetooth SDP server: sdpu_build_partial_attrib_entry now sizes its serialization buffer dynamically and rejects oversized attributes and invalid continuation offsets, process_service_attr_req/process_service_search_attr_req reject attributes with payload above SDP_MAX_ATTR_LEN, and SDP_AddAttributeToRecord caps attribute length at SDP_MAX_ATTR_LEN on insertion.",
     "component": "platform/packages/modules/Bluetooth, system/stack/sdp (SDP server: sdp_server.cc, sdp_utils.cc, sdp_db.cc)",
     "weakness": "Missing bounds check leading to heap buffer overflow (out-of-bounds write) and out-of-bounds read in a fixed-size serialization buffer",
     "root_cause": "sdpu_build_partial_attrib_entry (system/stack/sdp/sdp_utils.cc) allocated a fixed buffer of SDP_MAX_ATTR_LEN (400) bytes with osi_malloc and then called sdpu_build_attrib_entry to serialize the whole attribute (header plus payload) into it, without checking that the serialized length fit. SDP_AddAttributeToRecord (sdp_db.cc) only limited attribute length against the record's remaining pad space (SDP_MAX_PAD_LEN - free_pad_ptr), not against SDP_MAX_ATTR_LEN, so an attribute larger than 400 bytes could exist in the local server database. When a remote SDP request forced a partial attribute response (small max_list_len / continuation), the serialization overflowed the 400-byte heap buffer. The continuation offset (*offset) was also not validated against the serialized attribute length before the memcpy from &p_attr_buff[*offset], which allowed out-of-bounds reads. The request handlers did not check p_attr->len before reaching this helper. On Android 14 the partial-add branches checked only the serialized length (attr_len >= SDP_MAX_ATTR_LEN). The pre-fix state of those branches on Android 16 is not shown.",
     "fix_analysis": [
      "system/stack/sdp/sdp_utils.cc:sdpu_build_partial_attrib_entry: computes attr_len = sdpu_get_attrib_entry_len(p_attr) first. If p_attr->len > SDP_MAX_ATTR_LEN, it logs an error and returns p_out unchanged (nothing emitted). It now allocates exactly attr_len bytes instead of a fixed SDP_MAX_ATTR_LEN buffer, bails out on nullptr, and only then calls sdpu_build_attrib_entry. A new `*offset >= attr_len` check logs, frees p_attr_buff and returns p_out, which prevents the out-of-bounds read in the following memcpy. The existing clamp of len to SDP_MAX_ATTR_LEN is kept.",
      "system/stack/sdp/sdp_server.cc:process_service_attr_req: right after sdp_db_find_attr_in_rec returns a p_attr (before the AVRC/HFP helpers), if p_attr->len > SDP_MAX_ATTR_LEN it logs, sends an SDP error with tSDP_STATUS::SDP_NO_RESOURCES via sdpu_build_n_send_error, and returns.",
      "system/stack/sdp/sdp_server.cc:process_service_search_attr_req: the same p_attr->len > SDP_MAX_ATTR_LEN check and SDP_NO_RESOURCES error response, in the per-record attribute loop.",
      "system/stack/sdp/sdp_db.cc:SDP_AddAttributeToRecord: the truncation condition becomes `attr_len > SDP_MAX_ATTR_LEN || free_pad_ptr + attr_len >= SDP_MAX_PAD_LEN`. A new max_allowed_len = min(SDP_MAX_ATTR_LEN, SDP_MAX_PAD_LEN - free_pad_ptr) is used to truncate TEXT_STR_DESC_TYPE values and to NUL-terminate at max_allowed_len - 1. Oversized non-text attributes still take the attr_len = 0 path. Adds the <algorithm> include.",
      "Tests: stack_sdp_test.cc adds write_read_max_attr_len_slicing (a 400-byte text attribute is served correctly via continuation with max_list_len 50) and SDP_AddAttribute__exceed_max_attr_len_truncation (SDP_MAX_ATTR_LEN + 10 bytes truncated to 400). stack_sdp_utils_test.cc adds sdpu_build_partial_attrib_entry_exceeds_max_attr_len (len 500 returns p_out) and sdpu_build_partial_attrib_entry_within_bounds."
     ],
     "completeness": [
      "The fix adds checks at three layers: insertion (sdp_db.cc), request handling (both attribute request handlers in sdp_server.cc), and the serialization helper itself (sdp_utils.cc). The helper no longer relies on its callers for safety: the buffer is sized from sdpu_get_attrib_entry_len, so header overhead is covered even when the payload is exactly 400 bytes.",
      "The helper checks p_attr->len (payload), while the allocation is attr_len (header + payload). Because the allocation now matches the serialized size, the write fits the buffer whatever the header size.",
      "The new *offset >= attr_len check guards only sdpu_build_partial_attrib_entry. The material does not show whether the callers' own handling of cont_info.attr_offset (the `attr_len < p_ccb->cont_info.attr_offset` branch visible in the Android 14 diff context) changed in any other way.",
      "The insertion cap is applied in SDP_AddAttributeToRecord. The material does not show whether any other writer fills tSDP_ATTRIBUTE::len/value_ptr in the server database without going through this function.",
      "The commit message mentions correcting 'a couple of mistaken bounds checks elsewhere in sdp_server.cc'. The Android 16 diff removes no such check, while the Android 14 variant removes the `attr_len >= SDP_MAX_ATTR_LEN` checks in the partial-add branches. Whether those checks exist on 16 is not shown."
     ],
     "behavior_changes": [
      "SDP ServiceAttribute and ServiceSearchAttribute requests that touch a stored attribute with payload length > SDP_MAX_ATTR_LEN now get an SDP error response (SDP_NO_RESOURCES) instead of being served.",
      "sdpu_build_partial_attrib_entry returns p_out unchanged (emits nothing) when the attribute payload exceeds SDP_MAX_ATTR_LEN, when osi_malloc returns nullptr, or when *offset >= the serialized attribute length, and logs a warning or error.",
      "SDP_AddAttributeToRecord now truncates TEXT_STR_DESC_TYPE attributes to at most SDP_MAX_ATTR_LEN (400) bytes even when pad space would allow more. Non-text attributes longer than SDP_MAX_ATTR_LEN now take the attr_len = 0 path. How the record is stored after that path is not shown.",
      "sdpu_build_partial_attrib_entry now allocates per attribute: attr_len bytes instead of a fixed 400 bytes."
     ],
     "preconditions": [
      "A local SDP server record holds an attribute whose payload (or serialized entry) exceeds the 400-byte SDP_MAX_ATTR_LEN. Before the fix, SDP_AddAttributeToRecord allowed this as long as the record pad (SDP_MAX_PAD_LEN) had room. Per index search, SDP_AddProfileDescriptorListToRecord calls SDP_AddAttributeToRecord. The new truncation test shows that SDP_AddAttribute reaches the capped path.",
      "A remote SDP client sends a ServiceAttribute or ServiceSearchAttribute request over L2CAP whose max_list_len / continuation state forces the server into the partial attribute path (sdpu_build_partial_attrib_entry). This path is reached from process_service_attr_req / process_service_search_attr_req via sdp_server_handle_client_req, as the new test exercises.",
      "Bluetooth is enabled and an L2CAP SDP connection is established. The bulletin rates this Critical RCE, which indicates remote reachability, but the material does not give authentication or pairing requirements."
     ],
     "per_major": {
      "14": "adapted fix: commit 51b0cc33b16c in android-security-14.0.0_r29. Same logic, with SDP_TRACE_ERROR/SDP_TRACE_WARNING logging. In sdp_server.cc the p_attr->len > SDP_MAX_ATTR_LEN check sits after attr_len = sdpu_get_attrib_entry_len(p_attr) instead of right after the attribute lookup, and the old `attr_len >= SDP_MAX_ATTR_LEN` rejections in the partial-add branches of both handlers are removed. In sdp_utils.cc the offset check comes after the len clamp. StackSdpUtilsTest gains osi_malloc/osi_free mock bodies. Resolver: 7/8 patched lines present.",
      "15": "adapted fix: commit c738041c8ce8 in android-security-15.0.0_r17. Same structure as 16, with unscoped SDP_NO_RESOURCES and SDP_STATE_CONNECTED, and test::mock L2CA_DataWrite in the tests. Resolver: 8/8 patched lines present.",
      "16": "same fix: this commit 947e8bbe7dc6 in android-security-16.0.0_r8 (cherry-pick of 328d9f310670, RESTRICT AUTOMERGE). Resolver: 7/8 patched lines present.",
      "17": "adapted fix: commit 4a9e8875e210 in android-security-17.0.0_r1. Same logic. The DB function is the static sdp_add_attribute_to_record, and sdpu_set_avrc_target_version is passed p_ccb->device_address directly instead of its address. Resolver: 7/8 patched lines present."
     },
     "backport_notes": [
      "For branches that still have the `attr_len >= SDP_MAX_ATTR_LEN` rejection inside the 'add partially' branches of process_service_attr_req / process_service_search_attr_req, follow the Android 14 variant: remove those checks and add the p_attr->len > SDP_MAX_ATTR_LEN check after sdpu_get_attrib_entry_len. Otherwise a 400-byte payload (serialized length above 400) would be rejected, and the new write_read_max_attr_len_slicing test expects it to be served.",
      "sdpu_build_partial_attrib_entry now calls osi_free and allocates a variable size. Unit test fixtures that use test::mock::osi_allocator (as on Android 14) need osi_malloc/osi_free bodies set, as the 14 commit does."
     ],
     "confidence": "high",
     "unknowns": [
      "The numeric value of SDP_MAX_PAD_LEN, and so the largest attribute that could be stored before the fix, is not shown.",
      "Which local profiles or app-facing APIs can register attributes larger than 400 bytes in practice is not shown.",
      "Whether any writer other than SDP_AddAttributeToRecord fills server-record attributes is not shown.",
      "Which 'mistaken bounds checks elsewhere in sdp_server.cc' the commit message means on Android 16 is not visible in the 16 diff (only the 14 variant shows removals). Whether the old partial-branch checks exist on 16 is not shown.",
      "How SDP_AddAttributeToRecord stores an attribute after the attr_len = 0 path is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49932": {
     "cve": "CVE-2026-49932",
     "summary": "Hardens PduParser.parseParts in frameworks/base telephony common MMS code by bounding the multipart/alternative recursion depth to 30, rejecting part dataLength values larger than the remaining stream bytes, and verifying a nested child body is non-null and non-empty before taking its first part.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parsing (com.google.android.mms.pdu.PduParser)",
     "weakness": "Missing input validation: unbounded recursion, unchecked attacker-controlled length used for allocation, and missing null/empty check before indexing",
     "root_cause": "In telephony/common/com/google/android/mms/pdu/PduParser.java, parseParts() trusted the parsed part structure of a WAP multipart PDU. (1) For a part whose content type is multipart/vnd.wap.multipart.alternative it recursed into parseParts() on the part data with no depth bound, so deeply nested alternative parts could exhaust the stack (StackOverflowError). (2) The per-part dataLength was used directly in new byte[dataLength] without comparing it to the bytes actually left in the stream, so a huge declared length could cause OutOfMemoryError. (3) After the recursive call, childBody.getPart(0) was invoked without checking that childBody was non-null or contained any parts, giving NullPointerException or ArrayIndexOutOfBoundsException on a malformed nested body.",
     "fix_analysis": [
      "telephony/common/com/google/android/mms/pdu/PduParser.java: adds constant MAX_PARSING_DEPTH = 30 and turns the protected parseParts(ByteArrayInputStream) into a thin wrapper that calls a new private parseParts(ByteArrayInputStream, int depth) with depth 0.",
      "PduParser.parseParts(stream, depth): immediately after the existing null-stream check, returns null (with Log.e) when depth > MAX_PARSING_DEPTH, so multipart/alternative nesting deeper than 30 levels is rejected.",
      "PduParser.parseParts(stream, depth): inside the dataLength > 0 branch, before new byte[dataLength], returns null (with Log.e) if dataLength > pduDataStream.available(), preventing over-allocation.",
      "PduParser.parseParts(stream, depth): the recursive call for ContentType.MULTIPART_ALTERNATIVE now passes depth + 1, and the result is checked: if childBody == null || childBody.getPartsNum() == 0 the method logs and returns null instead of calling childBody.getPart(0).",
      "tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/PduParserTest.java (new): three regression tests feed 14-byte to 17-byte malformed PDUs (AIOOBE, NPE, OOM triggers) to new PduParser(payload, true).parse() and assert a null result."
     ],
     "completeness": [
      "All three guards live in the single parseParts loop, which is the only recursion site shown; the depth bound covers every multipart/alternative nesting path through this method.",
      "The commit message claims an optimization of skipWapValue and parameter skipping to use InputStream.skip instead of temporary byte arrays, but no such change appears in the diff of any branch; the applied change is limited to parseParts and the new test.",
      "Any failure (depth exceeded, oversized length, empty nested body) aborts parsing of the whole PDU with null rather than skipping the bad part."
     ],
     "behavior_changes": [
      "PDUs with multipart/alternative nesting deeper than 30 levels are now rejected (parseParts returns null) instead of being parsed.",
      "A part whose declared dataLength exceeds the remaining bytes in the stream now causes parseParts to return null instead of attempting the allocation.",
      "A nested multipart/alternative body that fails to parse or contains zero parts now causes parseParts to return null rather than throwing; the new tests assert parse() returns null for such inputs.",
      "New Log.e messages under LOG_TAG for each rejection path."
     ],
     "preconditions": [
      "A malformed MMS PDU must reach com.google.android.mms.pdu.PduParser.parse() with a message type for which parts are parsed. In the forked copies found by index search (ApiDemos and Messaging PduParser.parse), that is MESSAGE_TYPE_SEND_REQ or MESSAGE_TYPE_RETRIEVE_CONF; the Android 16 parse() is not shown in the material.",
      "The regression tests reach the bug with 14-byte to 17-byte inputs via new PduParser(data, true).parse(), so no large input is required.",
      "packages/services/Mms/src/com/android/mms/service/MmsService.java::parsePduForAnyCarrier invokes new PduParser(data, ...).parse() (per index search). It catches RuntimeException only, which would not cover StackOverflowError or OutOfMemoryError."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 8325de4e11e9 in android-security-14.0.0_r29, identical diff (same blob hashes 62eac7ac942a..ecab9562f72e) plus the new PduParserTest; 8/8 patched lines present.",
      "15": "same fix: platform/frameworks/base b86eef8c2ffe in android-security-15.0.0_r17, identical diff and test; 8/8 patched lines present.",
      "16": "same fix: platform/frameworks/base a28fb127a57b in android-security-16.0.0_r8, RESTRICT AUTOMERGE cherry-pick (Change-Id If97a6540b86380a01700fca887813ff117922e58); 8/8 patched lines present.",
      "17": "same fix: platform/frameworks/base 1c37e9ef69ea in android-security-17.0.0_r1; the PduParser.java pre-image blob differs (ffd2a8b419bb) but the hunks are textually identical; 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit; the PduParser.java hunks are textually identical on Android 14 through 17 (Android 17 has a different pre-image blob). The new PduParserTest is a new file; whether its test target exists in a given fork is not shown.",
      "Forks that carry their own copy of the mmslib PduParser (for example packages/apps/Messaging/src/com/android/messaging/mmslib/pdu/PduParser.java, per index search) do not receive this fix automatically and would need to be reviewed separately."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the full pre-fix parseParts body (how dataLength and headerLength are read and consumed) or the Android 16 PduParser.parse() caller. The message types that reach parseParts in frameworks/base are inferred from forked copies only.",
      "Whether headerLength is validated against the remaining stream bytes in the Android 16 parseParts is not shown.",
      "The skipWapValue/parameter-skipping optimization described in the commit message is absent from every branch diff; whether it landed in a separate change is not shown.",
      "The bulletin lists the issue as Critical DoS; the material does not show which specific thread or process crashes in the reported scenario, nor the source of data passed to MmsService.parsePduForAnyCarrier."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55256": {
     "cve": "CVE-2026-55256",
     "summary": "Adds null checks in PduParser.parsePartHeaders so that a null result from parseWapString for the Content-Disposition token-text, the filename parameter, or a text header name/value is no longer passed to PduPart setters or to new String(), preventing NullPointerExceptions while parsing an MMS PDU.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parser (com.google.android.mms.pdu.PduParser); mirrored copy in platform/development samples/ApiDemos mmslib",
     "weakness": "Missing null check on parser return value (NULL pointer dereference leading to DoS)",
     "root_cause": "In PduParser.parsePartHeaders, the result of parseWapString(pduDataStream, TYPE_TEXT_STRING) was used without a null check at three places. The Content-Disposition token-text branch passed it directly to part.setContentDisposition(). The P_FILENAME branch passed it directly to part.setFilename(). The Content-Transfer-Encoding check called new String(tempHeader) and part.setContentTransferEncoding(tempValue) without checking either for null. The fix implies parseWapString can return null on some input, and the commit message says these paths could cause NullPointerExceptions.",
     "fix_analysis": [
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (Content-Disposition, token-text branch): stores the parseWapString result in a local contentDisposition and only calls part.setContentDisposition() when it is non-null.",
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (P_FILENAME parameter): stores the parseWapString result in a local filename and only calls part.setFilename() when it is non-null.",
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (text header check): replaces `true ==` with `null != tempHeader && null != tempValue &&` before new String(tempHeader) and equalsIgnoreCase, so setContentTransferEncoding is only called when both header name and value are non-null.",
      "samples/ApiDemos/src/com/example/android/mmslib/pdu/PduParser.java:parsePartHeaders (platform/development): the same three changes are applied to the ApiDemos copy of the parser."
     ],
     "completeness": [
      "Covers the three null-unsafe uses of parseWapString inside parsePartHeaders (Content-Disposition token-text, filename parameter, Content-Transfer-Encoding header name/value) in both the framework parser and the ApiDemos sample copy.",
      "Parsing continues after a null value is skipped. The fix does not abort or flag the part; it leaves the corresponding PduPart field unset.",
      "This commit does not touch other uses of parseWapString in PduParser. In the material, parseHeaders calls new String(bVal) without a null check under LOCAL_LOGV, and that code is unchanged."
     ],
     "behavior_changes": [
      "If parseWapString returns null for a Content-Disposition token-text or a filename parameter, the field is now left unset on the PduPart and parsing continues.",
      "Content-Transfer-Encoding is now set only when both the header name and value are non-null. Otherwise the header is skipped."
     ],
     "preconditions": [
      "An MMS PDU is parsed by PduParser so that parsePartHeaders runs for a part.",
      "The part has a Content-Disposition value encoded as token-text, a filename parameter, or a text header where parseWapString returns null.",
      "The material does not show the exact input that makes parseWapString return null."
     ],
     "per_major": {
      "14": "same fix: platform/development 6aa9341a7695 in android-security-14.0.0_r29 (ApiDemos copy, identical diff). The material shows no frameworks/base commit for this branch. The resolver reports 6/8 patched-region lines present.",
      "15": "same fix: platform/frameworks/base e2b80df1360a in android-security-15.0.0_r17, identical diff to the Android 16 commit. The resolver reports 6/8 patched-region lines present.",
      "16": "same fix: platform/frameworks/base 01f51483eb2f and platform/development 8b9e6db92ce8 in android-security-16.0.0_r8 (cherry-picks of internal changes, Flag: EXEMPT BUGFIX). The resolver reports 6/8 patched-region lines present.",
      "17": "same fix: platform/development 21dbe0e7ba1f in android-security-17.0.0_r1 (ApiDemos copy, identical diff). The material shows no frameworks/base commit for this branch. The resolver reports 6/8 patched-region lines present."
     },
     "backport_notes": [
      "The frameworks/base and development commits are independent and make the same three changes. The frameworks/base copy (telephony/common) is the shipping parser; the development copy only changes the ApiDemos sample."
     ],
     "confidence": "high",
     "unknowns": [
      "Which inputs make parseWapString return null (its body is not in the material).",
      "Whether PduPart.setContentDisposition/setFilename/setContentTransferEncoding throw on null themselves or the NPE happens later. The commit message only says NPEs are prevented.",
      "How tempHeader is obtained in parsePartHeaders (its assignment is not shown).",
      "Whether frameworks/base received a counterpart commit on the Android 14 and 17 security tags. Only the development commit is listed for those branches."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55273": {
     "cve": "CVE-2026-55273",
     "summary": "aapt2's AnnotationProcessor now rewrites Javadoc terminators (\"*/\") and Java Unicode escape introducers (\"\\u\") in resource XML comments before emitting them into the Javadoc blocks of generated R.java, so a comment can no longer close the Javadoc block and inject Java source.",
     "component": "platform/frameworks/base, tools/aapt2 (Java R.java generation, AnnotationProcessor)",
     "weakness": "Improper neutralization of special elements in generated source (code injection via unescaped comment content, missing output sanitization)",
     "root_cause": "AnnotationProcessor::AppendCommentLine in tools/aapt2/java/AnnotationProcessor.cpp placed comment text taken from resource XML into the \"/** ... */\" Javadoc block that aapt2 prints into generated R.java, and did not escape Javadoc terminators or Unicode escapes first. A comment containing the literal sequence \"*/\" ends the Javadoc block early. Because the Java compiler decodes Unicode escapes (e.g. \\u002f for '/' or \\u002a for '*') before it parses comments, sequences like \"*\\u002f\" or \"\\u002a\\u002f\" have the same effect. Text after the breakout is then compiled as Java code in R.java.",
     "fix_analysis": [
      "tools/aapt2/java/AnnotationProcessor.cpp: adds a file-static SanitizationRule struct (find_target, replace_length, replacement) and a static SanitizeJavadoc(std::string*) function with two rules, applied in order. (1) Every \"*/\" is replaced by \"*&#47;\". (2) For every \"\\u\", only the leading backslash is replaced (replace_length 1) by \"&#92;\", giving \"&#92;u...\". Each rule scans with std::string::find and moves past the replacement, so the loop does not re-match inside its own output.",
      "tools/aapt2/java/AnnotationProcessor.cpp:AnnotationProcessor::AppendCommentLine: calls SanitizeJavadoc(&comment) after the annotation-rule processing and before the empty-line trim check.",
      "tools/aapt2/java/AnnotationProcessor_test.cpp: adds TEST(AnnotationProcessorTest, SanitizesJavadocTermination). It covers a literal \"*/\", \"*\\u002f\", \"\\u002a/\", the multi-u form \"\\uu002f\", adjacent \"\\u002a\\u002f\", and a backslash before the escape, and checks the exact printed Javadoc block."
     ],
     "completeness": [
      "Covers the two breakout primitives visible in the diff: the literal Javadoc terminator and Java Unicode escapes, including the multi-u form, because only the backslash is replaced. The second rule neutralizes every backslash followed by 'u', however many backslashes come before it (see the test case \"\\\\u002f\" -> \"\\&#92;u002f\").",
      "The sanitization is called in AppendCommentLine, which lines added via AppendComment reach (per the test). The output of the \"*/\" rule (\"*&#47;\") cannot form a new \"*/\" or \"\\u\", and the \"&#92;\" output cannot form a new \"*/\".",
      "The fix only sanitizes text that goes through AnnotationProcessor. The material does not show whether other parts of the R.java generator emit resource-derived text outside AnnotationProcessor.",
      "The replacement puts HTML entities into the Javadoc text. This changes the generated comment text but not the Java semantics of R.java."
     ],
     "behavior_changes": [
      "Generated R.java Javadoc comments now contain \"*&#47;\" wherever the source XML comment had \"*/\", and \"&#92;u\" wherever it had \"\\u\".",
      "No new errors or warnings are added in the diff. aapt2 rewrites the text instead of rejecting the resource.",
      "The existing TrimWhitespace empty-line check now runs on the sanitized text."
     ],
     "preconditions": [
      "aapt2 must process resource XML whose comments are attacker-influenced and generate R.java from them.",
      "The comment must reach AnnotationProcessor::AppendComment -> AppendCommentLine (per the test and code context, this is the path used for Javadoc output).",
      "The generated R.java must then be compiled for injected text to have any effect."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin). The resolver finds 6/8 lines of the patched region at android-security-14.0.0_r29. No Android 14 fix commit is shown.",
      "15": "Not affected (not listed by the bulletin). The resolver finds 8/8 lines of the patched region at android-security-15.0.0_r17. No Android 15 fix commit is shown.",
      "16": "Same fix: platform/frameworks/base 74f5e6fa6a20 in android-security-16.0.0_r8 (cherry-pick of internal changes 863026ecf4f3/89513b592c1e, Change-Id I6a566be2d3772bfbf86897f1ccf70bc8c7250e5f); 8/8 lines present.",
      "17": "Same fix: platform/frameworks/base 28830f292a06 in android-security-17.0.0_r1, with identical code. Only the surrounding context differs: sAnnotationRules has 4 entries including @hide, and the test file offset is different. 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit. It adds only a static struct and function plus one call in AppendCommentLine, with no dependency on sAnnotationRules, and it applied with only context differences to the 16 tree (3 rules) and the 17 tree (4 rules). The new test uses AppendComment, StringOutputStream and Printer, which the existing test file already uses."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether any other code path in the aapt2 Java generator emits resource-derived text into R.java without going through AnnotationProcessor.",
      "Which build or packaging scenario the bulletin's Critical EoP rating assumes, and whether the impact is limited to the build host. The material only shows the host-side aapt2 code change.",
      "Why Android 14 and 15 are not listed as affected while the patched region is partly (14) or fully (15) present in their latest security tags."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55285": {
     "cve": "CVE-2026-55285",
     "summary": "Adds a range check on the channel number the Secure Element returns in the MANAGE CHANNEL OPEN response, before that number is used as an index into mOpenedChannels. The check is added to the ten openLogicalChannel implementations the commit touches in the NXP secure_element HAL (pn8x 1.0/1.1, plus snxxx 1.0/1.1/1.2/aidl SecureElement and VirtualISO). The fix also makes pn8x closeChannel return immediately on an invalid channel number.",
     "component": "platform/hardware/nxp/secure_element, NXP eSE HAL (pn8x and snxxx HIDL/AIDL SecureElement and VirtualISO services)",
     "weakness": "Missing bounds check on an externally supplied array index (out-of-bounds write, intra-object per the commit message)",
     "root_cause": "In each openLogicalChannel, phNxpEse_Transceive sends the MANAGE CHANNEL OPEN command. When it succeeded with status word 0x90 0x00, the first response byte rspApdu.p_data[0] was copied into resApduBuff.channelNumber and used directly as the index in `mOpenedChannels[resApduBuff.channelNumber] = true`. Nothing compared that byte against the array capacity. The capacity is MAX_LOGICAL_CHANNELS for pn8x and mMaxChannelCount for snxxx; in snxxx 1.2 and aidl, mOpenedChannels is resized to mMaxChannelCount. A Secure Element that returned a channel byte at or above that bound caused an out-of-bounds boolean write. Separately, in pn8x closeChannel the invalid-channel branch only set sestatus = FAILED and did not return, so execution continued past the if/else chain. The commit message calls this a missing early return.",
     "fix_analysis": [
      "pn8x/1.0/SecureElement.cpp and pn8x/1.1/SecureElement.cpp :: SecureElement::openLogicalChannel: the 0x9000 success branch is wrapped in `if (rspApdu.p_data[0] < MAX_LOGICAL_CHANNELS && rspApdu.p_data[0] > 0)`. Only inside that condition are channelNumber, mOpenedchannelCount++ and mOpenedChannels[...] = true applied, with SUCCESS. Otherwise sestatus = SecureElementStatus::CHANNEL_NOT_AVAILABLE, and the pn8x else branch does not modify resApduBuff.channelNumber.",
      "pn8x/1.0/SecureElement.cpp and pn8x/1.1/SecureElement.cpp :: SecureElement::closeChannel: in the `(channelNumber >= MAX_LOGICAL_CHANNELS) || (mOpenedChannels[channelNumber] == false)` branch, `sestatus = SecureElementStatus::FAILED;` is replaced by `return SecureElementStatus::FAILED;`, so the rest of the function does not run for an invalid channel.",
      "snxxx/1.0, 1.1 and 1.2 SecureElement.cpp and VirtualISO.cpp :: openLogicalChannel: the same guard, using `rspApdu.p_data[0] < mMaxChannelCount && rspApdu.p_data[0] > 0`. The else branch sets resApduBuff.channelNumber = 0xff and sestatus = SecureElementStatus::CHANNEL_NOT_AVAILABLE.",
      "snxxx/aidl/SecureElement.cpp and snxxx/aidl/VirtualISO.cpp :: openLogicalChannel: the same guard against mMaxChannelCount. The else branch sets resApduBuff.channelNumber = -1 and sestatus = ISecureElement::CHANNEL_NOT_AVAILABLE.",
      "All variants reuse the existing post-processing. A non-SUCCESS sestatus goes down the existing failure path: seHalDeInit runs when mOpenedchannelCount == 0 (pn8x also requires isSeInitialized()), then the callback fires and the function returns."
     ],
     "completeness": [
      "Covers the ten openLogicalChannel implementations the commit touches: pn8x 1.0/1.1, and snxxx 1.0/1.1/1.2/aidl for both SecureElement and VirtualISO. Each uses the bound for that variant: the MAX_LOGICAL_CHANNELS constant for pn8x, mMaxChannelCount for snxxx. mMaxChannelCount matches the mOpenedChannels.resize(mMaxChannelCount, false) seen in snxxx 1.2 and aidl.",
      "The `> 0` condition also rejects a returned channel number of 0 (the basic channel), so the SE cannot mark the basic-channel slot as opened through this path.",
      "The closeChannel early return is added only in the pn8x variants. This commit does not touch snxxx closeChannel, and the material does not show whether those implementations needed the change.",
      "The fix validates only rspApdu.p_data[0]. After a successful transceive, the surrounding code still indexes rspApdu.p_data[rspApdu.len - 2] and [rspApdu.len - 1] with no visible check that rspApdu.len >= 2. The material does not show whether phNxpEse_Transceive guarantees a length of at least 2.",
      "openBasicChannel and other HAL entry points are not part of this commit, and the material does not show their code."
     ],
     "behavior_changes": [
      "A MANAGE CHANNEL OPEN response with SW 0x9000 whose first byte is 0, or at or above the channel bound, now returns CHANNEL_NOT_AVAILABLE instead of SUCCESS. The channel count is not incremented and no channel is marked opened.",
      "In that case the snxxx HIDL variants report channelNumber 0xff, the AIDL variants report -1, and the pn8x variants leave resApduBuff.channelNumber at its prior value.",
      "Because sestatus is not SUCCESS, the existing failure path runs: if mOpenedchannelCount == 0, the HAL calls seHalDeInit before returning.",
      "pn8x closeChannel now returns FAILED immediately for an out-of-range or not-opened channel instead of continuing through the rest of the function."
     ],
     "preconditions": [
      "A device using the NXP secure_element HAL (pn8x or snxxx builds) with the eSE (endpoint 0) or the VirtualISO endpoint (phNxpEse_SetEndPoint_Cntxt(1)) present.",
      "The Secure Element must answer MANAGE CHANNEL OPEN with status word 0x90 0x00 and a first data byte outside [1, bound). The commit message attributes this to a compromised Secure Element.",
      "Per index search, the HAL's openLogicalChannel is reached from the OMAPI stack: packages/apps/SecureElement Terminal.openLogicalChannel and frameworks/base android.se.omapi Session.openLogicalChannel."
     ],
     "per_major": {
      "14": "Not affected (Android 14 is not listed by the bulletin). The patched region is present (8/8 lines) at android-security-14.0.0_r29. The material does not identify the commit that brought it there.",
      "15": "Not affected (Android 15 is not listed by the bulletin). The patched region is present (8/8 lines) at android-security-15.0.0_r17. The material does not identify the commit that brought it there.",
      "16": "Same fix: commit 466c2e78559e in android-security-16.0.0_r8, a cherry-pick of 8ca0d691fa4b with Change-Id I54a5cb16202b49d0929e6306626ef11e2aa329b8. Patched region present (8/8 lines).",
      "17": "Same fix: commit addc064ca4ec in android-security-17.0.0_r1, with identical hunks. The snxxx/aidl pre-image blob hashes differ from Android 16 (ca80d6b/73563de vs 4ad9388/5808238), and the snxxx/aidl/SecureElement.cpp hunk starts at line 414 instead of 418. Patched region present (8/8 lines)."
     },
     "backport_notes": [
      "The fix is a single self-contained commit; each openLogicalChannel hunk wraps four existing lines in the new check. If your tree's snxxx mOpenedChannels is sized differently, use the actual array capacity as the bound. The material shows the resize only for snxxx 1.2 and aidl."
     ],
     "confidence": "high",
     "unknowns": [
      "What code follows the invalid-channel branch in pn8x closeChannel. The diff shows only the changed line, so the exact effect of the previously missing return is not visible.",
      "How mOpenedChannels is declared and sized in pn8x and in snxxx 1.0/1.1. The resize to mMaxChannelCount is shown only for snxxx 1.2 and aidl.",
      "Whether phNxpEse_Transceive guarantees rspApdu.len >= 2 on success, which the unchanged status-word indexing relies on.",
      "Whether other openLogicalChannel implementations exist in the repository beyond the ten files this commit changes.",
      "Why the bulletin lists only Android 16 and later while the patched region is present in the 14 and 15 security tags."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48564": {
     "cve": "CVE-2025-48564",
     "summary": "IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that same string to both IPackageManager.canForwardTo and a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload, sanitizes the received intent's selector before startActivityAsCaller, and keys the privileged skip-user-consent check on getLaunchedFromPackage() instead of getCallingPackage().",
     "component": "platform/frameworks/base (core/java/com/android/internal/app/IntentForwarderActivity, android.content.pm.PackageManager / ApplicationPackageManager) and platform/packages/modules/IntentResolver (com.android.intentresolver.IntentForwarderActivity, emptystate/CrossProfileIntentsChecker)",
     "weakness": "Time-of-check/time-of-use on an untrusted intent attribute. The intent's data type was resolved once for the cross-profile permission check and again for the activity resolution that followed, so the value checked and the value used could differ. Secondary issues: the intent selector was not fully sanitized, and getCallingPackage() was used for a privilege decision.",
     "root_cause": "In IntentForwarderActivity.onCreate, canForward() -> canForwardInner() computed `String resolvedType = intent.resolveTypeIfNeeded(contentResolver)` and passed it to packageManager.canForwardTo(intent, resolvedType, sourceUserId, targetUserId). Once that check passed, onCreate called mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId). In ApplicationPackageManager.resolveActivityAsUser this called intent.resolveTypeIfNeeded(mContext.getContentResolver()) a second time. The commit message says this is a vulnerability if the intent changes its type after the IntentForwarder#canForward check. Two further problems: in the path that sanitizes intentReceived before launching it (around IntentForwarderActivity.java lines 524-531 in base and lines 222-229 in the IntentResolver module), only sanitizeIntent(intentReceived) was applied and its selector was left alone; and the EXTRA_SKIP_USER_CONFIRMATION privileged-caller check used getCallingPackage(), which the fix replaces with getLaunchedFromPackage().",
     "fix_analysis": [
      "core/java/android/content/pm/PackageManager.java: adds two @hide overloads, resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The javadoc says resolvedType is passed explicitly when the type contained in the intent cannot be trusted; a null value falls back to Intent#resolveTypeIfNeeded.",
      "core/java/android/app/ApplicationPackageManager.java: resolveActivity(Intent, ResolveInfoFlags), resolveActivityAsUser(Intent, int, int) and resolveActivityAsUser(Intent, ResolveInfoFlags, int) now delegate with resolvedType = null. The new resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) calls mPM.resolveIntent with `resolvedType == null ? intent.resolveTypeIfNeeded(...) : resolvedType`, so a type supplied by the caller is used as is.",
      "core/java/com/android/internal/app/IntentForwarderActivity.java onCreate: computes `String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver())` once and passes it first to canForward(...) and then to mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). The permission check and the resolution therefore use the same type string.",
      "IntentForwarderActivity.canForward / canForwardInner: the `ContentResolver contentResolver` parameter becomes `String resolvedType`, and the per-call `intent.resolveTypeIfNeeded(contentResolver)` in canForwardInner is removed. The same resolvedType is passed for both the root intent check and the selector check. The Injector interface and the default injector gain resolveActivityAsUser(Intent, String, int, int), backed by PackageManager.resolveActivityAsUser(intent, resolvedType, flags, userId).",
      "IntentForwarderActivity (managed-profile branch): `String callingPackage = getCallingPackage()` is replaced by `getLaunchedFromPackage()` for the privilegedCallerAskedToSkipUserConsent decision.",
      "IntentForwarderActivity, path that sanitizes intentReceived before launching it (base line ~528, module line ~226): after sanitizeIntent(intentReceived), adds `if (intentReceived.getSelector() != null) sanitizeIntent(intentReceived.getSelector())`.",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents and IntentResolver emptystate/CrossProfileIntentsChecker.hasCrossProfileIntents: updated to the new signature by passing intent.resolveTypeIfNeeded(mContentResolver). Because canForward now reuses that root type for the selector check, the selector is no longer checked against its own type.",
      "platform/packages/modules/IntentResolver 4e6cf5285d0b: applies the same changes to com.android.intentresolver.IntentForwarderActivity: resolvedType computed once in onCreate and passed to canForward and to the new Injector.resolveActivityAsUser(Intent, String, int, int), the selector sanitization, and the canForward/canForwardInner signature change. It contains no getCallingPackage change.",
      "Follow-up frameworks/base 71d6dc109528 (16.0.0_r8) and 323c8b968a88 (17.0.0_r1), flagged EXEMPT TEST_ONLY: moves the executor and the DEVICE_PROVISIONED read behind Injector.getExecutorService() and isDeviceProvisioned(), corrects the backwards assertNull assertions in IntentForwarderActivityTest, adds HIDE_OVERLAY_WINDOWS to the test manifest and re-enables the test. It does not change security logic."
     ],
     "completeness": [
      "Covers both type resolutions inside IntentForwarderActivity.onCreate (the canForwardTo check and the target-user resolveActivityAsUser call), in both the frameworks/base and IntentResolver copies of IntentForwarderActivity.",
      "The selector check in canForward reuses the root intent's resolvedType instead of resolving the selector's own type, so the selector's type is never consulted on its own. This is a design choice visible in the diff.",
      "frameworks/base AbstractMultiProfilePagerAdapter still calls intent.resolveTypeIfNeeded per intent. The result only feeds a boolean has-cross-profile-intents decision, and no second resolution follows in the shown code. At 16.0.0_r8 the IntentResolver module checker calls isCrossProfileIntent(..., mContentResolver) instead, and that function's body is not shown.",
      "The existing resolveActivityAsUser overloads without resolvedType are unchanged and still take the type from the intent. Other code that checks and then resolves separately through those overloads is not addressed by this change.",
      "The material does not show whether the type is resolved again when newIntent is finally started in the target user.",
      "The IntentResolver module commit does not include the getCallingPackage -> getLaunchedFromPackage change. The material does not show whether that module's IntentForwarderActivity has the EXTRA_SKIP_USER_CONFIRMATION logic at all."
     ],
     "behavior_changes": [
      "New hidden PackageManager APIs resolveActivityAsUser(Intent, String, int, int) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int). PackageManager subclasses that do not override the ResolveInfoFlags variant (ApplicationPackageManager does) inherit a default that throws UnsupportedOperationException.",
      "The IntentForwarderActivity.canForward signature changed (ContentResolver -> String resolvedType) in base (package-private static) and in the IntentResolver module (public static). Existing callers must be updated.",
      "The intent selector is now checked against the root intent's resolved type rather than its own.",
      "The received intent's selector is now sanitized (sanitizeIntent) before the intent is launched, in addition to the forward path.",
      "Privileged skip-user-confirmation now depends on getLaunchedFromPackage() rather than getCallingPackage().",
      "The Injector interface gained a new resolveActivityAsUser overload, plus getExecutorService and isDeviceProvisioned with the follow-up. Test injectors must implement them."
     ],
     "preconditions": [
      "A device with a profile relationship (managed profile or profile parent), so that cross-profile intent filters resolve to IntentForwarderActivity (FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE).",
      "An intent that reaches IntentForwarderActivity and whose data type is resolved through Intent.resolveTypeIfNeeded. The commit message describes the intent changing its type after the canForward check.",
      "The cross-profile decision is made by IPackageManager.canForwardTo (ComputerEngine.canForwardTo, which enforces INTERACT_ACROSS_USERS_FULL on its caller), per index search.",
      "Telecomm CallIntentProcessor.maybeRedirectToIntentForwarderForPrivate redirects CALL intents to IntentForwarderActivity.FORWARD_INTENT_TO_PARENT, so system flows also reach the activity, per index search."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/base 042b52a8551f in android-security-14.0.0_r24. Same logic (PackageManager/ApplicationPackageManager overloads, type resolved once in IntentForwarderActivity, getLaunchedFromPackage, selector sanitization, AbstractMultiProfilePagerAdapter call site), adjusted to the 14 code, which has no private-profile branch in the shown context. At 14.0.0_r29, 6/8 lines of the patched region are present. No IntentResolver module commit is shown for 14.",
      "15": "same fix: platform/frameworks/base bf9e58d7edb5 in android-security-15.0.0_r12, with content matching the 16 frameworks/base commit. At 15.0.0_r17, 6/8 lines of the patched region are present. No IntentResolver module commit is shown for 15.",
      "16": "same fix: platform/frameworks/base 28579dff4305 plus platform/packages/modules/IntentResolver 4e6cf5285d0b in android-security-16.0.0_r3, with the follow-up test/injector refactor 71d6dc109528 in 16.0.0_r8. At 16.0.0_r8, 8/8 lines of the patched region are present.",
      "17": "resolved by Change-Id in android-security-17.0.0_r1 (main diff not shown). The follow-up test/injector refactor 323c8b968a88 is in 17.0.0_r1. 8/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Order matters: the frameworks/base commit must land first, because the IntentResolver module commit calls the new PackageManager.resolveActivityAsUser(Intent, String, int, int) overload. The IntentResolver copy of IntentForwarderActivity is patched by its own commit, separate from the frameworks/base copy.",
      "The 16.0.0_r8 / 17.0.0_r1 follow-up (Injector.getExecutorService/isDeviceProvisioned, test fixes) is not needed for the security fix, but it corrects the backwards assertNull assertions added in the r3 test and re-enables IntentForwarderActivityTest."
     ],
     "confidence": "high",
     "unknowns": [
      "How the intent's type can change between the two resolutions is not shown in the material beyond the commit message statement.",
      "Whether the IntentResolver module copy of IntentForwarderActivity on Android 14/15 was patched (no module commit is listed for those majors).",
      "The Android 17 main fix diff is not shown (resolved by change_id only).",
      "Whether the final startActivity of newIntent into the target user resolves the type again outside the patched code.",
      "What the private-profile branch (buildAndExecuteForPrivateProfile) does with newIntent/resolvedType; its body is not in the material.",
      "What IntentResolver CrossProfileIntentsChecker.isCrossProfileIntent (present at 16.0.0_r8) does with the content resolver; its body is not in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48565": {
     "cve": "CVE-2025-48565",
     "summary": "IntentForwarderActivity now resolves the intent's MIME type once and passes that same string to both the canForwardTo check and the target-user activity resolution (via a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload), uses getLaunchedFromPackage() for the privileged skip-consent check, and sanitizes the intent selector (package, component, nested selector) alongside the main intent.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity, android.content.pm.PackageManager / ApplicationPackageManager)",
     "weakness": "Time-of-check/time-of-use on intent MIME type resolution (type re-derived from the intent after the forwarding permission check), plus incomplete intent sanitization (selector not sanitized) and trust in getCallingPackage() for a privilege check",
     "root_cause": "In IntentForwarderActivity, canForwardInner() computed the type with intent.resolveTypeIfNeeded(contentResolver) and passed it to IPackageManager.canForwardTo(). Afterwards onCreate() called mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId), whose ApplicationPackageManager implementation called intent.resolveTypeIfNeeded() again before mPM.resolveIntent(). The commit message states that an intent that changes its type after the canForward check leads to the vulnerability: the type checked for cross-profile forwarding and the type used to resolve the activity in the target user could differ. In addition, the skip-user-confirmation privilege check used getCallingPackage(), launchResolverActivityWithCorrectTab() sanitized intentReceived but not its selector, and sanitizeIntent() did not clear the selector's package, component or nested selector.",
     "fix_analysis": [
      "Commit 28579dff4305, core/java/android/content/pm/PackageManager.java: adds hidden overloads resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The latter throws UnsupportedOperationException by default. The javadoc says resolvedType is given explicitly when the type in the intent cannot be trusted.",
      "Commit 28579dff4305, core/java/android/app/ApplicationPackageManager.java: all resolveActivity/resolveActivityAsUser variants now funnel into resolveActivityAsUser(Intent, String, ResolveInfoFlags, int), which calls mPM.resolveIntent() with the caller-supplied resolvedType. It falls back to intent.resolveTypeIfNeeded() only when resolvedType is null.",
      "Commit 28579dff4305, core/java/com/android/internal/app/IntentForwarderActivity.java onCreate(): computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...), then passes it to the new Injector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). The canForward()/canForwardInner() signatures change from ContentResolver to String resolvedType, and canForwardInner() no longer calls resolveTypeIfNeeded itself. The root intent and the selector are both checked with the same resolvedType.",
      "Commit 28579dff4305, IntentForwarderActivity.java: the privileged EXTRA_SKIP_USER_CONFIRMATION path now reads callingPackage from getLaunchedFromPackage() instead of getCallingPackage(). launchResolverActivityWithCorrectTab() also calls sanitizeIntent(intentReceived.getSelector()) when a selector is present. The Injector interface and the default injector gain resolveActivityAsUser(Intent, String, int, int).",
      "Commit 28579dff4305, core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents(): adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver).",
      "Commit 71d6dc109528 (test/injector follow-up): IntentForwarderActivity gets Injector.getExecutorService() and Injector.isDeviceProvisioned(). onCreate() uses mInjector.getExecutorService(), and isDeviceProvisioned() delegates to the injector. Test fixes: the 30s awaitTermination is removed, the inverted assertNull/assertNotNull assertions are corrected, HIDE_OVERLAY_WINDOWS is added to the coretests manifest, and the test is re-enabled in core/tests/coretests/Android.bp. No security logic change.",
      "Commit 7d8be5eb6753, IntentForwarderActivity.java sanitizeIntent(): now also clears the selector's package and component and calls selector.setSelector(null). The explicit sanitizeIntent(getSelector()) calls in launchResolverActivityWithCorrectTab() and canForward() are removed, since sanitizeIntent() now covers the selector itself. As a result, launchChooserActivityWithCorrectTab(), which calls sanitizeIntent() on intentReceived and innerIntent, also sanitizes their selectors."
     ],
     "completeness": [
      "The type is resolved exactly once in onCreate(). The same string is used for canForwardTo() on the root intent, canForwardTo() on the selector, and the target-user resolveActivityAsUser() call.",
      "launchResolverActivityWithCorrectTab() still calls the 3-arg mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, callingUserId) (code context at android-security-16.0.0_r8 line 526). That call re-derives the type from the intent via resolveTypeIfNeeded instead of using the pre-computed resolvedType. It decides which user the resolver is launched in, not whether forwarding is allowed.",
      "The 3-arg and ResolveInfoFlags resolveActivityAsUser overloads remain and still derive the type from the intent. In this diff, only IntentForwarderActivity's target-user resolution was switched to the explicit-type overload.",
      "After commit 7d8be5eb6753, sanitizeIntent() clears one level of selector and nulls the selector's own selector. Sanitization is applied to the forwarded copy in canForward(), to intentReceived in both launch*WithCorrectTab paths, and to the EXTRA_INTENT inner intent in launchChooserActivityWithCorrectTab().",
      "packages/modules/IntentResolver has its own copy of IntentForwarderActivity (per index search) whose canForward() already takes resolvedType. The material does not show whether its sanitizeIntent() received the selector change from 7d8be5eb6753."
     ],
     "behavior_changes": [
      "New hidden PackageManager APIs resolveActivityAsUser(Intent, String, int, int) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int). The ResolveInfoFlags variant, which the int variant delegates to, throws UnsupportedOperationException in PackageManager subclasses that do not override it.",
      "IntentForwarderActivity.canForward() static signature changed: the last parameter is String resolvedType instead of ContentResolver. Callers (AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker) must pass the resolved type.",
      "The skip-user-confirmation privilege check now keys on getLaunchedFromPackage() rather than getCallingPackage().",
      "Intent selectors that are forwarded or handed to the resolver/chooser have their package, component and nested selector cleared.",
      "IntentForwarderActivity.Injector gains resolveActivityAsUser(Intent, String, int, int), getExecutorService() and isDeviceProvisioned(). Custom injectors must implement them."
     ],
     "preconditions": [
      "The device has a profile relationship (managed profile or profile parent), so that intents resolve to IntentForwarderActivity's FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE components. onCreate() wtf-logs otherwise.",
      "An app in one profile starts an intent that is routed to IntentForwarderActivity for cross-profile forwarding. The intent's type is derived via resolveTypeIfNeeded, which for content data URIs without an explicit type queries the content provider (general platform knowledge).",
      "Per index search, ComputerEngine.canForwardTo enforces INTERACT_ACROSS_USERS_FULL on its caller."
     ],
     "per_major": {
      "14": "Adapted fix: commit 042b52a8551f in android-security-14.0.0_r24 carries the same PackageManager, ApplicationPackageManager, IntentForwarderActivity and AbstractMultiProfilePagerAdapter changes. The onCreate() hunk differs because Android 14 has no isPrivateProfile branch, and launchResolverActivityWithCorrectTab has no singleTabOnly. The test hunk keeps the original assertEquals(ACTION_VIEW) line. At android-security-14.0.0_r29, 6/8 lines of the patched region are present. This would fit a later selector refactor, but the material does not confirm it.",
      "15": "Same fix: commit bf9e58d7edb5 in android-security-15.0.0_r12, with the same hunks as the Android 16 commit, including the isPrivateProfile path. 6/8 lines are present at android-security-15.0.0_r17.",
      "16": "Same fix: commit 28579dff4305 in android-security-16.0.0_r3. Follow-ups 71d6dc109528 (test/injector) and 7d8be5eb6753 (selector sanitization moved into sanitizeIntent) are in android-security-16.0.0_r8. 6/8 lines of the original patched region are present at r8, which fits 7d8be5eb6753 removing the explicit selector sanitize calls (not confirmed as the cause).",
      "17": "Core fix already present in the branch (8/8 lines of the patched region at android-security-17.0.0_r1). The commit linked by Change-Id is the test/injector follow-up 323c8b968a88 ('Fix & speed up IntentForwarderActivityTest')."
     },
     "backport_notes": [
      "Apply in order: 28579dff4305 (adds the resolvedType overloads, changes the canForward signature, adds the selector sanitize calls), then 71d6dc109528 (Injector.getExecutorService/isDeviceProvisioned, test fixes), then 7d8be5eb6753. The last one touches only IntentForwarderActivity.java, applies on top of the state left by 71d6dc109528, and removes the selector sanitize lines added by the first commit. Any PackageManager subclass or Injector implementation in the tree must implement the new resolveActivityAsUser(Intent, String, ...) overloads; otherwise PackageManager subclasses hit the default UnsupportedOperationException.",
      "If the device ships the IntentResolver module copy of IntentForwarderActivity (packages/modules/IntentResolver, per index search), check that copy separately. It already has the resolvedType parameter, but the material does not show whether its sanitizeIntent() clears the selector's package, component and nested selector."
     ],
     "confidence": "high",
     "unknowns": [
      "Which of the three bug IDs (403565650, 407763772, 397216638) maps to the type TOCTOU, the getCallingPackage change, or the selector sanitization.",
      "Whether commit 7d8be5eb6753 (sanitizeIntent clears the nested selector) was applied to the Android 14 and 15 security branches. The 6/8 line counts suggest the region changed, but the material does not show those commits.",
      "Whether the packages/modules/IntentResolver copy of IntentForwarderActivity received equivalent selector sanitization."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48566": {
     "cve": "CVE-2025-48566",
     "summary": "IntentForwarderActivity now resolves the incoming intent's MIME type once and passes that fixed string through canForward/canForwardTo and a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload, instead of re-deriving the type from the intent at each step; it also uses getLaunchedFromPackage() for the skip-user-confirmation privilege check and sanitizes the received intent's selector alongside the received intent before relaunch.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity, android.content.pm.PackageManager / ApplicationPackageManager)",
     "weakness": "Time-of-check/time-of-use on intent MIME type resolution (check and use derive the type independently from attacker-influenced intent), plus a wrong caller identity source for a privilege check and incomplete selector sanitization",
     "root_cause": "In IntentForwarderActivity.onCreate the cross-profile permission check canForward -> canForwardInner called intent.resolveTypeIfNeeded(contentResolver) itself and passed that to IPackageManager.canForwardTo. The actual resolution that follows, Injector.resolveActivityAsUser -> ApplicationPackageManager.resolveActivityAsUser, called intent.resolveTypeIfNeeded(...) again before mPM.resolveIntent. The type is therefore derived twice from caller-controlled intent data; as the commit message states, if the intent's type changes after the canForward check, the forwarded intent can be resolved against a type that was not checked. Separately, the EXTRA_SKIP_USER_CONFIRMATION privilege check used getCallingPackage() rather than the launching package, and in the path that relaunches intentReceived only intentReceived was sanitized while its selector was not.",
     "fix_analysis": [
      "core/java/android/content/pm/PackageManager.java: adds two hidden overloads resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId); the javadoc says resolvedType is given explicitly when the type contained in the intent cannot be trusted, null falls back to Intent#resolveTypeIfNeeded. The ResolveInfoFlags variant throws UnsupportedOperationException by default (same pattern as other non-abstract overloads such as resolveActivity(Intent, ResolveInfoFlags)).",
      "core/java/android/app/ApplicationPackageManager.java: resolveActivity(Intent, ResolveInfoFlags), resolveActivityAsUser(Intent, int, int) and resolveActivityAsUser(Intent, ResolveInfoFlags, int) now delegate with resolvedType=null; the new resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) calls mPM.resolveIntent with the given resolvedType, only calling intent.resolveTypeIfNeeded when resolvedType is null.",
      "core/java/com/android/internal/app/IntentForwarderActivity.java onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...) and then to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId).",
      "IntentForwarderActivity.canForward / canForwardInner: signature changed from ContentResolver to String resolvedType; the local intent.resolveTypeIfNeeded(contentResolver) in canForwardInner is removed and the passed-in resolvedType is given to packageManager.canForwardTo for both the root intent and the selector. ContentResolver import removed.",
      "IntentForwarderActivity (skip-user-consent block): String callingPackage = getCallingPackage() replaced with getLaunchedFromPackage(), so the privilegedCallerAskedToSkipUserConsent decision is based on the launching package.",
      "IntentForwarderActivity (path that sanitizes intentReceived and adds EXTRA_SELECTED_PROFILE / EXTRA_CALLING_USER before relaunch): after sanitizeIntent(intentReceived), adds sanitizeIntent(intentReceived.getSelector()) when a selector is present.",
      "IntentForwarderActivity.Injector / InjectorImpl: new resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) that calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId) via CompletableFuture.supplyAsync; the old 3-arg method is kept.",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents: adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver) per intent (check-only path, no resolution follows there).",
      "Follow-up commit 71d6dc109528 (16 r8) / 323c8b968a88 (17 r1) 'Fix & speed up IntentForwarderActivityTest': makes the executor (Injector.getExecutorService) and the device-provisioned check (Injector.isDeviceProvisioned) injectable, adds HIDE_OVERLAY_WINDOWS to the test manifest, corrects the backwards assertNull assertions introduced by the first commit, removes the 30 s awaitTermination in the test wrapper, and re-enables IntentForwarderActivityTest in core/tests/coretests/Android.bp. InjectorImpl still returns Executors.newSingleThreadExecutor() and reads the same Settings.Global.DEVICE_PROVISIONED value, so production logic is equivalent."
     ],
     "completeness": [
      "Covers the forwarder's own flow: the type is resolved once in onCreate and reused for canForwardTo on the root and the selector and for the target-user resolveIntent, so the check and the resolution see the same type string.",
      "Only the IntentForwarderActivity path uses the explicit-type overload; all pre-existing PackageManager.resolveActivity*/resolveActivityAsUser callers keep re-resolving the type from the intent (resolvedType=null), so the new API is opt-in.",
      "The selector is now checked by canForwardTo with the root intent's resolvedType rather than a type derived from the selector itself, as canForwardInner did before; the material does not say whether this was intended.",
      "Sanitization of intentReceived.getSelector() is added only in the relaunch path; canForward already sanitized the selector of its forwardIntent copy.",
      "getLaunchedFromPackage() replaces getCallingPackage() only in the skip-user-confirmation check; no other caller-identity uses are shown in the diff.",
      "The first commit's test contained inverted assertions (assertNull on a captured intent that must be non-null) and the test was excluded in Android.bp; the follow-up commit fixes and re-enables it. For 14 and 15 the material shows only the first commit."
     ],
     "behavior_changes": [
      "New hidden PackageManager overloads resolveActivityAsUser(Intent, String, int, int) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int); custom PackageManager subclasses that do not override the ResolveInfoFlags variant throw UnsupportedOperationException if it is called.",
      "IntentForwarderActivity.canForward static signature changed (ContentResolver -> String resolvedType); callers such as AbstractMultiProfilePagerAdapter must be updated.",
      "Injector interface gained resolveActivityAsUser(Intent, String, int, int) and, with the follow-up, getExecutorService() and isDeviceProvisioned(); test injectors must implement them.",
      "Forwarded intents are resolved in the target user with the type string computed before the canForward check, not a freshly re-derived one.",
      "The selector passed to canForwardTo is evaluated with the root intent's resolved type.",
      "EXTRA_SKIP_USER_CONFIRMATION privilege is evaluated against the launching package (getLaunchedFromPackage) rather than getCallingPackage.",
      "In the relaunch path the selector of the received intent is sanitized as well as the intent itself."
     ],
     "preconditions": [
      "A device with more than one profile where cross-profile intent filters resolve to IntentForwarderActivity (FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE), so that an app's intent is routed through onCreate of this activity.",
      "IPackageManager.canForwardTo (ComputerEngine.canForwardTo, per index search) enforces INTERACT_ACROSS_USERS_FULL and decides based on intent plus resolvedType, which is why the type string matters for the check.",
      "For the getLaunchedFromPackage change: an intent carrying EXTRA_SKIP_USER_CONFIRMATION reaching the managed-profile forwarding path."
     ],
     "per_major": {
      "14": "adapted fix: commit 042b52a8551f in android-security-14.0.0_r24, same core changes (PackageManager/ApplicationPackageManager overloads, single resolvedType, getLaunchedFromPackage, selector sanitization) applied to older surrounding code (no isPrivateProfile branch; relaunch path calls startActivityAsCaller directly); the test diff differs slightly. Follow-up test fix not shown for 14. 6/8 lines of the patched region present at r29.",
      "15": "same fix: commit bf9e58d7edb5 in android-security-15.0.0_r12, content matches the Android 16 commit. Follow-up test fix not shown for 15. 6/8 lines present at r17.",
      "16": "same fix: commit 28579dff4305 in android-security-16.0.0_r3, plus follow-up test/injector commit 71d6dc109528 in android-security-16.0.0_r8. 6/8 lines present at r8.",
      "17": "resolved by change_id at android-security-17.0.0_r1; the commit shown is the follow-up 323c8b968a88 (test/injector fix). 8/8 lines of the patched region present at r1; a separate main-fix commit for 17 is not shown."
     },
     "backport_notes": [
      "Apply the PackageManager and ApplicationPackageManager overloads first; IntentForwarderActivity and AbstractMultiProfilePagerAdapter depend on the new signatures and will not compile without them. Any other implementors of IntentForwarderActivity.Injector or callers of the static canForward must be updated. The follow-up commit is not needed for production logic but is needed to make IntentForwarderActivityTest pass and to re-enable it.",
      "The IntentResolver module copy (packages/modules/IntentResolver/.../IntentForwarderActivity.java) already has canForward/canForwardInner taking String resolvedType per index search; if a fork ships a modified module, verify it too (general platform knowledge that IntentResolver is a separate module)."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of sanitizeIntent is not in the material; the test only shows that getPackage() is null on the root and selector intents passed to canForwardTo.",
      "How the intent's type could change between the canForward check and resolution is not described beyond the commit message.",
      "Which of the three linked bugs (403565650, 407763772, 397216638) corresponds to each of the three code changes (type TOCTOU, getLaunchedFromPackage, selector sanitization) is not shown.",
      "Why 6/8 lines of the patched region are reported present on the latest 14/15/16 tags versus 8/8 on 17 is not explained by the material.",
      "Whether the follow-up test-fix commit was applied to Android 14 and 15 is not shown.",
      "The main-fix commit for Android 17 is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0008": {
     "cve": "CVE-2026-0008",
     "summary": "FaceEnroll in Settings now strips the MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE extra (alongside Intent.EXTRA_USER_ID) from the forwarded intent when the activity was launched by a package other than Settings itself.",
     "component": "platform/packages/apps/Settings, biometrics face enrollment (FaceEnroll trampoline activity)",
     "weakness": "Untrusted caller-controlled intent extra forwarded into an internal flow (missing sanitisation of extras from external packages)",
     "root_cause": "FaceEnroll.onCreate builds nextIntent for the real enrollment activity and, per its own comment, includes any existing extras from the current intent. Before the fix, the only extra removed for launches from an external package (launchedFromProvider() != packageName) was Intent.EXTRA_USER_ID. The EXTRA_ENROLL_AFTER_FACE extra, which the updated test populates with a PendingIntent, was forwarded unchanged, so an external caller could pass that key into Settings' downstream enrollment activity.",
     "fix_analysis": [
      "src/com/android/settings/biometrics/face/FaceEnroll.kt:onCreate: inside the existing block guarded by `if (launchedFromProvider() != packageName)`, add `nextIntent.removeExtra(EXTRA_ENROLL_AFTER_FACE)` next to the existing `nextIntent.removeExtra(Intent.EXTRA_USER_ID)`; add the import of com.android.settings.biometrics.MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE. The remaining hunks (class declaration spacing, comment reflow, trailing commas, onActivityResult condition formatting) are formatting only.",
      "tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt: the test intent now also carries EXTRA_ENROLL_AFTER_FACE as a PendingIntent; the internal-launch test expects 3 extras including EXTRA_ENROLL_AFTER_FACE, and the renamed external-launch test (testFinishAndLaunchDefaultActivity_fromExternal_dropUserAndDropEnrollAfterFace) asserts both EXTRA_USER_ID and EXTRA_ENROLL_AFTER_FACE are absent and only the generic test extra survives. The mockito whenever import also switches to org.mockito.kotlin."
     ],
     "completeness": [
      "Covers the single forwarding point in FaceEnroll.onCreate for callers whose launchedFromPackage differs from Settings' own package name.",
      "The approach remains a denylist: all other extras from the incoming intent are still copied into nextIntent; only EXTRA_USER_ID and EXTRA_ENROLL_AFTER_FACE are removed. Any other extra that downstream enrollment activities treat as trusted is not addressed by this change (none is shown in the material).",
      "Launches where launchedFromPackage equals Settings' package keep the extra, by design.",
      "The downstream consumer of EXTRA_ENROLL_AFTER_FACE is not in the material, so whether it performs its own caller validation is unknown."
     ],
     "behavior_changes": [
      "For externally launched FaceEnroll, the EXTRA_ENROLL_AFTER_FACE extra is silently dropped before the real enrollment activity starts; the diff adds no error or log for the drop.",
      "No change for launches originating from Settings itself."
     ],
     "preconditions": [
      "An app other than Settings starts the FaceEnroll activity with an intent carrying EXTRA_ENROLL_AFTER_FACE. The existing code already tells external launches apart via launchedFromPackage, which suggests the activity is reachable from outside Settings; the manifest export state is not in the material."
     ],
     "per_major": {
      "14": "not affected per the bulletin; code absent (FaceEnroll.kt and FaceEnrollTest.kt do not exist at android-security-14.0.0_r29)",
      "15": "not affected per the bulletin; code absent (FaceEnroll.kt and FaceEnrollTest.kt do not exist at android-security-15.0.0_r17)",
      "16": "same fix: commit 059c9678eebe in android-security-16.0.0_r4 (cherry-pick of 7ac22f2eae5d); at android-security-16.0.0_r8, 7/8 lines of the patched region are present and the EXTRA_ENROLL_AFTER_FACE import is visible in the code context",
      "17": "not affected per the bulletin (Android 17 not listed); at android-security-17.0.0_r1, 7/8 lines of the patched region are present"
     },
     "backport_notes": [
      "The functional change is one line plus one import; the rest of the FaceEnroll.kt hunk is formatting and can be dropped if it conflicts. It requires MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE and the existing launchedFromProvider / removeExtra(EXTRA_USER_ID) block to be present; Android 14 and 15 do not have FaceEnroll.kt at all."
     ],
     "confidence": "high",
     "unknowns": [
      "How the downstream enrollment flow consumes EXTRA_ENROLL_AFTER_FACE (the PendingIntent) and what privilege it is sent with; not in the material.",
      "Whether FaceEnroll is exported or protected by a permission in the manifest.",
      "Whether device or feature conditions (for example face hardware support) gate reaching FaceEnroll.",
      "Which single line differs at android-security-16.0.0_r8 and android-security-17.0.0_r1 (7/8 lines present).",
      "Why the bulletin/OSV list 17-next while the per-major facts treat Android 17 as not listed."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0010": {
     "cve": "CVE-2026-0010",
     "summary": "Replaces a fixed 16-byte stack buffer filled by sprintf(\"%lu\", (unsigned long)fd) with String8::format(\"%d\", fd) when BnDrmManagerService::onTransact stores the \"FileDescriptorKey\" value of a DrmInfoRequest in drmserver.",
     "component": "platform/frameworks/av, drm/common (drmserver, IDrmManagerService binder server side)",
     "weakness": "Stack-based buffer overflow (fixed-size buffer written with unbounded sprintf)",
     "root_cause": "In drm/common/IDrmManagerService.cpp, BnDrmManagerService::onTransact has a case that builds drmInfoRequest. Given the hunk position (line 1001) and the fact that case ACQUIRE_DRM_INFO starts at line 985 at r8, this is likely the ACQUIRE_DRM_INFO case. When a key equals \"FileDescriptorKey\", that case called data.readFileDescriptor() and wrote the int result into a local 'char buffer[16]' with sprintf(buffer, \"%lu\", (unsigned long)fd). Casting a signed int to unsigned long and printing it with an unbounded sprintf into a 16-byte stack buffer can write past the buffer, for example when the value is negative. The material does not show exactly which input values trigger it.",
     "fix_analysis": [
      "drm/common/IDrmManagerService.cpp:BnDrmManagerService::onTransact: the fix deletes the 'char buffer[16]' local and the sprintf(buffer, \"%lu\", (unsigned long)fd) call.",
      "The value is now built with String8::format(\"%d\", fd), which allocates a string of the needed size and prints the fd as a signed int, so there is no fixed-size destination left to overflow.",
      "drmInfoRequest->put(key, ...) takes the formatted String8 directly. Nothing else in the handler changes."
     ],
     "completeness": [
      "The fix covers the single sprintf into a fixed buffer in the FileDescriptorKey branch of the DrmInfoRequest-building case in onTransact. That is the only change in the diff.",
      "The fd value itself is still not validated. Whatever readFileDescriptor() returns is stored as a decimal string in the DrmInfoRequest. The material does not show how consumers handle such a value.",
      "The PROCESS_DRM_INFO case shown in the context (lines 932-983) does not use the FileDescriptorKey/sprintf pattern and is not touched. It already checks bufferSize against data.dataAvail()."
     ],
     "behavior_changes": [
      "The stored FileDescriptorKey value changes from an unsigned long decimal to a signed int decimal. For non-negative fds the string is the same. A negative fd would now be printed as a negative number.",
      "No new error codes or rejections. Control flow of the transaction is unchanged."
     ],
     "preconditions": [
      "The ability to send binder transactions to the drmserver IDrmManagerService (BnDrmManagerService::onTransact) for the case that builds a DrmInfoRequest (likely ACQUIRE_DRM_INFO).",
      "A key entry equal to \"FileDescriptorKey\" in the transaction. The exact fd values that overflow the buffer are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/av 8bf16f1ae904 in android-security-14.0.0_r25, identical hunk. At android-security-14.0.0_r29, 5/8 lines of the patched region are present.",
      "15": "same fix: platform/frameworks/av c8713a065232 in android-security-15.0.0_r13, identical diff. At android-security-15.0.0_r17, 5/8 lines of the patched region are present.",
      "16": "same fix: platform/frameworks/av ebdbf918dd87 in android-security-16.0.0_r4 (cherry-pick of internal 358b3457 / 5dd95d00, Change-Id I6efecac362757c10b2308d020440eac5edc5b873). At android-security-16.0.0_r8, 5/8 lines of the patched region are present.",
      "17": "not affected (not listed by the bulletin). At android-security-17.0.0_r1, 5/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Single-hunk, self-contained change. Google merged the identical hunk on 14, 15 and 16, though the Android 14 pre-image blob differs from the one on 15/16. If the file has drifted, check that the hunk lands in the FileDescriptorKey branch of the DrmInfoRequest case and not in PROCESS_DRM_INFO."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which transaction case contains the hunk is inferred from line positions and the drmInfoRequest variable. The patched lines are not in the provided context excerpt.",
      "Which fd values readFileDescriptor() can return here, and so the exact trigger condition, is not shown.",
      "Which SELinux domains or clients can reach the drmserver binder interface is not shown in the material.",
      "How downstream DRM plugins consume the FileDescriptorKey string is not shown.",
      "Why only 5/8 lines of the patched region match at each latest tag is not explained.",
      "The content of the second internal Cherrypick-From commit (5dd95d00) is not shown. The AOSP commit contains only this one hunk."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0054": {
     "cve": "CVE-2026-0054",
     "summary": "Adds a caller check to the binder exposed by SystemUI's WalletContextualLocationsService. Every incoming transaction is now rejected with a SecurityException unless the calling UID owns a privileged package that holds the android.app.role.SYSTEM_UI_INTELLIGENCE role.",
     "component": "platform/frameworks/base, SystemUI (packages/SystemUI wallet controller, WalletContextualLocationsService)",
     "weakness": "Missing permission / caller authorization check on an exposed binder interface",
     "root_cause": "WalletContextualLocationsService is a LifecycleService in SystemUI that sits between QuickAccessWalletService and ContextualCardManager (PCC). It returned an IWalletContextualLocationsService.Stub binder whose methods addWalletCardsUpdatedListener and onWalletContextualLocationsStateUpdated passed calls straight to the internal implementations, with no check in code of who the caller was. Per the commit message, the service has a designed target caller, but the code did not enforce it.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt: the anonymous IWalletContextualLocationsService.Stub now overrides onTransact(code, data, reply, flags). Before delegating to super.onTransact it calls isCallerAllowed(). If that returns false, it throws SecurityException(\"Caller is not allowed.\"). Because the check sits in onTransact, it applies to every AIDL method on the interface, not just the two currently defined.",
      "WalletContextualLocationsService.kt: new private isCallerAllowed(). It reads Binder.getCallingUid() and clears the calling identity. It gets packageManager.getPackagesForUid(uid) and returns false if that is null or empty. It fetches RoleManager.getRoleHolders(\"android.app.role.SYSTEM_UI_INTELLIGENCE\"), using an empty list if RoleManager is null. It returns true only if at least one package of the UID is a role holder AND packageManager.getApplicationInfo(packageName, 0).isPrivilegedApp() is true. NameNotFoundException counts as not allowed. The identity is restored in a finally block.",
      "WalletContextualLocationsService.kt companion object: adds the constant ROLE_SYSTEM_UI_INTELLIGENCE = \"android.app.role.SYSTEM_UI_INTELLIGENCE\". It also adds imports for RoleManager, PackageManager, Binder and Parcel."
     ],
     "completeness": [
      "The check is in onTransact, so it gates every transaction dispatched through the Stub's onTransact. That includes addWalletCardsUpdatedListener, onWalletContextualLocationsStateUpdated and any future AIDL methods.",
      "Authorization is based on a role plus privileged-app status, not on a manifest permission. The commit message calls this a quick fix and says a new permission should replace it. If RoleManager is null, the role holder list is empty and all callers are rejected (fail closed).",
      "The internal methods addWalletCardsUpdatedListenerInternal and onWalletContextualLocationsStateUpdatedInternal have no guard of their own. In the code shown they are only reached through the binder, and no other entry point is visible in the material.",
      "The material does not show whether the service is exported in the SystemUI manifest or protected there by a permission. The fix does not touch the manifest."
     ],
     "behavior_changes": [
      "Callers that are not privileged apps holding the SYSTEM_UI_INTELLIGENCE role now get a SecurityException on every transaction to the service's binder.",
      "Each transaction now runs PackageManager and RoleManager lookups under a cleared calling identity, which adds a small cost per call.",
      "Legitimate callers must hold the SYSTEM_UI_INTELLIGENCE role and be privileged apps. The commit says this matches the designed target caller and the existing primary callers."
     ],
     "preconditions": [
      "The caller must be able to bind to WalletContextualLocationsService in the SystemUI process and obtain its IWalletContextualLocationsService binder. Export and permission status in the manifest is not shown in the material.",
      "Before the fix, the binder methods had no role, permission or UID check in code.",
      "After the fix, the calling UID must own a package that is both a SYSTEM_UI_INTELLIGENCE role holder and a privileged app."
     ],
     "per_major": {
      "14": "adapted fix: commit a83edee34b0d in android-security-14.0.0_r28. The onTransact/isCallerAllowed logic is the same, but the diff also reformats the existing binder object declaration (older indentation), and the Android 14 file has no DeadObjectException import. The patched region is fully present at android-security-14.0.0_r29 (8/8 lines).",
      "15": "same fix: commit 3717e022bc2a in android-security-15.0.0_r16. The logic is the same as Android 16, but the import context differs (no DeadObjectException import) and so do the line offsets. The patched region is fully present at android-security-15.0.0_r17 (8/8 lines).",
      "16": "same fix: commit a8f3c0ccb472 in android-security-16.0.0_r7 (Change-Id I7e8b2bf83727464369b94c63e208c5717601a51f). The patched region is fully present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "not affected: the bulletin does not list Android 17. The patched region (8/8 lines) is present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The change is one Kotlin file. On branches with the older binder formatting (as in Android 14), or with different imports, apply the logic rather than the hunk verbatim. STS tests are in a separate commit (I4c59ae71f66035d2aeeb9b9e4dd13d7911e5fae5) that is not in the material.",
      "After this fix, forks with no SYSTEM_UI_INTELLIGENCE role holder, or whose intended caller is not a privileged app, will have that caller rejected. Check that the intended caller meets both conditions before backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "What information is actually disclosed. The bulletin only classifies the issue as ID, and the listener payload is not shown.",
      "Whether the service is exported in the SystemUI AndroidManifest, and whether any manifest-level permission existed before the fix.",
      "What the separate STS test commit and the two internal cherry-pick sources named in the commit message contain.",
      "Whether the service and the IWalletContextualLocationsService AIDL exist unchanged on all affected branches, beyond what the three shown diffs cover."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0065": {
     "cve": "CVE-2026-0065",
     "summary": "The \"bound by foreground UID\" background activity launch (BAL) exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned window (new ActivityTaskManagerService.hasActiveVisibleNotPinnedWindow), while foreground-service start checks keep using the original any-visible-window predicate.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManager background activity launch (BAL) controls",
     "weakness": "Insufficient authorization check (overly permissive visibility condition in a BAL exemption)",
     "root_cause": "BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed allows an activity start when the process is bound by a client UID that is \"foreground\". That client UID must have been added via addBoundClientUid with BIND_ALLOW_ACTIVITY_STARTS. Before the fix, isBoundByForegroundUid() decided \"foreground\" with the single mUidHasActiveVisibleWindowPredicate, which is ActivityTaskManagerService.hasActiveVisibleWindow. That method returns true for any visible non-toast window, including a picture-in-picture (pinned) activity. Regular BAL rules in the same controller block a caller whose own visible task is pinned (see testForegroundTaskBlockedIfPinned). So a client that only had a PiP window was enough to let a bound process launch background activities, which is inconsistent with normal BAL behavior.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java: adds hasActiveVisibleNotPinnedWindow(int uid). It returns true if mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity(uid), and otherwise falls back to mActiveUids.hasNonAppVisibleWindow(uid), the same fallback as hasActiveVisibleWindow.",
      "services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java: the constructor takes a second IntPredicate, uidHasActiveVisibleNotPinnedWindowPredicate, stored in mUidHasActiveVisibleNotPinnedWindowPredicate. isBoundByForegroundUid() now takes an IntPredicate parameter and tests each UID in mBalOptInBoundClientUids against it.",
      "services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java:areBackgroundActivityStartsAllowed: on Android 16 the bound-by-foreground branch is still gated on checkConfiguration.checkVisibility and appSwitchState != APP_SWITCH_DISALLOW. It now picks the predicate by checkConfiguration.isCheckingForFgsStart: FGS start checks keep mUidHasActiveVisibleWindowPredicate, and activity start checks use mUidHasActiveVisibleNotPinnedWindowPredicate.",
      "services/core/java/com/android/server/wm/WindowProcessController.java: the constructor passes atm::hasActiveVisibleNotPinnedWindow as the new second argument when it builds BackgroundLaunchProcessController.",
      "services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java (commit 66c209d1f65f): adds the mHasActiveNotPinnedVisibleWindow set and mFgsCheckConfiguration, and splits testBoundByForeground into an FGS variant and a BAL variant.",
      "Follow-up commit 2f86ea93fcdd (android-security-16.0.0_r8, test only): testBoundByForegroundFgs now uses mFgsCheckConfiguration. It removes testBoundByForegroundOld because that scenario (client only in mHasActiveVisibleWindow, BAL configuration) now yields BAL_BLOCK."
     ],
     "completeness": [
      "The fix covers only the bound-by-foreground-UID exemption path in BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed. FGS start checks still use the hasActiveVisibleWindow predicate unchanged, by design (commit message: FGS starts are still allowed with PiP windows).",
      "hasActiveVisibleNotPinnedWindow still falls back to mActiveUids.hasNonAppVisibleWindow(uid). A client UID with a visible non-app window therefore still satisfies the exemption; only the app-activity part is filtered for pinned mode.",
      "The fix relies on mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity, which is not part of the diff and is assumed to already exist on every branch.",
      "This fix does not change other callers of ActivityTaskManagerService.hasActiveVisibleWindow. The material does not show whether any of them feed BAL decisions."
     ],
     "behavior_changes": [
      "If the only qualifying bound client UID of a process has just a pinned (PiP) activity visible, an activity start from that process is no longer allowed through the bound-by-foreground exemption. The verdict falls through to the remaining checks (BAL_BLOCK in the unit test scenario).",
      "FGS start checks (isCheckingForFgsStart) are unchanged and still accept a PiP window of the binding client.",
      "The Android 14 backport also applies appSwitchState != APP_SWITCH_DISALLOW to this exemption, which the original 14 code did not do.",
      "The Android 15 backport removes the Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid() gate, so the app-switch-state condition is unconditional on that branch."
     ],
     "preconditions": [
      "The target process is bound by a client with Context.BIND_ALLOW_ACTIVITY_STARTS, so the client UID is in mBalOptInBoundClientUids (addBoundClientUid).",
      "The binding client UID has a visible activity only in pinned (picture-in-picture) windowing mode and no other qualifying visible window.",
      "The BAL check reaches the bound-by-foreground branch: checkConfiguration.checkVisibility is true, appSwitchState != APP_SWITCH_DISALLOW, and no earlier exemption matched.",
      "The check is an activity start check, not an FGS start check. Per index search, WindowProcessController.areBackgroundFgsStartsAllowed uses CHECK_FOR_FGS_START and keeps the old predicate."
     ],
     "per_major": {
      "14": "Adapted fix (407d7b083010 in android-security-14.0.0_r28). Same new ATMS method, predicate and constructor change. The condition uses a plain isCheckingForFgsStart parameter and also newly adds appSwitchState != APP_SWITCH_DISALLOW. The return path is logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW). The shown diff has no test changes. 8/8 lines present at 14.0.0_r29.",
      "15": "Adapted fix (3d4a174f1def in android-security-15.0.0_r16). Same core change. It also removes the Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid() gate and the Flags import, and returns BalVerdict(BAL_ALLOW_VISIBLE_WINDOW). Tests are updated with the mInPinnedWindowMode rename and the split tests. 8/8 lines present at 15.0.0_r17.",
      "16": "Same fix (66c209d1f65f in android-security-16.0.0_r7), using checkConfiguration.isCheckingForFgsStart and balImprovedMetrics() verdict codes, plus the test-only follow-up 2f86ea93fcdd in android-security-16.0.0_r8. 8/8 lines present at 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 patch calls mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity(uid) without adding it. None of the shown 14/15/16 diffs add it, so confirm it exists in the target tree. The constructor signature change of BackgroundLaunchProcessController means every instantiation must be updated (WindowProcessController and the wmtests test class).",
      "On 16, apply 66c209d1f65f first, then the test-only 2f86ea93fcdd. On 14 and 15 the condition has a different shape (isCheckingForFgsStart is a direct parameter, there is no BalCheckConfiguration), and the 15 variant also deletes a flag check. Cherry-pick the branch-specific commits rather than the 16 one."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of VisibleActivityProcessTracker.hasVisibleNotPinnedActivity is not shown.",
      "Beyond saying that a PiP window of the binding client was sufficient, the commit messages do not state the concrete trigger scenario. The bulletin gives only High / EoP.",
      "The material does not show whether any other caller of ActivityTaskManagerService.hasActiveVisibleWindow contributes to BAL decisions."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-0084": {
     "cve": "CVE-2026-0084",
     "summary": "Makes the NFC HostEmulationManager unbind non-preferred HCE service connections right away in two cases, instead of keeping the binding alive until the next NFC transaction: when the preferred service is unset (the app leaves the foreground), and when the regular connection's binding dies.",
     "component": "platform/packages/modules/Nfc, NfcNci card emulation (HostEmulationManager)",
     "weakness": "Improper resource release / missing cleanup: the NFC stack keeps its binding to an app's HCE service after the triggering condition ends, because the deferred-unbind logic only runs after a new NFC transaction",
     "root_cause": "In multiple-binding mode (isMultipleBindingSupported(), introduced with the change referenced as b/365533082; the TODO links this to Flags#allowMultipleHceBindings), the no-arg unbindServiceIfNeededLocked() keeps bindings in mComponentNameToConnectionsMap. It has a branch whose condition starts with mServiceName == null or the DONT_IMMEDIATELY_UNBIND_SERVICES compat change. Per the new javadoc, those bindings are cleared only when mUnbindInactiveServicesRunnable runs after a transaction completes. Two paths called this deferred variant: the preferred-service update path (the service == null branch, i.e. preferred service unset) and the onBindingDied callback of a non-payment ServiceConnection. As a result, per the commit message, when an app with a registered HCE service called setPreferredService and then moved to the background or was closed, the NFC stack kept the binding to the app's service alive until the next NFC transaction or reboot.",
     "fix_analysis": [
      "Commit a90c62ca6e75 (android-security-16.0.0_r7), NfcNci/src/com/android/nfc/cardemulation/HostEmulationManager.java: moves unbindInactiveServicesLocked() out of the anonymous mUnbindInactiveServicesRunnable into a private method on the class. The logic is unchanged: for every entry in mComponentNameToConnectionsMap whose key differs from mAidCache.getPreferredService() and whose mMessenger != null, it calls mContext.unbindService(), and it keeps only the preferred entry. The commit also adds a Log.d line.",
      "Same commit: adds an overload, unbindServiceIfNeededLocked(boolean force). The no-arg version now delegates with force=false, which keeps the documented deferred behavior for polling loops and for switching services within a transaction. With force=true and isMultipleBindingSupported(), the method calls unbindInactiveServicesLocked() right away and returns before reaching the deferral branch.",
      "Same commit: on the preferred-service update path (hunk starting at old line 766: if service != null bindServiceIfNeededLocked(userId, service), else unbind), the else branch now calls unbindServiceIfNeededLocked(/* force */ true).",
      "Commit 6bc72fe8b34e (android-security-16.0.0_r8), same file: in the ServiceConnection.onBindingDied(ComponentName) at about old line 1770 (not the payment connection's onBindingDied), unbindServiceIfNeededLocked() becomes unbindServiceIfNeededLocked(/* force */ true)."
     ],
     "completeness": [
      "The force path only takes effect when isMultipleBindingSupported() is true. Otherwise the code falls through to the existing unbind logic, which the diff does not change.",
      "unbindInactiveServicesLocked() keeps the entry equal to mAidCache.getPreferredService(). What that call returns when the preferred service is unset is not shown.",
      "In unbindInactiveServicesLocked(), non-preferred connections with mMessenger == null are not unbound through mContext.unbindService(), but they are dropped from mComponentNameToConnectionsMap. This logic existed before the fix and is carried over unchanged.",
      "The payment ServiceConnection's onBindingDied (context at line 1624, which rebinds through bindPaymentServiceLocked) is not touched by either commit.",
      "Other callers of the no-arg unbindServiceIfNeededLocked() keep the deferred behavior on purpose, as the new javadoc documents."
     ],
     "behavior_changes": [
      "When the preferred HCE service is unset (multiple-binding mode), non-preferred HCE service bindings are unbound immediately instead of when mUnbindInactiveServicesRunnable runs after the next transaction.",
      "When the regular service connection's binding dies, inactive bindings are cleared immediately.",
      "A new Log.d line is written for each unbound service in unbindInactiveServicesLocked(). An IllegalArgumentException from unbindService is still caught and logged with Log.wtf.",
      "No API changes. mComponentNameToConnectionsMap is replaced by a map that holds only the retained preferred entry."
     ],
     "preconditions": [
      "isMultipleBindingSupported() returns true (the material implies a link to Flags.allowMultipleHceBindings).",
      "An app with a registered HCE service calls setPreferredService and then moves to the background or is closed, so the preferred-service update path runs with service == null (per the commit message).",
      "No later NFC transaction occurs, since a transaction would trigger mUnbindInactiveServicesRunnable."
     ],
     "per_major": {
      "14": "not affected per the bulletin; platform/packages/modules/Nfc is not present in the android-security-14.0.0_r29 security tags.",
      "15": "not affected per the bulletin; the project is not present in the android-security-15.0.0_r17 security tags.",
      "16": "same fix: commit a90c62ca6e75 in android-security-16.0.0_r7, follow-up 6bc72fe8b34e in android-security-16.0.0_r8; 8/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin listing in the per-major facts; at android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Apply a90c62ca6e75 first: it adds unbindServiceIfNeededLocked(boolean force) and moves unbindInactiveServicesLocked() to the class level. 6bc72fe8b34e is a one-line change that depends on that overload. Both rely on the multi-binding infrastructure (mComponentNameToConnectionsMap, mUnbindInactiveServicesRunnable, isMultipleBindingSupported()).",
      "The changed paths only run when isMultipleBindingSupported() is true, so the runtime state of the multi-binding feature decides whether the fix has any effect."
     ],
     "confidence": "medium",
     "unknowns": [
      "What mAidCache.getPreferredService() returns when the preferred service is unset, which affects which entry, if any, is kept.",
      "How isMultipleBindingSupported() is implemented, and the default state of Flags.allowMultipleHceBindings and DONT_IMMEDIATELY_UNBIND_SERVICES in the affected releases.",
      "The full body of the deferral branch in unbindServiceIfNeededLocked() (the diff cuts it off).",
      "Which of the 8 patched lines is missing at android-security-17.0.0_r1, and why the bulletin's version list includes 17-next while the per-major facts treat 17 as not listed.",
      "Which releases contain the change referenced as b/365533082.",
      "The concrete privilege impact of a retained system-side binding (the bulletin only states High EoP)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28581": {
     "cve": "CVE-2026-28581",
     "summary": "Telecom's CallIntentProcessor.fixInitiatingUserIfNecessary now decides whether a managed (work) profile has a dialer by asking RoleManager for a ROLE_DIALER holder in that user and checking with that user's PackageManager that the holder package exists and is enabled. Before, it only checked whether anything resolved ACTION_DIAL, via DefaultDialerManager.getInstalledDialerApplications.",
     "component": "platform/packages/services/Telecomm, CallIntentProcessor (initiating-user resolution for calls from managed profiles)",
     "weakness": "Unreliable check: intent resolution (an ACTION_DIAL handler) and later an unverified role holder were used as a proxy for 'a dialer is installed in the work profile'. This drives the decision to redirect a work-profile call to the parent user. The bulletin classifies it as ID.",
     "root_cause": "fixInitiatingUserIfNecessary redirects a call started from a managed profile to the parent user when the work profile has no dialer. The 'no dialer' test was DefaultDialerManager.getInstalledDialerApplications(context, userId).size() == 0, which only checks whether some activity in that user handles ACTION_DIAL. The first commit message says a PackageManager#queryIntentActivitiesAsUser regression reports the 'android' package (system server) as an ACTION_DIAL handler in the work profile even when nothing there handles it. Telecom therefore wrongly concluded that a work dialer existed and did not redirect to the parent user. The second commit message records a further gap: on Android 14, getQualifiedDefaultHolderPackageName from Role used the system user, so the dialer role could look active for the work profile even though the package was not enabled in the work profile context.",
     "fix_analysis": [
      "Commit 1 (510bcce16c24, android-security-16.0.0_r7), src/com/android/server/telecom/CallIntentProcessor.java: adds private static doesUserHaveDialerRoleHolder(Context, UserHandle). It calls context.createContextAsUser(user, 0), gets RoleManager from that user context and returns roleManager.getRoleHolders(RoleManager.ROLE_DIALER).size() > 0.",
      "Commit 1, CallIntentProcessor.fixInitiatingUserIfNecessary: replaces 'noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context, initiatingUser.getIdentifier()).size() == 0' with 'noDialerInstalled = !doesUserHaveDialerRoleHolder(context, initiatingUser)'. The rest of the redirect-to-parent logic (UserManager lookup, parentUserHandle) is unchanged.",
      "Commit 2 (3d2e80ac1953, android-security-16.0.0_r8), CallIntentProcessor.doesUserHaveDialerRoleHolder: now fetches List<String> roleHolders and returns false if it is null or empty. Otherwise it takes roleHolders.get(0), calls userContext.getPackageManager().getApplicationInfo(dialerPackage, 0) in the work-profile user context and returns info.enabled. It returns false on PackageManager.NameNotFoundException. The commit adds the imports android.content.pm.ApplicationInfo and java.util.List.",
      "The DefaultDialerManager import is still in the file but is no longer used for this decision."
     ],
     "completeness": [
      "The fix covers the single decision point in fixInitiatingUserIfNecessary. On 16 and 17, where commit 2 is shown, both the ACTION_DIAL-resolution weakness and the unverified role holder are handled.",
      "Only roleHolders.get(0) is cross-checked with PackageManager. ROLE_DIALER is an exclusive single-holder role, so in practice there is at most one holder (general platform knowledge).",
      "The check applies only when UserUtil.isManagedProfile(...) is true for the initiating user.",
      "The underlying PackageManager#queryIntentActivitiesAsUser regression described in the commit message is not fixed here; Telecom simply stops relying on that query. Other callers of DefaultDialerManager.getInstalledDialerApplications are not changed by this fix."
     ],
     "behavior_changes": [
      "A work profile that has an app handling ACTION_DIAL but no ROLE_DIALER holder is now treated as having no dialer. Calls from it are redirected to the parent (personal) user and its dialer.",
      "A work profile whose reported ROLE_DIALER holder is not installed or is disabled in that profile is now treated as having no dialer, and the call is redirected to the parent user.",
      "The outgoing-call path now uses RoleManager and PackageManager through createContextAsUser for the initiating user. PackageManager.NameNotFoundException is caught and treated as 'no dialer'."
     ],
     "preconditions": [
      "The device has a managed (work) profile, and a call intent reaches CallIntentProcessor with KEY_INITIATING_USER set to that profile. Per the class javadoc, UserCallIntentProcessor forwards per-user call intents to CallIntentProcessor.",
      "The work profile has no real dialer: either nothing holds ROLE_DIALER there, or the reported holder is not installed or enabled in that profile."
     ],
     "per_major": {
      "14": "Adapted fix: commit 13272bca51ee in android-security-14.0.0_r28. It adds the same RoleManager-based doesUserHaveDialerRoleHolder, but fixInitiatingUserIfNecessary(Context, Intent) has no FeatureFlags parameter and uses UserUtil.isManagedProfile(context, initiatingUser) and UserManager.get(context). Only commit 1 is shown for 14. The resolver reports 8/8 lines of the patched region present at android-security-14.0.0_r29, but this does not show whether the commit 2 update is present.",
      "15": "Same fix as 16 for commit 1: 34079b659664 in android-security-15.0.0_r16, identical hunks including the FeatureFlags signature. The commit 2 diff is not shown for 15. The resolver reports 8/8 lines of the patched region at android-security-15.0.0_r17, which does not show whether commit 2 is present.",
      "16": "Same fix in two commits. 510bcce16c24 (android-security-16.0.0_r7) introduces the RoleManager check; 3d2e80ac1953 (android-security-16.0.0_r8) adds the PackageManager installed/enabled cross-check.",
      "17": "Same fix. Only commit 2 (c1104b6c1bbe in android-security-17.0.0_r1) is shown. It applies to a file that already contains the commit 1 version of doesUserHaveDialerRoleHolder, at different line offsets."
     },
     "backport_notes": [
      "Apply both commits in order: commit 1 introduces doesUserHaveDialerRoleHolder and rewires fixInitiatingUserIfNecessary, and commit 2 modifies that helper. Both changes are confined to CallIntentProcessor.java.",
      "On branches where Telecom has no FeatureFlags, use the Android 14 variant (UserUtil.isManagedProfile(context, user), UserManager.get(context)). The helper body from commit 1 is identical on 14, 15 and 16."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show how the wrong initiating-user decision leads to information disclosure (the bulletin's ID type), or which profile handles the call in the buggy case.",
      "Whether the commit 2 update (PackageManager cross-check) is present on Android 14 and 15 is not shown, even though the commit 2 message describes the issue on Android 14.",
      "The material does not show the specific PackageManager#queryIntentActivitiesAsUser regression or whether it was fixed separately."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28582": {
     "cve": "CVE-2026-28582",
     "summary": "ConfirmDeviceCredentialActivity now finishes immediately when it receives KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL through any component other than the com.android.settings.ConfirmRemoteDeviceCredentialActivity alias, so the alias (and the checks attached to it) can no longer be skipped by targeting the underlying activity class directly.",
     "component": "platform/packages/apps/Settings, password/ConfirmDeviceCredentialActivity (remote device credential confirmation)",
     "weakness": "Missing access control: permission check attached to an activity-alias could be bypassed by addressing the real activity component directly (CWE-862 style)",
     "root_cause": "ConfirmDeviceCredentialActivity handled the ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL action based purely on intent.getAction(). The remote validation flow is intended to be reached only via the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity, which per the fix comment carries permission checks (\"Remote validation must use the alias to avoid bypassing permission checks\"). Because the activity never verified which component the intent was delivered to, an intent with the remote action addressed to ConfirmDeviceCredentialActivity (or a subclass such as InternalActivity) entered the remote validation path without passing through the alias's protection. The manifest declaration of the alias is not included in the material, so the exact permission being bypassed is not shown.",
     "fix_analysis": [
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: adds the constant CONFIRM_REMOTE_DEVICE_CREDENTIAL_ACTIVITY_ALIAS = \"com.android.settings.ConfirmRemoteDeviceCredentialActivity\".",
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java (onCreate, right after `remoteValidation` is computed from intent.getAction()): if remoteValidation is true, reads intent.getComponent(); if it is null or its getClassName() is not equal to the alias constant, logs \"Caller bypassing alias for action: \" + KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL via Log.w, calls finish() and returns before the mTaskOverlay and prepareRepairMode assignments that follow.",
      "tests/robotests/src/com/android/settings/password/ConfirmDeviceCredentialActivityTest.java (new): two Robolectric tests, onCreate_remoteValidationWithAlias_doesNotFinish (component set to the alias, activity must not be DESTROYED) and onCreate_remoteValidationWithoutAlias_finishes (component set to ConfirmDeviceCredentialActivity.class.getName(), activity must be DESTROYED).",
      "tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java: adds a getCredentialTypeForUser(int) shadow returning CREDENTIAL_TYPE_PIN when the user is marked secure, CREDENTIAL_TYPE_NONE otherwise, supporting the new tests. Test-only, no runtime effect."
     ],
     "completeness": [
      "The gate is applied only to the ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL path; other actions (e.g. ACTION_CONFIRM_FRP_CREDENTIAL, the regular confirm flow) are untouched and keep their existing behavior.",
      "The check compares only componentName.getClassName() against the alias string; the package name is not compared. Since the activity runs inside the Settings package this is implicitly bound to Settings, but the check does not verify it.",
      "Because InternalActivity extends ConfirmDeviceCredentialActivity and shares onCreate, delivering the remote action to InternalActivity is also rejected (its class name differs from the alias).",
      "The fix is enforced inside the activity code, not by changing the manifest; the material does not show the manifest, so whether the base component is still exported or whether other entry points to the remote validation logic exist cannot be judged from the provided code."
     ],
     "behavior_changes": [
      "Intents with action ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL whose component is null or anything other than com.android.settings.ConfirmRemoteDeviceCredentialActivity now cause the activity to finish() immediately, with no result set in the new block.",
      "A new Log.w entry \"Caller bypassing alias for action: \" followed by the KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL value is emitted in that case.",
      "Legitimate callers that already launch via the alias are unaffected (covered by onCreate_remoteValidationWithAlias_doesNotFinish)."
     ],
     "preconditions": [
      "A caller must be able to start ConfirmDeviceCredentialActivity (or InternalActivity) with an explicit component and action KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL, bypassing the ConfirmRemoteDeviceCredentialActivity alias.",
      "Whether the base component is reachable by third-party apps depends on its manifest export/permission state, which is not included in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit 4b69be85e55d in android-security-14.0.0_r29 ([RESTRICT AUTOMERGE]) adds the same constant and the same alias gate after the (non-final) `remoteValidation` boolean at a different offset (lines 68/169); no Robolectric test or ShadowLockPatternUtils change was included. Patched region 7/7 lines present.",
      "15": "same fix: commit 839f92783965 in android-security-15.0.0_r17 ([RESTRICT AUTOMERGE]), identical source change, new test and ShadowLockPatternUtils shadow, hunk at line 184. 8/8 lines present.",
      "16": "same fix: commit 3ee7e15a126f in android-security-16.0.0_r8 ([RESTRICT AUTOMERGE]), hunk at line 219, with test and shadow. 8/8 lines present.",
      "17": "same fix: commit 642944e4c4e6 in android-security-17.0.0_r1 (no RESTRICT AUTOMERGE prefix), hunk at line 212, ShadowLockPatternUtils hunk at line 106; identical logic. 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained source change: insert the alias constant and the `if (remoteValidation) { ... finish(); return; }` block immediately after `remoteValidation` is computed in onCreate; the Robolectric test and ShadowLockPatternUtils.getCredentialTypeForUser shadow are optional and were omitted on Android 14. Ensure android.content.ComponentName is imported (the diff relies on it already being imported in these branches)."
     ],
     "confidence": "high",
     "unknowns": [
      "The AndroidManifest.xml entries for ConfirmDeviceCredentialActivity and the ConfirmRemoteDeviceCredentialActivity alias (export state and the permission the alias enforces) are not in the material, so the exact permission that was bypassable is not shown.",
      "What the remote validation path exposes (the information disclosed per the bulletin's ID classification) is not shown by the diff or context.",
      "Whether the second upstream Cherrypick-From commit (1007fdf8c8de) contained additional changes beyond what is squashed here is not visible.",
      "The parts of onCreate before the check and after the mTaskOverlay/prepareRepairMode assignments are not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28583": {
     "cve": "CVE-2026-28583",
     "summary": "validate_camera_metadata_structure in system/media camera_metadata.c now converts entry_capacity (a count of entries) to bytes by multiplying with sizeof(camera_metadata_buffer_entry_t), with an overflow guard, before checking that the entry region ends at or before data_start.",
     "component": "platform/system/media, camera metadata library (libcamera_metadata, camera/src/camera_metadata.c)",
     "weakness": "Incorrect bounds check (unit mismatch: entry count treated as byte size) leading to out-of-bounds write",
     "root_cause": "In camera/src/camera_metadata.c, validate_camera_metadata_structure computed entries_end as header->entries_start + header->entry_capacity and required entries_end <= header->data_start. entry_capacity is the number of camera_metadata_buffer_entry_t slots, not a byte length, so the check under-counted the entry region by a factor of sizeof(camera_metadata_buffer_entry_t). A crafted header whose entry region (in bytes) extends past data_start could still pass validation; per the commit message, this led to an out-of-bounds write and a cameraserver crash.",
     "fix_analysis": [
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: adds a pre-multiplication guard rejecting header->entry_capacity > (metadata_size_t)~0 / sizeof(camera_metadata_buffer_entry_t) with CAMERA_METADATA_VALIDATION_ERROR, so the byte conversion cannot wrap in the 32-bit metadata_size_t.",
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: computes entries_capacity_bytes = sizeof(camera_metadata_buffer_entry_t) * header->entry_capacity and uses it (instead of the raw entry_capacity) in entries_end = header->entries_start + entries_capacity_bytes.",
      "camera/src/camera_metadata.c:validate_camera_metadata_structure: splits the former combined condition into two checks with distinct log messages: entries_end < entries_start (addition overflow) and entries_end > data_start (entry region overlapping data region); both return CAMERA_METADATA_VALIDATION_ERROR.",
      "camera/src/camera_metadata.c:struct camera_metadata: documentation-only comments clarifying entry_capacity is a count of entries and data_capacity is a count of bytes; no layout change."
     ],
     "completeness": [
      "Covers the entry-region bound in validate_camera_metadata_structure: the entry capacity is now checked in bytes against data_start, with multiplication and addition overflow guards.",
      "Only the entries_start/entry_capacity check is modified; the data_count/data_capacity/data_start checks and any entry_count vs entry_capacity check are outside the diff and not shown, so their state cannot be judged from the material.",
      "The fix is purely in the validator; code paths that write into the entry region are unchanged and rely on callers having run validate_camera_metadata_structure (callers such as cloneFromAidl and convertFromHidl in libcameraservice do, per index search; CameraMetadata.cpp only validates under IF_ALOGV)."
     ],
     "behavior_changes": [
      "Stricter validation: a camera_metadata_t whose entry_capacity * sizeof(camera_metadata_buffer_entry_t) added to entries_start exceeds data_start is now rejected with CAMERA_METADATA_VALIDATION_ERROR where it previously passed.",
      "New rejection for entry_capacity values large enough that the byte conversion would overflow metadata_size_t.",
      "New ALOGE messages: 'Entry capacity (...) is too large' and 'Entry start (...) + capacity bytes (...) overflows'; the existing message now prints entries_end in bytes.",
      "Metadata buffers with an inconsistent entry_capacity that previously passed will now be treated as malformed by callers (e.g. 'Malformed camera metadata received from HAL' in AidlUtils.cpp/Utils.cpp per index search)."
     ],
     "preconditions": [
      "A camera_metadata_t buffer with attacker-controlled header fields must reach validate_camera_metadata_structure and then be used; per index search it is called on HAL-provided metadata in frameworks/av/services/camera/libcameraservice/aidl/AidlUtils.cpp:cloneFromAidl and hidl/Utils.cpp:convertFromHidl, and under IF_ALOGV in frameworks/av/camera/CameraMetadata.cpp.",
      "The header must have entry_capacity (as a count) such that entries_start + entry_capacity <= data_start but entries_start + entry_capacity * sizeof(camera_metadata_buffer_entry_t) > data_start.",
      "The exact untrusted input path from the bug report (bug 486597445) is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/system/media a9dc2a964e84 in android-security-14.0.0_r29, same hunks in camera/src/camera_metadata.c as the 16 commit; 8/8 patched lines present.",
      "15": "same fix: platform/system/media 9b4fe9dce95b in android-security-15.0.0_r17, same hunks as the 16 commit; 8/8 patched lines present.",
      "16": "same fix: platform/system/media 312249da098c in android-security-16.0.0_r8 (lists two internal Cherrypick-From commits, Change-Id I798ae12e04a8b95760fb083b42099dbe5934d198); 8/8 patched lines present.",
      "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 touching only validate_camera_metadata_structure and two struct comments; the same hunks were applied on 14, 15 and 16 (pre-image blob e0c1cadb on 14 and 15, 1f09d5b1 on 16).",
      "Relies on metadata_size_t being 32-bit (the file has a _Static_assert for this) and on PRIu32 for the new ALOGE formats."
     ],
     "confidence": "high",
     "unknowns": [
      "Which concrete write path performed the out-of-bounds write after validation passed; the commit message only says cameraserver crashed.",
      "Whether validate_camera_metadata_structure separately enforces entry_count <= entry_capacity and the data-region bounds; those checks are outside the diff.",
      "Why the fix lists two internal cherry-picks while the AOSP commit is a single change; the second internal commit's content is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28593": {
     "cve": "CVE-2026-28593",
     "summary": "The LiveTV \"Send Feedback\" action in SettingsFragment now launches an explicit intent targeting com.google.android.feedback/.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent.",
     "component": "platform/packages/apps/TV (LiveTV), settings side panel (src/com/android/tv/ui/sidepanel/SettingsFragment.java)",
     "weakness": "Implicit intent used for a sensitive launch (intent hijacking): resolution could route the Send Feedback launch to any app registering for ACTION_APP_ERROR",
     "root_cause": "In SettingsFragment, onSelected() of the \"Send Feedback\" ActionItem built an implicit Intent with action Intent.ACTION_APP_ERROR and constructed an ApplicationErrorReport (packageName/processName set to the TV app's package, and time). The rest of the method, including how the report is attached and how the intent is started, is not shown. No target component or package was set, so intent resolution could deliver the intent to any installed app registered for ACTION_APP_ERROR rather than the intended feedback app. The commit message says the change is \"to avoid opening malicious apps\".",
     "fix_analysis": [
      "src/com/android/tv/ui/sidepanel/SettingsFragment.java: adds an import of android.content.ComponentName and two constants, FEEDBACK_PACKAGE = \"com.google.android.feedback\" and FEEDBACK_CLASS = \"com.google.android.feedback.FeedbackActivity\".",
      "src/com/android/tv/ui/sidepanel/SettingsFragment.java: in onSelected() of the settings_send_feedback ActionItem, `new Intent(Intent.ACTION_APP_ERROR)` becomes `new Intent().setComponent(new ComponentName(FEEDBACK_PACKAGE, FEEDBACK_CLASS))`. The intent is now explicit, so only the named FeedbackActivity can receive it, and the action string is dropped. The ApplicationErrorReport construction and the rest of the method are not changed by the diff."
     ],
     "completeness": [
      "Covers the single implicit ACTION_APP_ERROR launch in the Send Feedback action of SettingsFragment by pinning it to one explicit component.",
      "The fix is a single hunk in one file. The material shows neither the rest of onSelected() nor any other intent launches in the TV app, so it cannot tell whether other implicit intents exist elsewhere.",
      "The target is hardcoded to the Google feedback package. The shown hunk adds no fallback or availability check."
     ],
     "behavior_changes": [
      "Send Feedback now only launches com.google.android.feedback.FeedbackActivity. Third-party or OEM handlers of ACTION_APP_ERROR no longer receive this intent.",
      "On devices without com.google.android.feedback installed, the launch will not resolve, where before any ACTION_APP_ERROR handler could be used. Whether the failure is caught is not shown in the diff."
     ],
     "preconditions": [
      "A user (or UI driver) opens the LiveTV settings side panel and selects the \"Send Feedback\" item (R.string.settings_send_feedback), which calls onSelected() of that ActionItem in SettingsFragment.",
      "Before the fix, an installed app declares an intent filter for Intent.ACTION_APP_ERROR and is chosen or offered by intent resolution.",
      "Per index search, no other callers of this onSelected were found in the TV app or outside it. It is a UI-triggered path."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/TV 2e9c27acedc2 in android-security-14.0.0_r29, identical diff (blob indexes 762a190c..1ac5d81e), 8/8 patched lines present",
      "15": "same fix: platform/packages/apps/TV 7e516e62a48d in android-security-15.0.0_r17, identical diff, 8/8 patched lines present",
      "16": "same fix: platform/packages/apps/TV d688b27298d4 in android-security-16.0.0_r8, identical diff, 8/8 patched lines present",
      "17": "same fix: platform/packages/apps/TV 724154d5db03 in android-security-17.0.0_r1, identical diff, 8/8 patched lines present"
     },
     "backport_notes": [
      "SettingsFragment.java has the same pre-image blob (762a190c) on all four branches, so the patch applies verbatim. It is a single self-contained hunk whose only new dependency is the android.content.ComponentName import.",
      "The target component is hardcoded to com.google.android.feedback. Forks shipping LiveTV without that package may find Send Feedback no longer resolves and may want to adapt the component or guard the launch."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of onSelected() is not shown: how the ApplicationErrorReport is attached to the intent, any flags, and whether startActivity failures are caught.",
      "The material does not say what made this an EoP rather than an information disclosure, beyond the bulletin rating and the commit message \"avoid opening malicious apps\".",
      "Whether the TV app has other implicit intent launches is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28596": {
     "cve": "CVE-2026-28596",
     "summary": "Adds OutOfMemoryError to the catch clause around the game-mode-config XML meta-data parsing in GameManagerService, so that an oversized XML resource resets the flags to defaults and logs an error instead of crashing the process hosting the service.",
     "component": "platform/frameworks/base, services/core GameManagerService (game mode config meta-data parsing)",
     "weakness": "Uncaught error (OutOfMemoryError) while parsing app-supplied XML meta-data, leading to DoS",
     "root_cause": "In services/core/java/com/android/server/app/GameManagerService.java, the block that loads an application's METADATA_GAME_MODE_CONFIG XML resource (via packageManager.getResourcesForApplication, Xml.asAttributeSet, parser.next and resources.obtainAttributes) only caught NameNotFoundException, XmlPullParserException and IOException. Per the commit message, an XML resource that is too large (over 1 GB) makes this code fail with OutOfMemoryError. That is an Error, not one of the caught exceptions, so it was not handled and the process crashed.",
     "fix_analysis": [
      "services/core/java/com/android/server/app/GameManagerService.java, in the meta-data parsing block that returns xmlFound: the multi-catch is widened from `NameNotFoundException | XmlPullParserException | IOException` to also include `OutOfMemoryError`.",
      "The existing handler body is reused unchanged: mPerfModeOverridden=false, mBatteryModeOverridden=false, mAllowDownscale=true, mAllowAngle=true, mAllowFpsOverride=true, plus a Slog.e line logging 'Error while parsing XML meta-data for ' + METADATA_GAME_MODE_CONFIG. The method then returns xmlFound as before.",
      "Single commit, one file, 2 insertions and 1 deletion. Nothing changes in how the XML is read, and no size limit is added. The fix only lets the code survive the error."
     ],
     "completeness": [
      "Covers only OutOfMemoryError raised inside this one try block. The allocation attempt still happens: the fix does not bound the size of the meta-data resource or avoid the memory pressure.",
      "Other Error subclasses (e.g. StackOverflowError) thrown in this block are still not caught. The material does not show whether any of them are reachable.",
      "Only this parsing site in GameManagerService is changed. The material does not show other XML or resource parsing sites in the file, so their status is unknown.",
      "If OutOfMemoryError is thrown after xmlFound was set to true, the method still returns true with the flags reset to defaults. This matches the existing behaviour for the other caught exceptions."
     ],
     "behavior_changes": [
      "An OutOfMemoryError during game-mode-config meta-data parsing now resets the game mode flags to defaults (perf/battery mode not overridden; downscale, ANGLE and FPS override allowed) and writes an Slog.e log line. Before the fix, the uncaught Error crashed the process.",
      "No API or data-format changes."
     ],
     "preconditions": [
      "An installed package declares METADATA_GAME_MODE_CONFIG meta-data that points at an XML resource large enough to exhaust the heap when loaded (commit message: >1 GB).",
      "GameManagerService must process that package's configuration. The material does not show which entry points trigger the parsing."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 12567c8df8f3 in android-security-14.0.0_r29, identical catch-clause change at line 613.",
      "15": "same fix: platform/frameworks/base 47898903e7c3 in android-security-15.0.0_r17, identical change at line 622.",
      "16": "same fix: platform/frameworks/base 581fd9950f41 in android-security-16.0.0_r8, change at line 636.",
      "17": "not affected per the bulletin (Android 17 is not listed). No fix commit is linked for 17. android-security-17.0.0_r1 shows a 4/5 patched-region line match."
     },
     "backport_notes": [
      "Trivial, self-contained backport: add `| OutOfMemoryError` to the existing multi-catch in GameManagerService's meta-data parsing block. The catch line and the visible handler context are the same on 14, 15 and 16; only line offsets differ."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the name of the method or class enclosing the patched block. It only shows that the block is in GameManagerService and returns xmlFound.",
      "The material does not show which service entry points trigger the parsing, or whether an unprivileged app can trigger it on demand.",
      "Whether other XML or resource parsing sites in GameManagerService have the same uncaught OutOfMemoryError exposure.",
      "Whether the Android 17 code already contains the catch (only a 4/5 region line match is given, with no linked commit)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28599": {
     "cve": "CVE-2026-28599",
     "summary": "In ActivityManagerService.addCreatorToken, the targetPackage used for the isCreatorSameAsTarget shortcut no longer falls back to intent.getPackage() when the outer intent has no explicit component. In that case creator tokens are now attached to nested intents instead of being skipped because of a package constraint the caller controls and that a selector could override.",
     "component": "platform/frameworks/base, ActivityManagerService (intent redirect hardening / IntentCreatorToken)",
     "weakness": "Incorrect authorization decision based on an attribute the caller controls (Intent.getPackage() trusted as the routing target while the selector determines actual routing)",
     "root_cause": "addCreatorToken decides whether to skip attaching IntentCreatorTokens to nested intents. It computes targetPackage and checks creatorPackage.equals(targetPackage) (isCreatorSameAsTarget). Before the fix, when the outer intent had no explicit component, targetPackage came from intent.getPackage(). That value does not determine routing when the intent carries a selector: per the commit message, ComputerEngine routes on the selector's explicit component. A caller could therefore set the outer intent's package to its own package, which made isCreatorSameAsTarget true so the forEachNestedCreatorToken callback returned early without calling createIntentCreatorToken. At the same time a selector could target another app's component, so the nested intents were delivered without creator tokens.",
     "fix_analysis": [
      "services/core/java/com/android/server/am/ActivityManagerService.java:addCreatorToken: the ternary that computes targetPackage now returns null instead of intent.getPackage() when intent.getComponent() is null. With targetPackage null, creatorPackage.equals(targetPackage) is false, so isCreatorSameAsTarget is false. The forEachNestedCreatorToken lambda then calls createIntentCreatorToken(extraIntent, creatorUid, creatorPackage) and calls extraIntent.setCreatorToken(...) whenever a non-null token is returned. createIntentCreatorToken returns null if the nested intent already has a valid token.",
      "The explicit-component branch is unchanged: when intent.getComponent() is non-null, targetPackage is still intent.getComponent().getPackageName() and the same-package shortcut still applies.",
      "The shipped diff (both Android 16 and 17) is a one-line change that removes the getPackage() fallback entirely. It does not implement the selector-aware priority logic described in the commit message (outer component, then selector component or package, then outer package). The shipped behavior is more conservative than that description."
     ],
     "completeness": [
      "Covers the described bypass: when the outer intent has no explicit component, the same-package shortcut can no longer trigger, whatever intent.getPackage() or the selector contain.",
      "The shortcut still exists for outer intents with an explicit component and is based only on that component's package. The material does not show whether a selector can influence routing in that case. In the platform resolver the outer explicit component takes precedence over the selector (general platform knowledge), which would make this branch match actual routing.",
      "addCreatorToken is still gated by preventIntentRedirect() and the intent != null check. The fix does not change that gating. The nested-key flags (wtf log, toast, optional SecurityException, server-side key collection) are also unchanged.",
      "addCreatorToken still does not inspect the selector. The fix avoids the selector question instead of modelling it."
     ],
     "behavior_changes": [
      "When the creator token path is active, nested intents inside an outer intent with no explicit component now get IntentCreatorTokens even when intent.getPackage() equals the creator's package. Previously the shortcut skipped them.",
      "FrameworkStatsLog INTENT_CREATOR_TOKEN_ADDED is logged with false (token created) in more cases, and createIntentCreatorToken is called more often.",
      "This diff adds no new error or exception paths."
     ],
     "preconditions": [
      "preventIntentRedirect() must return true and the intent must be non-null, per the early returns in addCreatorToken.",
      "The outer intent must carry nested intents in its extras (forEachNestedCreatorToken) for the token logic to matter.",
      "For the fixed bypass: an outer intent with no explicit component, intent.getPackage() set to the creator's own package, and a selector that routes elsewhere (from the commit message).",
      "The material does not show the callers of addCreatorToken."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29 only 2/5 lines of the patched region are present. No fix was applied.",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17 only 2/5 lines of the patched region are present. No fix was applied.",
      "16": "Same fix. Commit 5108d2606324 in android-security-16.0.0_r8 is a one-line change in ActivityManagerService.addCreatorToken (around line 19462). 5/5 patched lines are present.",
      "17": "Same fix. Commit ee7ea482d39e in android-security-17.0.0_r1 makes the identical one-line change at a different offset (hunk at line 21098). 5/5 patched lines are present."
     },
     "backport_notes": [
      "This is a single-commit, single-line change with no dependencies shown. It applies where addCreatorToken contains the getPackage() fallback in the targetPackage computation.",
      "If a fork implemented the selector-aware logic from the commit message instead of the shipped null fallback, check that it still denies the shortcut when a selector is present. The AOSP diff simply drops the getPackage() fallback."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the callers of addCreatorToken or the exact entry points.",
      "Whether the two internal Cherrypick-From commits contained more changes (for example tests or the described selector-aware logic) than the one-line diff shipped to AOSP.",
      "The state of the preventIntentRedirect() flag on shipping builds is not shown.",
      "Whether addCreatorToken and the creator-token hardening exist on Android 14/15. Only 2/5 lines of the patched region are present there, and the bulletin does not list those versions."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28600": {
     "cve": "CVE-2026-28600",
     "summary": "PaymentDefaultDialog.onCreate no longer reads a UserHandle from the incoming intent's Intent.EXTRA_USER extra; it always uses UserHandle.myUserId() as the userId passed to buildDialog.",
     "component": "platform/packages/apps/Settings, NFC payment settings (src/com/android/settings/nfc/PaymentDefaultDialog.java)",
     "weakness": "Caller-controlled user identifier accepted from an intent extra without validation (cross-user authorization bypass)",
     "root_cause": "PaymentDefaultDialog.onCreate extracted a UserHandle from getIntent().getParcelableExtra(Intent.EXTRA_USER) and, when present, used userHandle.getIdentifier() as the userId passed to buildDialog(component, category, userId). onClick on BUTTON_POSITIVE calls mBackend.setDefaultPaymentApp(mNewDefault.componentName, mNewDefault.userId). Nothing in the shown code checked that the supplied user matched the calling user, so the intent sender could choose the user id the dialog was built for.",
     "fix_analysis": [
      "src/com/android/settings/nfc/PaymentDefaultDialog.java:onCreate: the block reading Intent.EXTRA_USER into a UserHandle and the null check selecting between UserHandle.myUserId() and userHandle.getIdentifier() are deleted and replaced by `int userId = UserHandle.myUserId();`.",
      "userId is still passed to buildDialog(component, category, userId), but it is now always the activity's own user rather than a value taken from the intent.",
      "EXTRA_SERVICE_COMPONENT and EXTRA_CATEGORY are still read from the intent unchanged; the null check for them remains at the top of buildDialog."
     ],
     "completeness": [
      "The fix removes the only caller-controlled user id input visible in the shown code (Intent.EXTRA_USER) and binds the userId passed to buildDialog to the current user.",
      "Component and category remain intent-controlled; the material does not show how buildDialog validates them beyond the null check, so coverage of those inputs cannot be judged from the material.",
      "The commit title says the dialog building logic was removed from onCreate, but the diff still calls buildDialog from onCreate; only the user id derivation changed. The fix is a single-file, single-hunk change identical on all patched branches."
     ],
     "behavior_changes": [
      "Intent.EXTRA_USER is ignored by PaymentDefaultDialog; the dialog is always built for UserHandle.myUserId().",
      "No new error path or result code: the dialog still sets RESULT_CANCELED by default and RESULT_OK on positive click, and still finishes if buildDialog fails."
     ],
     "preconditions": [
      "Ability to start the PaymentDefaultDialog activity in Settings with an intent carrying CardEmulation.EXTRA_SERVICE_COMPONENT, CardEmulation.EXTRA_CATEGORY and Intent.EXTRA_USER (export/permission status of the activity is not shown in the material).",
      "A user interaction on the dialog's positive button is required before setDefaultPaymentApp is invoked (onClick, BUTTON_POSITIVE).",
      "PaymentBackend must be constructible (a NullPointerException during construction leads to finish())."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin). Per-major facts show 8/8 lines of the patched region present at android-security-14.0.0_r29; no fix commit is recorded for 14.",
      "15": "Same fix: platform/packages/apps/Settings 1d0237d77a38 in android-security-15.0.0_r17, identical diff to the 16 commit.",
      "16": "Same fix: platform/packages/apps/Settings bf85ca6c87fc in android-security-16.0.0_r8 (cherry-pick of c2f680bf6093, Change-Id I9224a3aec3ecb2c8bbf3fe3ee966afaf782cb9c4).",
      "17": "Same fix: platform/packages/apps/Settings d1db7e8d9994 in android-security-17.0.0_r1, identical diff."
     },
     "backport_notes": [
      "Single self-contained hunk in PaymentDefaultDialog.onCreate with no visible dependencies; the same diff (index d333b3da6ac..d556b6df929) was used on 15, 16 and 17, so a cherry-pick should fit branches that still contain the Intent.EXTRA_USER block."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether PaymentDefaultDialog is exported or protected by a permission in the Settings manifest (not in the material).",
      "How buildDialog uses userId (e.g. whether it becomes mNewDefault.userId) and what PaymentBackend.setDefaultPaymentApp does with it, including any additional validation.",
      "Why the bulletin does not list Android 14 although the per-major facts show the patched region's pre-fix lines at android-security-14.0.0_r29.",
      "Which legitimate caller (if any) sent Intent.EXTRA_USER to this dialog before the fix."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28602": {
     "cve": "CVE-2026-28602",
     "summary": "ClipboardService now reads CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS for the explicit userId in the access-notification check (getIntForUser instead of getInt on the system server context's resolver) and writes it with an explicit userId in setClipboardAccessNotificationsEnabledForUser (putIntForUser instead of putInt).",
     "component": "platform/frameworks/base, services/core ClipboardService (clipboard access notifications)",
     "weakness": "Per-user secure setting read under the wrong user context (bulletin type ID)",
     "root_cause": "In services/core/java/com/android/server/clipboard/ClipboardService.java, the method that decides whether to show the clipboard access notification (around line 1438 in android-security-16.0.0_r8) read Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS with Settings.Secure.getInt(getContext().getContentResolver(), ...). That reads it under the system server context's user, not under the userId passed to the method. On a multi-user device the notification decision could therefore follow another user's setting instead of the accessed user's own setting. The setter setClipboardAccessNotificationsEnabledForUser used Settings.Secure.putInt on a resolver created via createContextAsUser(UserHandle.of(userId), 0) and did not pass an explicit userId. The getter areClipboardAccessNotificationsEnabledForUser already used getIntForUser with userId.",
     "fix_analysis": [
      "ClipboardService.java, access-notification check method (its name is not shown in the material; the hunk is the block after 'if (clipboard.primaryClip == null) return;'): replace Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, default) with Settings.Secure.getIntForUser(..., default, userId). The early return that suppresses the notification now depends on the setting of the user passed to the method.",
      "ClipboardService.java:setClipboardAccessNotificationsEnabledForUser: replace Settings.Secure.putInt(resolver, CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, enable ? 1 : 0) with Settings.Secure.putIntForUser(resolver, ..., userId), so the user being written is stated explicitly.",
      "Single-commit fix with no new permission checks, flags or data structures. The default value (mShowAccessNotifications ? 1 : 0) is unchanged."
     ],
     "completeness": [
      "Three accesses to CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS are visible in the material, and all now use an explicit userId: the getter (areClipboardAccessNotificationsEnabledForUser, lines 535-537) already did, and the fix converts the setter and the notification check. The material does not show the rest of the file.",
      "The fix relies on the userId passed to the notification check method being the correct user. The material does not show how callers derive that userId.",
      "The notification check still uses the system context's ContentResolver (getContext().getContentResolver()), now with an explicit userId. This is the standard pattern for per-user settings reads from system_server (general platform knowledge)."
     ],
     "behavior_changes": [
      "Clipboard access notifications are now shown or suppressed according to the setting of the user passed to the check, not the system server context's user. On multi-user devices where these settings differ, notification behavior changes.",
      "setClipboardAccessNotificationsEnabledForUser now writes with an explicit userId.",
      "No new exceptions, error codes or API surface changes."
     ],
     "preconditions": [
      "A multi-user or profile setup where the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS setting of the target user differs from that of the system server context's user.",
      "The target user's clipboard has a non-null primary clip, and the app accessing it is not the clip owner. The check returns early if primaryClip == null, and a later check skips the notification when the accessor is the owner.",
      "For the setter path: the caller holds Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION (enforced via checkCallingOrSelfPermission)."
     ],
     "per_major": {
      "14": "same fix: DO NOT MERGE cherry-pick 6f90eac7f9e5 in android-security-14.0.0_r29. Identical two-hunk change (setter putIntForUser; notification check getIntForUser) at different line offsets (516, 1364).",
      "15": "same fix: DO NOT MERGE cherry-pick 993d9100be4c in android-security-15.0.0_r17. Identical two-hunk change at offsets 560 and 1441.",
      "16": "same fix: DO NOT MERGE cherry-pick 80b1b1fe63b5 in android-security-16.0.0_r8 (Change-Id Iff63d51c5b269649589c45a8aab0bb9f75b7254a), hunks at 553 and 1438.",
      "17": "adapted fix: commit 4b3bf7fb29ec in android-security-17.0.0_r1 (no DO NOT MERGE prefix). The setter hunk is the same. In the notification check, the method returns false instead of return and is preceded by shouldSuppressAccessNotificationForUidLocked(uid), so the hunk context differs, but the getIntForUser change is the same."
     },
     "backport_notes": [
      "The change is two mechanical substitutions (putInt to putIntForUser with userId; getInt to getIntForUser with userId). It applies wherever the notification check method has userId in scope. On 17-style code the method returns boolean, so keep the local return style.",
      "Settings.Secure.getIntForUser/putIntForUser are hidden system APIs that system_server has had for many releases, so no dependency commits are needed (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The name and full signature of the notification check method that contains the second hunk, and how callers derive its userId argument.",
      "Whether the old setter (putInt on a createContextAsUser resolver) actually wrote to a different user than intended, or whether the setter change is only defensive.",
      "Whether the issue was reported as wrong notification suppression for a secondary user or as the setter writing the wrong user's setting; the material shows only the code change and the bulletin type (ID, High).",
      "The content of upstream commit 5572625867bc3f4bc375129df666bc6d2000053f beyond what is cherry-picked here."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28603": {
     "cve": "CVE-2026-28603",
     "summary": "In Settings' AppRestrictionsFragment, the vetted copy of an app-supplied custom restrictions Intent now has all four FLAG_GRANT_*_URI_PERMISSION flags removed and its data and clipData cleared before Settings starts the activity with it.",
     "component": "platform/packages/apps/Settings, users / app restrictions (AppRestrictionsFragment)",
     "weakness": "Missing sanitization of an app-supplied Intent forwarded by a privileged app (URI permission grant flags and URI payload not stripped), allowing URI grants to be issued with Settings' identity",
     "root_cause": "AppRestrictionsFragment.RestrictionsResultReceiver.assertSafeToStartCustomActivity(Intent) copies an Intent tied to the target app, resolves it, checks that the resolved activity belongs to the expected packageName, and pins component and package. It then returned that copy, which Settings started with AppRestrictionsFragment.this.startActivityForResult. The copy still carried whatever flags, data URI and ClipData the app had supplied. Because Settings launches the activity, FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION flags on the Intent could lead to URI grants for the URIs in data/clipData being issued under Settings' identity. The package-match check alone was not enough.",
     "fix_analysis": [
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.assertSafeToStartCustomActivity: after setComponent/setPackage on vettedIntent, the fix calls vettedIntent.removeFlags(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION | FLAG_GRANT_PREFIX_URI_PERMISSION).",
      "Same function: vettedIntent.setData(null) and vettedIntent.setClipData(null), so the launched Intent no longer carries a data URI or ClipData.",
      "tests/robotests/src/com/android/settings/users/AppRestrictionsFragmentTest.java (new): assertSafeToStartCustomActivity_stripsSensitiveData builds an Intent with all four grant flags, a content:// data URI and a ClipData. It mocks PackageManager.resolveActivity to return a matching package, calls the private method via reflection, and asserts that the flags are cleared and data/clipData are null."
     ],
     "completeness": [
      "Covers the single launch path in the diff: the vetted Intent returned by assertSafeToStartCustomActivity and started with AppRestrictionsFragment.this.startActivityForResult(vettedIntent, requestCode).",
      "Removes all four FLAG_GRANT_*_URI_PERMISSION flags and clears both data and clipData.",
      "Other Intent fields from the app (action, categories, extras, remaining flags) still pass through unchanged; the fix does not sanitize them.",
      "The existing package-match check and component pinning are unchanged; the fix only adds stripping on top of them."
     ],
     "behavior_changes": [
      "Custom restrictions activities launched from AppRestrictionsFragment no longer receive a data URI, ClipData or URI grant flags from the Intent they supplied. Apps that relied on data/clipData being forwarded to their custom activity will see them as null.",
      "No new exceptions or error paths; the existing SecurityException/ActivityNotFoundException behavior is unchanged."
     ],
     "preconditions": [
      "An app must supply a custom restrictions Intent that reaches RestrictionsResultReceiver.assertSafeToStartCustomActivity and is then started by Settings (from code context).",
      "The app-supplied Intent must resolve to an activity in the same package as the expected packageName (the existing check)."
     ],
     "per_major": {
      "14": "same fix: commit 0f9f6238cf25 in android-security-14.0.0_r29, identical hunk (at line 687) plus the same new robolectric test; 4/4 patched lines present.",
      "15": "same fix: commit 856f8ccca12e in android-security-15.0.0_r17, identical hunk (line 683) and test; 4/4 patched lines present.",
      "16": "same fix: commit 3bf7abe6e14b in android-security-16.0.0_r8, hunk at line 683 plus new test; 4/4 patched lines present.",
      "17": "same fix: commit a3cbe8fbf286 in android-security-17.0.0_r1, identical hunk (line 726) and test; 4/4 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit. The six added lines go directly after the setComponent/setPackage calls in assertSafeToStartCustomActivity. Only the line offset differs across branches (687/683/683/726).",
      "The robolectric test uses ReflectionHelpers to set mPackageManager and call the private method. It needs the SettingsRoboTests target and a three-argument RestrictionsResultReceiver(String, ..., boolean) constructor."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which URIs or providers reachable through Settings could be exposed by the grant, nor the reporter scenario behind bug 491253353.",
      "The material does not show the code that receives the custom Intent from the app (the RestrictionsResultReceiver code above line 653), so the delivery mechanism and which user opens the UI are not shown.",
      "Not shown: whether a URI in the extras that pass through unchanged could still end up granted when the activity starts, for example if the framework moves extras into ClipData after this vetting."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28606": {
     "cve": "CVE-2026-28606",
     "summary": "AdapterService now removes the per-device entry from mBondAttemptCallerInfo whenever the bond state transitions to anything other than BOND_BONDING, so the CallerInfo recorded for a bonding attempt is no longer retained after bonding completes or fails.",
     "component": "platform/packages/modules/Bluetooth, btservice/AdapterService (bond state handling)",
     "weakness": "Missing cleanup of per-device caller state (stale CallerInfo retained in mBondAttemptCallerInfo after bonding concludes)",
     "root_cause": "AdapterService holds mBondAttemptCallerInfo, which stores the CallerInfo for a device's bond attempt and is looked up by device address string in the fix. The bond-state-change handler (the method that resets phonebook/SIM access permissions to ACCESS_UNKNOWN and, in Android 17, calls mStorage.onBondStateChanged) did not remove that entry when the device left BOND_BONDING. So the caller info outlived the bonding process, whether it completed or failed. The commit message says the fix ensures \"stale caller information is not retained after the bonding process has completed or failed\". The material does not show how the stale entry was later used.",
     "fix_analysis": [
      "android/app/src/com/android/bluetooth/btservice/AdapterService.java, bond-state-change handler (function name not shown in the diff): after the existing block that resets setPhonebookAccessPermission/setSimAccessPermission, a new block runs `if (toState != BOND_BONDING)` and calls `mBondAttemptCallerInfo.remove(device.getAddress())`. If an entry was present, it logs at debug level. A static import of BluetoothDevice.BOND_BONDING is added.",
      "The removal runs for any toState except BOND_BONDING, so it covers both the BOND_BONDED and BOND_NONE outcomes.",
      "On Android 15 and 16 the inserted code is unconditional (no Flags check), even though the commit message cites Flag com.android.bluetooth.flags.remove_bond_caller_info.",
      "On Android 17, commit 1b0d684402b2 'Unflag remove_bond_caller_info' changes an existing `if (Flags.removeBondCallerInfo() && toState != BOND_BONDING)` (preceded by a comment and placed after mStorage.onBondStateChanged) to `if (toState != BOND_BONDING)`. It also deletes the remove_bond_caller_info flag from flags/pairing.aconfig. The commit that first added the flagged version is not identified in the material."
     ],
     "completeness": [
      "Covers every bond state transition that goes through this handler with toState != BOND_BONDING. Both the BONDED and NONE outcomes clear the entry.",
      "Only this handler is changed. The code that inserts into mBondAttemptCallerInfo and the code that reads it are not shown and are not touched. Any other lifecycle (for example a bond attempt that never produces a state change callback, or adapter off/reset) is not addressed by this diff and cannot be judged from the material.",
      "The entry is removed only for the exact address string returned by device.getAddress(). The material does not show whether entries can exist under other identifiers."
     ],
     "behavior_changes": [
      "After a device's bond state leaves BOND_BONDING, mBondAttemptCallerInfo no longer holds an entry for that device address. Any later consumer of this caller info for that device finds no entry until a new bond attempt records one.",
      "New debug log line 'Removed bond caller info for device: <device>' when an entry is removed.",
      "Android 17 only: the aconfig flag remove_bond_caller_info is removed and the behavior is always on."
     ],
     "preconditions": [
      "A bond attempt must have stored a CallerInfo in mBondAttemptCallerInfo for the device address (where it is stored is not shown in the material).",
      "The device's bond state must move out of BOND_BONDING (to BOND_BONDED or BOND_NONE) through AdapterService's bond-state-change handler."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. android-security-14.0.0_r29 has only 3/8 lines of the patched region. The material does not show which lines match or whether mBondAttemptCallerInfo exists there.",
      "15": "Same fix: commit 9b196f44770c in android-security-15.0.0_r17. The 8-line insertion is identical and unconditional, at a different file offset (around line 6098). 8/8 lines present.",
      "16": "Same fix: commit 07542331e7c4 in android-security-16.0.0_r8 (cherry-pick of cb84e50dfff3), unconditional insertion. 8/8 lines present.",
      "17": "Adapted fix: the code was first gated by Flags.removeBondCallerInfo(), with a comment, and placed after mStorage.onBondStateChanged. Commit 1b0d684402b2 'Unflag remove_bond_caller_info' in android-security-17.0.0_r1 removes the flag check and the aconfig flag definition. 6/8 lines of the patched region are present (the reason for the mismatch is not shown)."
     },
     "backport_notes": [
      "The fix is a self-contained insertion. It needs mBondAttemptCallerInfo and the CallerInfo type to already exist in AdapterService. Place it in the bond-state-change handler after the block that resets phonebook/SIM access permissions. The 15/16 versions need no aconfig flag. If following the 17 lineage, the flagged code must already be present before applying 1b0d684402b2.",
      "The inserted `if` line has non-standard 6-space indentation in the 15/16 commits. This is cosmetic."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact name of the AdapterService method containing the change is not shown in the diff context.",
      "Where mBondAttemptCallerInfo is declared and populated, and which code reads it (and therefore how stale CallerInfo leads to the bulletin's EoP), is not in the material.",
      "Whether other lifecycle events (adapter off, a bond attempt that never reaches a state-change callback) can leave entries in the map.",
      "The commit that introduced the flag-gated version on Android 17 is not identified.",
      "Why only 3/8 (Android 14) and 6/8 (Android 17) lines of the patched region match.",
      "The bug id in the aconfig flag (492665910) differs from the commit's Bug (495543559). The relationship is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28607": {
     "cve": "CVE-2026-28607",
     "summary": "Adds caller permission checks (REQUEST_INSTALL_PACKAGES requested or INSTALL_PACKAGES granted) and the hide-non-system-overlays window flag to UnarchiveErrorActivity.onCreate, moves the helper lookups into PackageUtil, and on Android 17 moves the PIA v2 UnarchiveLaunch forwarding in UnarchiveActivity behind the same checks.",
     "component": "platform/frameworks/base, packages/PackageInstaller (unarchive UI activities)",
     "weakness": "Missing permission/caller authorization check on an activity entry point",
     "root_cause": "UnarchiveErrorActivity.onCreate read its intent extras (EXTRA_UNARCHIVE_STATUS, EXTRA_REQUIRED_BYTES and others) without first finding the launching UID or checking that the caller requested REQUEST_INSTALL_PACKAGES or held INSTALL_PACKAGES. Its window also lacked SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS. UnarchiveActivity already ran these checks. On Android 17, however, it forwarded to the v2 UnarchiveLaunch activity (when PackageUtil.isVersionTwoEnabled is true) before running them, so the v2 path skipped the gate.",
     "fix_analysis": [
      "PackageUtil.java: adds static getPackageNameForUid(PackageManager, int), which returns packagesForUid[0] or null and logs on null, and getRequestedPermissions(PackageManager, String), which returns requestedPermissions via GET_PERMISSIONS or an empty array and catches Exception rather than only NameNotFoundException. These are the former private helpers of UnarchiveActivity, moved so both activities can share them.",
      "UnarchiveActivity.java:onCreate: removes the private getRequestedPermissions/getPackageNameForUid helpers, calls the PackageUtil versions instead and reuses a single PackageManager instance. On Android 15/16 this file change is refactoring only; the existing checks are unchanged.",
      "UnarchiveErrorActivity.java:onCreate: adds getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) before super.onCreate. It then reads getLaunchedFromUid() and aborts with RESULT_FIRST_USER/finish() on INVALID_UID. Next it resolves the caller package via PackageUtil.getPackageNameForUid and aborts if that returns null. Finally it computes hasRequestInstallPermission (REQUEST_INSTALL_PACKAGES among the requested permissions) and hasInstallPermission (checkPermission(INSTALL_PACKAGES, 0, callingUid) == PERMISSION_GRANTED), and aborts with RESULT_FIRST_USER if neither holds. Only after these checks are the intent extras read.",
      "Android 17 only, UnarchiveActivity.java:onCreate: the isVersionTwoEnabled block that builds the piaV2 Intent (EXTRA_CALLING_PKG_NAME, EXTRA_CALLING_PKG_UID, setClass UnarchiveLaunch, FLAG_ACTIVITY_FORWARD_RESULT) moves from before the UID/permission checks to after them, so the v2 launch is now gated. In UnarchiveErrorActivity on 17, the existing isVersionTwoEnabled forward now sits after the newly added checks."
     ],
     "completeness": [
      "Covers UnarchiveErrorActivity (new checks) and, on 17, the v2 forwarding path from both v1 activities. UnarchiveActivity's own checks were already present on 15/16.",
      "The REQUEST_INSTALL_PACKAGES check only tests that the permission is listed in the caller's requested permissions. It does not check that the permission is granted. The in-code comment says AppOps is intentionally not consulted because the requester is not the installation source.",
      "PackageUtil.getPackageNameForUid returns packagesForUid[0], so for shared UIDs the permission lookup uses the first package only.",
      "This fix does not change how the intent extras read after the checks are validated.",
      "The material does not show whether the v2 UnarchiveLaunch activity runs its own caller checks when started directly."
     ],
     "behavior_changes": [
      "UnarchiveErrorActivity now logs an error and finishes immediately with Activity.RESULT_FIRST_USER when the launching UID is INVALID_UID, has no package, or neither requests REQUEST_INSTALL_PACKAGES nor holds INSTALL_PACKAGES.",
      "UnarchiveErrorActivity's window now has SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, so non-system overlays are hidden while it is shown.",
      "On Android 17 with android.content.pm.use_pia_v2 enabled, UnarchiveActivity starts the v2 UnarchiveLaunch activity only after the caller passes the permission checks.",
      "PackageUtil.getRequestedPermissions now swallows any Exception (not just NameNotFoundException) and returns an empty array."
     ],
     "preconditions": [
      "A caller able to start UnarchiveErrorActivity, or on 17 with use_pia_v2 enabled, UnarchiveActivity forwarding to UnarchiveLaunch. The material does not show the activities' manifest export or permission attributes.",
      "Before the fix, UnarchiveErrorActivity.onCreate had no in-code caller check. After the fix, the caller must request REQUEST_INSTALL_PACKAGES or hold INSTALL_PACKAGES.",
      "The Android 17 UnarchiveActivity reordering only matters when PackageUtil.isVersionTwoEnabled is true (flag android.content.pm.use_pia_v2)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed); no fix commit is linked. The resolver reports 7/8 lines of the patched region present at android-security-14.0.0_r29, but the material does not show whether the unarchive activities exist or are reachable on 14.",
      "15": "Same fix: a4b48c54049c in android-security-15.0.0_r17. Same content as the 16 commit; only the PackageUtil base blob and hunk offsets differ.",
      "16": "Same fix: 2e158f2d9a81 in android-security-16.0.0_r8. UnarchiveErrorActivity gains the checks; UnarchiveActivity is refactored to use the PackageUtil helpers.",
      "17": "Adapted fix: 743a34f05af0 in android-security-17.0.0_r1. PackageUtil changes are the same. UnarchiveErrorActivity gets the same checks, but the LOG_TAG field is not added (it already appears to exist there), and the checks are placed before the existing isVersionTwoEnabled forward. In UnarchiveActivity, the isVersionTwoEnabled/UnarchiveLaunch forwarding block moves after the permission checks and the androidx NonNull import is dropped."
     },
     "backport_notes": [
      "Single commit. The PackageUtil additions use PackageUtil.LOG_TAG and the NonNull annotation without adding them, so both must already exist in that file. On branches whose UnarchiveActivity carries the PIA v2 UnarchiveLaunch forwarding (17), take the 17 variant so the isVersionTwoEnabled block lands after the permission checks. The 15/16 diffs contain no such block in UnarchiveActivity."
     ],
     "confidence": "high",
     "unknowns": [
      "The manifest export status and any intent-filter/permission attributes of UnarchiveErrorActivity and UnarchiveLaunch.",
      "What concrete privileged effect an unauthorized caller could get through UnarchiveErrorActivity's extras. The bulletin only rates the issue High EoP.",
      "How UnarchiveErrorActivity reads the installer package name/title and the pending intent.",
      "Whether the v2 UnarchiveLaunch activity has its own caller checks.",
      "Why most of the patched region is present on Android 14 even though the bulletin does not list it as affected."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28611": {
     "cve": "CVE-2026-28611",
     "summary": "Makes the caller-UID vs. passed-in package name validation (mNfcPermissions.checkPackage) unconditional in six INfcAdapter binder methods of NfcService by removing the aconfig flag check_passed_in_package that previously gated it.",
     "component": "platform/packages/modules/Nfc (NfcNci), NfcService INfcAdapter binder interface / NfcPermissions",
     "weakness": "Missing / conditionally disabled caller identity validation (package name supplied by the binder caller trusted without verifying it belongs to the calling UID)",
     "root_cause": "In NfcNci/src/com/android/nfc/NfcService.java, these NfcAdapterService binder methods take a package name from the caller: enable(String pkg), disable(boolean, String pkg), setObserveMode(boolean, String packageName), updateDiscoveryTechnology(..., String packageName), setReaderMode(..., String packageName) and indicateDataMigration(boolean, String pkg). The name feeds authorization-relevant decisions, for example isDeviceOrProfileOwner(Binder.getCallingUid(), pkg) in enable/disable, the nfc_allow_list check and getAppName in enable, and logging. The call to mNfcPermissions.checkPackage(Binder.getCallingUid(), pkg) was wrapped in `if (Flags.checkPassedInPackage())`. In the Android 15 implementation this check verifies through AppOpsManager.checkPackage that the package belongs to the calling UID and rejects a null name. When the aconfig flag was not enabled, the package name was never validated against the calling UID, so a caller could present another package's name to these methods. On Android 15 (platform/packages/apps/Nfc) the checkPackage helper did not exist and the methods did no such validation.",
     "fix_analysis": [
      "Android 16, NfcNci/src/com/android/nfc/NfcService.java: in NfcAdapterService.enable, disable, setObserveMode, updateDiscoveryTechnology, setReaderMode and indicateDataMigration, the `if (Flags.checkPassedInPackage()) { ... }` guard is removed. mNfcPermissions.checkPackage(Binder.getCallingUid(), pkg/packageName) now runs unconditionally as the first statement (in setObserveMode, the first statement inside the synchronized block). It runs before isDeviceOrProfileOwner, enforceUserPermissions, enforceAdminPermissions and the other shown uses of the package name.",
      "Android 16, NfcNci/flags/nfc_flags.aconfig: the flag definition `check_passed_in_package` (bug 364020298) is deleted, so flag configuration can no longer turn the behaviour off.",
      "Android 16, NfcNci/tests/unit/src/com/android/nfc/NfcServiceTest.java: adds an AppOpsManager mock and returns it from mApplication.getSystemService(AppOpsManager.class). This change is test-only.",
      "Android 15, platform/packages/apps/Nfc d914feee6a9b, src/com/android/nfc/NfcPermissions.java: adds an AppOpsManager field set in the constructor and a new checkPackage(int uid, String pkgName). The helper throws SecurityException when pkgName is null and otherwise calls mAppOpsManager.checkPackage(uid, pkgName).",
      "Android 15, src/com/android/nfc/NfcService.java: inserts mNfcPermissions.checkPackage(Binder.getCallingUid(), pkg/packageName) as the first statement of enable, disable and setObserveMode, with no flag. The test now stubs getPackageUid(PKG_NAME, 0) to return the calling UID and passes PKG_NAME instead of null to setObserveMode."
     ],
     "completeness": [
      "Android 16: the validation is now unconditional in all six NfcAdapterService methods that previously had the flagged check (enable, disable, setObserveMode, updateDiscoveryTechnology, setReaderMode, indicateDataMigration). Removing the flag also means flag configuration can no longer disable the check.",
      "Android 16: in those methods the check runs before any shown use of the package name. It precedes isDeviceOrProfileOwner in enable/disable and the permission enforcement in the others.",
      "Android 15: the fix only adds the check to enable, disable and setObserveMode. The material does not show whether other binder methods in the Android 15 NfcService accept a package name without validation.",
      "The material does not show whether other INfcAdapter methods, or other binder stubs in NfcService, take a caller-supplied package name without a checkPackage call. The diff touches only the six methods that were already flag-gated."
     ],
     "behavior_changes": [
      "On enable, disable, setObserveMode, updateDiscoveryTechnology, setReaderMode and indicateDataMigration, a caller whose supplied package name does not belong to its calling UID is now always rejected by checkPackage, whatever the flag state. On Android 15 the rejection is a SecurityException from AppOpsManager.checkPackage.",
      "A null package name is now rejected rather than processed. On Android 15 this is a SecurityException (\"Checking UID ... but Package Name is Null\"), which is why the Android 15 test changes setObserveMode(true, null) to PKG_NAME.",
      "The aconfig flag com.android.nfc.flags.check_passed_in_package no longer exists."
     ],
     "preconditions": [
      "The caller can reach the NFC service's INfcAdapter binder interface (NfcAdapterService) and call enable, disable, setObserveMode, updateDiscoveryTechnology, setReaderMode or indicateDataMigration with a chosen package name string.",
      "Before the fix on Android 16: the package check was skipped when the aconfig flag check_passed_in_package was not enabled.",
      "Before the fix on Android 15: these methods had no package validation, so no flag condition applied.",
      "The other per-method checks still apply after the fix: checkAdminPermissions or device/profile owner status in enable/disable, enforceUserPermissions in updateDiscoveryTechnology, enforceAdminPermissions in indicateDataMigration, and NFC being enabled for setObserveMode. In enable/disable, the device/profile-owner path depended directly on the unvalidated package name."
     ],
     "per_major": {
      "14": "Not affected per the bulletin; platform/packages/modules/Nfc is not present in Android 14 security tags.",
      "15": "Adapted fix: platform/packages/apps/Nfc d914feee6a9b (android-security-15.0.0_r17) introduces NfcPermissions.checkPackage (AppOpsManager-based) and calls it unconditionally in enable, disable and setObserveMode only; 8/8 lines of the patched region present.",
      "16": "Fix ebbca6f8125e in platform/packages/modules/Nfc (android-security-16.0.0_r8) removes the check_passed_in_package flag gate in six methods and deletes the flag from nfc_flags.aconfig. 5/8 lines of the patched region are present at that tag, and the shown context has the unconditional checkPackage call in enable.",
      "17": "Not affected (not listed by the bulletin); at android-security-17.0.0_r1, 3/8 lines of the patched region present."
     },
     "backport_notes": [
      "On a tree without NfcPermissions.checkPackage (as on Android 15), the Android 15 commit is the self-contained form: add the AppOpsManager field and checkPackage helper to NfcPermissions, then insert the call as the first statement of each package-taking binder method. The Android 16 commit assumes the helper and the flagged calls already exist and only removes the Flags.checkPassedInPackage() guard. If the target tree's updateDiscoveryTechnology, setReaderMode and indicateDataMigration take a package name, consider covering them too.",
      "When deleting the flag from nfc_flags.aconfig, remove every Flags.checkPassedInPackage() reference. Unit tests that construct NfcService need an AppOpsManager from getSystemService: the Android 15 NfcPermissions gets it in its constructor, and the Android 16 test adds that mock."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether check_passed_in_package was enabled or disabled by default on shipping Android 16 builds before the fix (the material does not show flag defaults).",
      "The Android 16 NfcPermissions.checkPackage implementation (it is not in the material; the AppOpsManager-based version is shown only for Android 15).",
      "Whether other binder methods in NfcService (on 15 or 16) accept a caller-supplied package name without validation; only the six flagged methods are shown.",
      "The concrete security impact of a spoofed package name beyond the shown uses (device/profile-owner check, nfc_allow_list, app-name display, logging); the bulletin only rates it High EoP.",
      "The state of the Android 17 code (3/8 patched lines present)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28612": {
     "cve": "CVE-2026-28612",
     "summary": "Reorders activity start so that the intent-redirect checks (the EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN check and the IntentCreatorToken-based creator uid/package extraction) run before supervisor.resolveIntent/resolveActivity in both ActivityStarter and ActivityStartController, so that intent resolution can no longer change the intent's component and invalidate the creator token before it is checked.",
     "component": "platform/frameworks/base, services/core WindowManager activity start (ActivityStarter, ActivityStartController)",
     "weakness": "Incorrect ordering of a security check relative to state mutation: the creator-token checks were evaluated on an intent that resolution had already modified.",
     "root_cause": "In ActivityStarter (the request resolution block around line 606) and in the ActivityStartController per-intent loop (around line 437), the code first called supervisor.resolveIntent/resolveActivity. Only afterwards did it check EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN (ActivityStarter only) and call IntentCreatorToken.isValid(intent) to extract the creator uid/package. resolveActivity can modify the intent: the existing comment in ActivityStartController says \"the intent may be modified in resolveActivity below\", and the commit message says resolveActivity changes the intent's component, which invalidates the creatorToken. In the patched region of the ActivityStartController loop there was no MISSING_CREATOR_OR_INVALID_TOKEN check before the fix.",
     "fix_analysis": [
      "ActivityStarter.java (request resolution block before 'Carefully collect grants'): the supervisor.resolveIntent(...) call, the resolveIntentForLockedOrStoppedProfiles fallback and supervisor.resolveActivity(...) are moved below the existing 'Check if the Intent was redirected' block. The EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN check (logAndThrowExceptionForIntentRedirect) and the IntentCreatorToken.isValid(intent) extraction of intentCreatorUid/intentCreatorPackage now operate on the unresolved intent copy.",
      "ActivityStartController.java (per-intent loop over intents[]): the mSupervisor.resolveActivity(...) and mService.mAmInternal.getActivityInfoForUser(...) calls are moved after the creator-token handling. A new check is added: if EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN is set, the loop calls ActivityStarter.logAndAbortForIntentRedirect(mService.mContext, INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN, intent, creatorUid, creatorPackage, filterCallingUid, callingPackage) and returns START_CANCELED when that call returns true. The ActivityManagerService.IntentCreatorToken.isValid(intent) extraction of creatorUid/creatorPackage follows, and resolution comes last.",
      "ActivityStarter.java: logAndAbortForIntentRedirect is changed from 'private static' to package-private 'static' so that ActivityStartController can call it."
     ],
     "completeness": [
      "Both resolution sites shown in the diff (ActivityStarter request resolution and the ActivityStartController multi-intent loop) are reordered in the same way.",
      "The ActivityStartController loop gains a MISSING_CREATOR_OR_INVALID_TOKEN abort that the patched region did not have before. The material does not show whether that condition was enforced elsewhere on that path.",
      "The fix only reorders checks. It does not change how resolveActivity mutates the intent or how IntentCreatorToken.isValid validates the token. If any other start path resolves the intent before consulting the creator token, it is not visible in the material and this diff does not address it.",
      "After the fix, the two sites behave differently on a missing or invalid token. ActivityStarter calls logAndThrowExceptionForIntentRedirect with resolvedCallingUid. ActivityStartController calls logAndAbortForIntentRedirect with filterCallingUid and returns START_CANCELED; this check is new in this fix."
     ],
     "behavior_changes": [
      "The creator uid/package used for intent redirect handling are now derived from the intent before resolution.",
      "New early return of START_CANCELED (with a Slog.d message) in the ActivityStartController multi-intent loop when the intent carries EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN and logAndAbortForIntentRedirect returns true.",
      "In ActivityStarter, the missing/invalid-token check (logAndThrowExceptionForIntentRedirect) now runs before intent resolution instead of after it.",
      "The visibility of logAndAbortForIntentRedirect is widened to package-private."
     ],
     "preconditions": [
      "An activity start goes through ActivityStarter request resolution or the ActivityStartController multi-intent start loop with an Intent carrying an IntentCreatorToken.",
      "Per the commit message, intent resolution (resolveActivity) changes the intent's component, which invalidates the creatorToken.",
      "The exact trigger (which intent shapes cause the component change, which caller API) is not shown in the material."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29, 4/8 lines of the patched region are present. No fix applied.",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17, 6/8 lines of the patched region are present. No fix applied.",
      "16": "Same fix: commit f7398bb64526 in android-security-16.0.0_r8 (cherry-pick, Change-Id Iae6a7ef5cb569e1436c9a705b686488bf348b5ff). 8/8 lines present.",
      "17": "Same fix: commit a2f24fcecabd in android-security-17.0.0_r1, identical hunks at shifted line offsets. 8/8 lines present."
     },
     "backport_notes": [
      "Single commit touching two files. Both reorderings and the visibility change of ActivityStarter.logAndAbortForIntentRedirect must land together, because ActivityStartController now calls that method. The commit depends on the IntentCreatorToken / EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN logic already being present on the branch."
     ],
     "confidence": "high",
     "unknowns": [
      "How IntentCreatorToken.isValid validates the token (which intent fields are bound) and exactly how resolveActivity alters the component. Only the commit message states that the component change invalidates the token.",
      "What concretely happened downstream before the fix when the token was invalidated (for example, whether creatorUid stayed at DEFAULT_INTENT_CREATOR_UID and how later enforcement was affected).",
      "Whether logAndAbortForIntentRedirect always aborts or depends on a flag or enforcement mode (it returns a boolean), and whether logAndThrowExceptionForIntentRedirect always throws.",
      "Whether other activity start entry points outside these two sites perform creator-token checks after resolution.",
      "Whether Android 14/15 contain the creator-token logic; the material gives only partial line counts."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28613": {
     "cve": "CVE-2026-28613",
     "summary": "In Live TV's ChannelImpl.initAppLinkTypeAndIntent, the fix refuses to build a channel app link Intent when the resolved target activity is exported and declares an android:permission, so no app link is created for permission-protected activities.",
     "component": "platform/packages/apps/TV (Live TV), channel app link handling in src/com/android/tv/data/ChannelImpl.java",
     "weakness": "Intent redirection: missing check that a parsed channel app link Intent does not target a permission-protected activity",
     "root_cause": "ChannelImpl.initAppLinkTypeAndIntent parses the channel's mAppLinkIntentUri with Intent.parseUri(..., Intent.URI_INTENT_SCHEME) and resolves it with intent.resolveActivityInfo(pm, 0). Before the fix, the only rejection was isProtectedActivity, which covers targets in CommonConstants.BASE_PACKAGE or its sub-packages. Any other resolvable activity was accepted as mAppLinkIntent with APP_LINK_TYPE_CHANNEL. That included exported activities guarded by an android:permission. Going by the commit title, the concern is intent redirection to such permission-protected activities. How and where the Intent is launched is not in the material.",
     "fix_analysis": [
      "src/com/android/tv/data/ChannelImpl.java:initAppLinkTypeAndIntent: after resolving activityInfo, the fix computes isPermissionProtected = activityInfo.exported && (activityInfo.permission != null).",
      "If isPermissionProtected is true, the method logs 'Attempt to add app link to permission protected activity' and returns early. mAppLinkType stays APP_LINK_TYPE_NONE and mAppLinkIntent stays null, as set at the top of the method, so getAppLinkType/getAppLinkIntent expose no app link for that channel.",
      "The existing isProtectedActivity check for own-package targets is kept and evaluated first. Only whitespace in its Log.w call changed.",
      "A code comment records that ChooserIntent and ACTION_SEND were considered and intentionally not restricted. The stated reasons: parseUri cannot carry Intent.EXTRA_INTENT, and testing showed ACTION_SEND does not yield FLAG_GRANT_* here."
     ],
     "completeness": [
      "Covers the APP_LINK_TYPE_CHANNEL path in ChannelImpl.initAppLinkTypeAndIntent, which both getAppLinkType(Context) and getAppLinkIntent(Context) call lazily. The rest of the method, after line 710, is not shown.",
      "Only exported activities with a non-null activityInfo.permission are blocked. The fix does not inspect Intent flags (e.g. FLAG_GRANT_* URI permissions) or extras of the parsed Intent, and the commit comment explicitly declines to restrict chooser or ACTION_SEND intents.",
      "Activities whose permission is null but that are otherwise sensitive are not restricted. The check looks only at the android:permission attribute of the resolved activity.",
      "The check runs only when resolveActivityInfo returns non-null. The material does not show the path taken when it returns null."
     ],
     "behavior_changes": [
      "Channel app links whose Intent resolves to an exported activity with an android:permission are now ignored. The channel reports APP_LINK_TYPE_NONE and a null Intent instead of APP_LINK_TYPE_CHANNEL.",
      "A new Log.w message, 'Attempt to add app link to permission protected activity: <uri>', is emitted in that case.",
      "TV input apps whose app link pointed at a permission-protected activity lose that link."
     ],
     "preconditions": [
      "A channel visible to Live TV carries non-empty app link text and a non-empty app link Intent URI (mAppLinkText, mAppLinkIntentUri).",
      "The URI parses via Intent.parseUri(URI_INTENT_SCHEME) and resolves to an activity outside CommonConstants.BASE_PACKAGE.",
      "Live TV subsequently uses the resulting app link Intent. The launch site is not in the material."
     ],
     "per_major": {
      "14": "same fix: platform/packages/apps/TV e64e4b90fc77 in android-security-14.0.0_r29, identical diff (blob 5be1179d..62c06cc2); 7/8 lines of the patched region present at the tag",
      "15": "same fix: platform/packages/apps/TV 481a7a588acd in android-security-15.0.0_r17, identical diff; 7/8 lines of the patched region present at the tag",
      "16": "same fix: platform/packages/apps/TV 86fb106012ec in android-security-16.0.0_r8, identical diff; 7/8 lines of the patched region present at the tag",
      "17": "same fix: platform/packages/apps/TV a10682aac138 in android-security-17.0.0_r1, identical diff; 7/8 lines of the patched region present at the tag"
     },
     "backport_notes": [
      "Single self-contained Java change. The pre-fix file blob (5be1179d) is the same in the 14, 15, 16 and 17 diffs, so the patch should apply as is. Forks that modified the isProtectedActivity block in ChannelImpl.initAppLinkTypeAndIntent need to insert the isPermissionProtected computation and its early return."
     ],
     "confidence": "high",
     "unknowns": [
      "Where and how Live TV launches mAppLinkIntent, including the launch site and any flags added there, is not in the material.",
      "Who supplies or controls the channel app link fields (mAppLinkText, mAppLinkIntentUri) is not shown.",
      "Which TV-app permissions could be involved in the redirection is not stated in the bulletin or the commit.",
      "The rest of initAppLinkTypeAndIntent after line 710 is not shown, including the path taken when resolveActivityInfo returns null.",
      "Whether other code in packages/apps/TV parses channel-supplied Intent URIs outside ChannelImpl.initAppLinkTypeAndIntent is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28614": {
     "cve": "CVE-2026-28614",
     "summary": "Adds an isCallerValid() check to SystemUI's SlicePermissionActivity.onCreate so the slice permission dialog is only shown when the activity was actually launched by the SliceProvider's package, SystemUI itself, or the \"android\" system package; any other launcher causes finish().",
     "component": "platform/frameworks/base, SystemUI (packages/SystemUI) SlicePermissionActivity, Slices permission UI",
     "weakness": "Missing caller/origin validation on a permission-granting activity (confused deputy via intent extras)",
     "root_cause": "SlicePermissionActivity.onCreate took the requesting package (mCallingPkg) from the intent extra SliceProvider.EXTRA_PKG and the target slice from EXTRA_BIND_URI. It validated only that the URI was non-null, that its content type was SliceProvider.SLICE_TYPE, that the intent action was ACTION_REQUEST_SLICE_PERMISSION, and (in verifyCallingPkg) the optional \"provider_pkg\" extra against the resolved provider. It then built the permission dialog, whose title is formatted from the labels of mCallingPkg (app1) and mProviderPkg (app2). It never checked which package actually launched the activity. Per the commit message, this let a malicious app trick the user into granting slice permissions by spoofing the package name in the intent.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java:onCreate: after mProviderPkg is resolved via pm.resolveContentProvider(mUri.getAuthority(), ...) and verifyCallingPkg() runs, a new 'if (!isCallerValid()) { finish(); return; }' gate is inserted before the application labels are loaded and the AlertDialog is built and shown.",
      "packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java:isCallerValid (new): obtains the launcher via getLaunchedFromPackage(), falling back to getCallingPackage() when that is null. Returns true only if the result is non-null and equals mProviderPkg (the package owning the slice URI's authority), getPackageName() (SystemUI itself), or the literal string \"android\". Otherwise logs 'Direct launch blocked. Expected provider ... or system, but got ...' via Log.e and returns false.",
      "packages/SystemUI/tests/src/com/android/systemui/SlicePermissionActivityTest.kt (new): unit tests covering launch from the provider package, from com.android.systemui, from \"android\", from a null getLaunchedFromPackage with getCallingPackage fallback (all expected not to finish), and from an untrusted package (expected to finish)."
     ],
     "completeness": [
      "The gate sits before label loading and dialog creation in onCreate, and before the activity registers itself as the dialog's positive/negative button and dismiss listener, so an untrusted launcher no longer gets the dialog.",
      "The check is placed after resolveContentProvider and verifyCallingPkg inside the existing try block, so it runs only once mProviderPkg is known; the earlier intent validity checks (URI null, slice type, action) are unchanged.",
      "The EXTRA_PKG value (mCallingPkg) itself is still taken from the intent without verification; the fix relies on the launcher being trusted (provider, SystemUI, or android) rather than cross-checking mCallingPkg against the launcher.",
      "verifyCallingPkg() (comparison of the optional \"provider_pkg\" extra with the resolved provider) is left as is; its behaviour on mismatch is not shown in the material.",
      "Allow-list is by package name string; the \"android\" entry is a literal string comparison, and the SystemUI entry uses getPackageName()."
     ],
     "behavior_changes": [
      "SlicePermissionActivity now finishes immediately (no dialog) when launched by any package other than the slice's provider package, SystemUI, or \"android\"; a Log.e line 'Direct launch blocked...' is emitted.",
      "If both getLaunchedFromPackage() and getCallingPackage() return null, the activity is rejected (null caller is treated as invalid).",
      "Launches by the SliceProvider's own package, SystemUI, or \"android\" still reach the dialog as before."
     ],
     "preconditions": [
      "An app able to start SlicePermissionActivity with an intent whose action is SliceManager.ACTION_REQUEST_SLICE_PERMISSION, an EXTRA_BIND_URI whose content type resolves to SliceProvider.SLICE_TYPE, and a chosen EXTRA_PKG.",
      "The slice provider for the chosen URI must be resolvable via PackageManager.resolveContentProvider so mProviderPkg is set.",
      "User interaction is required to grant the permission in the displayed dialog (per the commit message, the user is tricked into granting).",
      "Whether the activity is exported in the SystemUI manifest is not shown in the material; the commit message implies it was directly launchable."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base c6b5038a9e23 in android-security-14.0.0_r29; identical isCallerValid() and onCreate gate, only hunk line offsets differ (66/110 vs 72/116); same new test file. 8/8 lines of patched region present.",
      "15": "same fix: platform/frameworks/base 971f1d196295 in android-security-15.0.0_r17; identical diff to the 16 commit. 8/8 lines present.",
      "16": "same fix: platform/frameworks/base dba6ec2c05ff in android-security-16.0.0_r8 (cherry-pick of 7f44449562bc); 8/8 lines present.",
      "17": "same fix: platform/frameworks/base 7e25b93a1faa in android-security-17.0.0_r1; identical diff. 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit; the Java change inserts the gate right after verifyCallingPkg() in SlicePermissionActivity.onCreate plus a new private method. The new Kotlin test depends on SysuiTestCase, @RunWithLooper, reflection into Activity private fields (mActivityInfo, mFragments, mApplication, mWindow) and android.window.OnBackInvokedDispatcher, so it may need adaptation on branches with different SystemUI test infrastructure; the production change does not depend on it.",
      "Activity.getLaunchedFromPackage() is a public API since API 34 (Android 14) and was hidden before that (general platform knowledge); confirm availability on the target branch before backporting."
     ],
     "confidence": "high",
     "unknowns": [
      "The Allow-button handler (what is granted and to which package via SliceManager) is not shown in the provided context, so the exact privilege gained on a successful spoof is inferred only from the commit message.",
      "The SystemUI manifest entry for SlicePermissionActivity (exported status, permission) is not in the material.",
      "How the platform SliceProvider launches this activity (and therefore whether getLaunchedFromPackage() reliably yields the provider package in the legitimate flow) is not shown; the fix's fallback to getCallingPackage() suggests more than one launch context exists.",
      "Behaviour of verifyCallingPkg() on mismatch is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28617": {
     "cve": "CVE-2026-28617",
     "summary": "On SDK V and later, WifiNetworkSuggestionsManager now clears the vendor data from every WifiConfiguration in an incoming suggestion batch before the suggestions are processed. Per the commit message, this keeps an app from putting a deeply nested PersistableBundle into persisted suggestion data.",
     "component": "platform/packages/modules/Wifi, WifiNetworkSuggestionsManager (network suggestion add path)",
     "weakness": "Missing input sanitization of caller-supplied PersistableBundle vendor data. Its nesting depth is not bounded during serialization.",
     "root_cause": "Per the commit message, XmlUtil.writeOuiKeyedDataToXml and readPersistableBundleFromBytes serialize and deserialize PersistableBundle data without bounding its depth. WifiNetworkSuggestionsManager accepted WifiNetworkSuggestion objects whose WifiConfiguration carried vendor data, even though there is no API for an app to set it ('we never allow to set it'). The commit calls the result a persistent DoS through a PersistableBundle depth bomb.",
     "fix_analysis": [
      "service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java, suggestion add path. The hunk sits right after the 'bad wifi suggestion from app' check that returns WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED. A new loop, guarded by SdkLevel.isAtLeastV(), calls wns.wifiConfiguration.setVendorData(Collections.emptyList()) on every suggestion in networkSuggestions. It runs before the existing loop that calls convertLegacyFieldsToSecurityParamsIfNeeded and WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary.",
      "Adds import java.util.Collections.",
      "Net effect: suggestions added through this path no longer carry vendor data into later processing. This commit does not modify the XmlUtil routines."
     ],
     "completeness": [
      "Covers only the network-suggestion add path, and only on V and later (the SdkLevel guard skips the loop on earlier SDKs).",
      "Does not add depth bounding to XmlUtil.writeOuiKeyedDataToXml or readPersistableBundleFromBytes, which the commit message names as the unbounded serialization. This diff does not change any other path that uses those helpers.",
      "The diff adds no cleanup of suggestion data that is already persisted. It only acts on suggestions at add time.",
      "The commit message lists two Cherrypick-From sources, but the diff is a single 7-line change to one file. The material does not show what the second upstream change contained."
     ],
     "behavior_changes": [
      "On SDK V and later, vendor data on WifiConfiguration objects inside added network suggestions is silently replaced with an empty list. No error code is returned.",
      "No change on pre-V SDK levels, because of the SdkLevel.isAtLeastV() guard."
     ],
     "preconditions": [
      "A caller reaches the WifiNetworkSuggestionsManager add path with a suggestion batch that passes the preceding 'bad wifi suggestion from app' validation.",
      "A WifiConfiguration inside a suggestion carries vendor data. The commit says there is no API for an app to set it.",
      "The new clearing runs only when SdkLevel.isAtLeastV() is true.",
      "Per the commit message, the data is serialized and deserialized through XmlUtil."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14. The per-major facts still show 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Same fix: commit 5dab8604e9c0 in android-security-15.0.0_r17, the same 7-line change (import plus guarded setVendorData loop). 8/8 lines present.",
      "16": "Same fix: commit 4d8225a6d03d in android-security-16.0.0_r8. 8/8 lines present.",
      "17": "Same fix: commit 9b1148745f6b in android-security-17.0.0_r1, an identical change at a shifted line offset. 8/8 lines present."
     },
     "backport_notes": [
      "Self-contained single-file change. It uses WifiConfiguration.setVendorData and com.android.modules.utils.build.SdkLevel, and SdkLevel is already imported in the file. Wifi is a mainline module, so the module version can matter as well as the platform release (general platform knowledge).",
      "Placement: insert the loop after the batch validation that returns STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED and before the convertLegacyFieldsToSecurityParamsIfNeeded loop."
     ],
     "confidence": "medium",
     "unknowns": [
      "The name of the enclosing method; the hunk header does not show it.",
      "Whether XmlUtil.writeOuiKeyedDataToXml or readPersistableBundleFromBytes got a separate depth-bounding fix.",
      "What the second Cherrypick-From commit contained.",
      "Whether other persistence paths that carry OuiKeyedData accept vendor data the caller controls.",
      "How and when persisted suggestion data is reloaded, which would explain why the DoS is persistent.",
      "Whether data persisted before the fix is handled anywhere."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28620": {
     "cve": "CVE-2026-28620",
     "summary": "Changes the intent-redirection URI grant logic in ActivityStarter and ActivityStartController so that, when an intent's creator UID differs from the launching UID, URI grants are only issued if both the caller's and the creator's checkGrantUriPermissionFromIntent calls yield grants, instead of using or merging whichever party's grants were available; the NeededUriGrants.merge helper is removed.",
     "component": "platform/frameworks/base, WindowManager activity start / UriGrantsManager (Intent Redirection Hardening)",
     "weakness": "Permissive authorization logic (OR instead of AND of two authorization results) leading to URI permission grants not authorized by both parties",
     "root_cause": "In the intent redirection hardening path, when an Intent carries a valid IntentCreatorToken whose creator UID differs from the resolved calling UID, the code computed two NeededUriGrants sets: intentGrants for the calling UID and creatorIntentGrants for the creator UID. The pre-fix code combined them permissively: if intentGrants was null it was replaced by creatorIntentGrants, otherwise creatorIntentGrants was merged (union of URIs via NeededUriGrants.merge) into intentGrants. The result was that URI grants could be issued to the target activity based on either party's grant result, so one party's lack of grant authorization did not block the grant. The same pattern existed in three places: ActivityStarter.java under the contentUriPermissionApis flag, ActivityStarter.java in the non-flag else branch, and the multi-intent loop in ActivityStartController.java.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java (flagged branch, android.security.Flags.contentUriPermissionApis() true, around line 650): after computing creatorIntentGrants for intentCreatorUid, replaces 'if (intentGrants == null) intentGrants = creatorIntentGrants; else intentGrants.merge(creatorIntentGrants);' with 'if (intentGrants == null || creatorIntentGrants == null) intentGrants = null;'. Only the caller's intentGrants are used, and only when the creator check also returned a non-null set; otherwise no grants are made.",
      "services/core/java/com/android/server/wm/ActivityStarter.java (non-flag else branch, guarded by 'intentCreatorUid != DEFAULT_INTENT_CREATOR_UID && intentGrants != null', around line 676): same replacement, so a null creatorIntentGrants now clears intentGrants instead of leaving the caller's grants intact.",
      "services/core/java/com/android/server/wm/ActivityStartController.java (loop over intents[] in the multi-activity start path, around line 497): same replacement after computing creatorIntentGrants for creatorUid.",
      "services/core/java/com/android/server/uri/NeededUriGrants.java: removes the public merge(NeededUriGrants other) method (union of uris with a Slog.wtf on mismatched targetPkg/targetUid/flags), since no caller remains. The SecurityException handling (logAndThrowExceptionForIntentRedirect with INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION) is unchanged."
     ],
     "completeness": [
      "All three sites in the material that combined caller and creator grants are patched (two in ActivityStarter.java covering both the flagged and non-flagged paths, one in ActivityStartController.java), and the merge helper is removed so the old union pattern cannot be reused.",
      "The new logic is a gate, not an intersection: when both results are non-null, the caller's intentGrants set is granted as-is; the URIs in creatorIntentGrants are not compared against it. The commit message says the change maintains compatibility with the pre-hardening workflow.",
      "The fix only applies when IntentCreatorToken.isValid(intent) and the creator UID differs from the calling UID; intents without a valid creator token follow the unchanged single-UID path.",
      "NeededUriGrants.java keeps now-unused imports (java.util.Objects, android.util.Slog) per the context; cosmetic only."
     ],
     "behavior_changes": [
      "For redirected intents (creator UID != calling UID), if either checkGrantUriPermissionFromIntent call returns null, the target activity receives no URI grants at all; previously it received the non-null party's grants or the union of both.",
      "URIs that only the creator's check produced are no longer granted; only the caller's computed set is granted, and only when the creator's check also succeeded with a non-null set.",
      "NeededUriGrants.merge(NeededUriGrants) no longer exists; any out-of-tree caller would fail to compile.",
      "SecurityException behavior is unchanged: a creator-side failure still goes through logAndThrowExceptionForIntentRedirect; a caller-side failure in ActivityStartController still returns START_CANCELED."
     ],
     "preconditions": [
      "An activity start whose Intent carries a valid IntentCreatorToken (IntentCreatorToken.isValid(intent)) with getCreatorUid() different from the resolved calling UID, i.e. an intent created by one app and launched by another.",
      "The intent resolves to an ActivityInfo (activityInfo/aInfo non-null) and carries content URIs with grant flags so that checkGrantUriPermissionFromIntent produces NeededUriGrants.",
      "Reached via the activity resolution path in ActivityStarter.java (both the android.security.Flags.contentUriPermissionApis() branch and the else branch) or the multi-intent start loop in ActivityStartController.java."
     ],
     "per_major": {
      "14": "not affected per the bulletin (not listed). android-security-14.0.0_r29 shows 1/8 lines of the patched region, the same count as on the fixed 16 and 17 tags, so the count does not show whether the affected code exists here.",
      "15": "not affected per the bulletin (not listed). android-security-15.0.0_r17 shows 1/8 lines of the patched region, the same count as on the fixed 16 and 17 tags, so the count does not show whether the affected code exists here.",
      "16": "resolved: same fix, commit 353a1fa497d3 in android-security-16.0.0_r8 (cherry-pick of two internal changes, Change-Id Ica84e8733595bd2dced557bade0811feacbded19), touching NeededUriGrants.java, ActivityStartController.java and ActivityStarter.java.",
      "17": "resolved: same fix, commit 8df772fb59b4 in android-security-17.0.0_r1; identical logic, only surrounding context differs (activityInfo.getUid()/aInfo.getUid() instead of applicationInfo.uid, shifted line numbers)."
     },
     "backport_notes": [
      "The fix presupposes the Intent Redirection Hardening code (IntentCreatorToken, intentCreatorUid/creatorUid, logAndThrowExceptionForIntentRedirect, and the creatorIntentGrants computation) already exists in ActivityStarter.java and ActivityStartController.java. Make sure all three sites are changed, including the non-flag else branch in ActivityStarter.java, before removing NeededUriGrants.merge, otherwise the build breaks."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the semantics of a null return from UriGrantsManagerInternal.checkGrantUriPermissionFromIntent versus a thrown SecurityException, so the exact scenario in which the old OR logic granted unauthorized URIs is not shown.",
      "The two internal cherry-picked changes referenced in the commit message are not provided separately; only the squashed AOSP diff is available.",
      "No regression test is included (Test: manual exploit-app verification), so the exact exploit scenario is not shown.",
      "Whether the Intent Redirection Hardening grant code (creatorIntentGrants computation) exists on Android 14 and 15 is not shown; the bulletin simply does not list those versions."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28623": {
     "cve": "CVE-2026-28623",
     "summary": "Stops the device Bluetooth MAC address from being serialized in BleCsRangingCapabilities and BleRssiRangingCapabilities parcels. The Parcel constructors now always produce the placeholder \"00:00:00:00:00:00\", while the builder/constructor path used inside the ranging service keeps the real address.",
     "component": "platform/packages/modules/Uwb, ranging framework (android.ranging.ble.cs / android.ranging.ble.rssi Parcelables)",
     "weakness": "Information disclosure: a sensitive device identifier (the Bluetooth MAC address) was written into a Parcelable that crosses binder to client apps",
     "root_cause": "BleCsRangingCapabilities.writeToParcel wrote mBluetoothAddress with dest.writeString, and the private Parcel constructor read it back with in.readString. BleRssiRangingCapabilities did the same. These objects are transacted over binder, so an app receiving them could read the device's real Bluetooth address through getBluetoothAddress(). The commit message describes this as a mac address leak to user apps.",
     "fix_analysis": [
      "ranging/framework/java/android/ranging/ble/cs/BleCsRangingCapabilities.java: adds DEFAULT_MAC_ADDRESS = \"00:00:00:00:00:00\". writeToParcel no longer writes mBluetoothAddress; only the supported security levels list is written. The Parcel constructor BleCsRangingCapabilities(Parcel in) sets mBluetoothAddress = DEFAULT_MAC_ADDRESS instead of calling in.readString(). The Builder-based constructor is unchanged, so the service-side object still holds the real address.",
      "ranging/framework/java/android/ranging/ble/rssi/BleRssiRangingCapabilities.java: adds the same DEFAULT_MAC_ADDRESS constant. writeToParcel now has an empty body, and the protected Parcel constructor sets mBluetoothAddress = DEFAULT_MAC_ADDRESS. getBluetoothAddress() gains @NonNull and a javadoc saying it is for internal use and that clients should use BluetoothAdapter#getAddress(). The String constructor is unchanged for internal use.",
      "ranging/framework/tests/src/android/ranging/RangingFrameworkTest.java: adds testBleCsRangingCapabilitiesParcel and testBleRssiRangingCapabilitiesParcel. Each asserts the real address before parceling and the fake address after CREATOR.createFromParcel."
     ],
     "completeness": [
      "Both classes named in the commit (CS and RSSI capabilities) are fixed. The write and read sides change consistently, so the parcel layout stays self-consistent.",
      "The address is masked only at the parcel boundary. In-process objects built by the service still carry the real address, which the commit message says is intentional. The material does not show any other service-to-app channel that might carry the address, and this commit does not touch one."
     ],
     "behavior_changes": [
      "Apps that unparcel BleCsRangingCapabilities or BleRssiRangingCapabilities now always get \"00:00:00:00:00:00\" from getBluetoothAddress().",
      "The parcel wire format changed: neither class writes or reads the trailing address string any more, and BleRssiRangingCapabilities.writeToParcel now writes nothing.",
      "getBluetoothAddress() on BleRssiRangingCapabilities is now annotated @NonNull and documented as internal-only."
     ],
     "preconditions": [
      "An app obtains ranging capabilities from the ranging service such that a BleCsRangingCapabilities or BleRssiRangingCapabilities instance is parceled across binder to the app process.",
      "BleCsRangingCapabilities is gated by @FlaggedApi(Flags.FLAG_RANGING_CS_ENABLED), and BleRssiRangingCapabilities is @hide. The material does not show which binder methods deliver these objects or what permissions those methods require."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. The patched files are absent at android-security-14.0.0_r29 (code absent).",
      "15": "Not affected per the bulletin. The patched files are absent at android-security-15.0.0_r17 (code absent).",
      "16": "Same fix: commit 4a9740318035 in android-security-16.0.0_r8 (cherry-pick of c8aed37c3cd8), resolved by bug_id. 7 of 8 lines of the patched region are present.",
      "17": "Same fix: commit 5dbf65f08b16 in android-security-17.0.0_r1, resolved by bug_id. The source-file diff is identical; only the test file base and hunk offsets differ. 7 of 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Google shipped this as a single commit per branch, with identical source diffs on 16 and 17. The parcel writer and reader change together, so both sides of the binder must run the patched classes."
     ],
     "confidence": "high",
     "unknowns": [
      "Which ranging service binder calls return these capability objects, and what caller permissions apply.",
      "Whether the Bluetooth address reaches apps through any other path in the ranging stack.",
      "Whether BleRssiRangingCapabilities.toString() includes mBluetoothAddress (the diff context cuts off).",
      "Why the resolver reports 7 of 8 lines of the patched region present on 16 and 17.",
      "Whether the patch applies cleanly to customized fork trees."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28624": {
     "cve": "CVE-2026-28624",
     "summary": "Removes the EXTRA_DATA pass-through from ConfirmDeviceCredentialActivity, so the activity no longer echoes a caller-supplied Intent back as its RESULT_OK result, and makes FingerprintSettings keep the gatekeeper password handle in its own state instead of reading it from the returned Intent.",
     "component": "platform/packages/apps/Settings, password/ConfirmDeviceCredentialActivity and biometrics/fingerprint/FingerprintSettings (mandatory biometrics / identity check flow)",
     "weakness": "Untrusted Intent pass-through: an Intent taken from the launching intent (EXTRA_DATA) was returned unmodified as the activity result and then trusted by the caller as the source of a gatekeeper password handle (insufficient validation of externally supplied data).",
     "root_cause": "ConfirmDeviceCredentialActivity read a Parcelable Intent from its launching intent (intent.getParcelableExtra(EXTRA_DATA, Intent.class)) into mIntentData. On successful authentication its callback called setResult(Activity.RESULT_OK, mIntentData), returning whatever Intent the launcher supplied. FingerprintSettings used this channel to pass the credential result Intent (which contains the gatekeeper password handle) through the biometric prompt via Utils.launchBiometricPromptForMandatoryBiometrics(..., data). On BIOMETRIC_AUTH_REQUEST RESULT_OK it called handleAuthenticationSuccessful(data), which extracted the gatekeeper password handle from the returned Intent and used it for requestGatekeeperHat / addFirstFingerprint. The activity therefore echoed a caller-controlled Intent as its result, and the fingerprint settings flow trusted the content of that returned Intent as authentication state.",
     "fix_analysis": [
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: deletes the EXTRA_DATA constant and the mIntentData field. onCreate no longer reads the EXTRA_DATA extra, and the authentication success callback now calls setResult(Activity.RESULT_OK) with no result Intent.",
      "src/com/android/settings/Utils.java: removes the launchBiometricPromptForMandatoryBiometrics(Fragment, int, int, boolean, Intent data) overload and the EXTRA_DATA static import. getIntentForBiometricAuthentication(Resources, int, boolean) loses its data parameter and no longer calls intent.putExtra(EXTRA_DATA, data). The remaining Fragment and Activity launch helpers are updated to the new signature.",
      "src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java: adds a long mGkPwHandle field. It is saved under KEY_GK_PW_HANDLE (\"gk_pw_handle\") in onSaveInstanceState and restored from savedInstanceState. In onActivityResult for the credential result, when BiometricUtils.containsGatekeeperPasswordHandle(data) is true, it stores mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data) and then launches the biometric prompt without data. The NOT_ACTIVE path calls handleAuthenticationSuccessful(mGkPwHandle).",
      "FingerprintSettings.java onActivityResult, BIOMETRIC_AUTH_REQUEST branch: on RESULT_OK it no longer reads the returned Intent. If mGkPwHandle == 0L it logs \"Gatekeeper password not set.\"; otherwise it calls handleAuthenticationSuccessful(mGkPwHandle).",
      "FingerprintSettings.java handleAuthenticationSuccessful: the signature changes from (Intent data) to (long gkPwHandle). addFirstFingerprint(gkPwHandle), provider.requestGatekeeperHat(gkPwHandle, challenge, mUserId) and provider.removeGatekeeperPasswordHandle(gkPwHandle) now use the locally held handle."
     ],
     "completeness": [
      "The fix removes the echo channel at the activity instead of validating it: the success setResult changed in the diff no longer attaches any Intent. This covers launchers of ConfirmDeviceCredentialActivity and InternalActivity on that path, not only the Settings-internal mandatory biometrics flow.",
      "The producer of EXTRA_DATA (Utils.getIntentForBiometricAuthentication, which had no other callers per index search) and the consumer of the returned data (FingerprintSettings) are updated in the same commit. Removing the public EXTRA_DATA constant and the Utils overload would cause any remaining in-tree user to fail to compile.",
      "The mGkPwHandle == 0L guard is only in the BIOMETRIC_AUTH_REQUEST branch. The NOT_ACTIVE path passes mGkPwHandle right after setting it inside the containsGatekeeperPasswordHandle(data) check.",
      "The diff does not clear mGkPwHandle after use in handleAuthenticationSuccessful, and the value stays in saved instance state. This is a state hygiene point; it does not reintroduce the external channel."
     ],
     "behavior_changes": [
      "The ConfirmDeviceCredentialActivity success result now carries no data Intent (setResult(RESULT_OK) only). A launcher that relied on getting EXTRA_DATA back no longer receives it.",
      "Utils.launchBiometricPromptForMandatoryBiometrics(Fragment, int, int, boolean, Intent) is removed (API change inside Settings).",
      "FingerprintSettings saves a new long \"gk_pw_handle\" in its instance state. If the biometric prompt returns RESULT_OK with no stored handle, it logs \"Gatekeeper password not set.\" and skips enrollment and HAT generation."
     ],
     "preconditions": [
      "ConfirmDeviceCredentialActivity (or InternalActivity) launched with an EXTRA_DATA Parcelable Intent, followed by successful authentication, which returned that Intent to the launcher. The material does not show whether the base activity is exported.",
      "The vulnerable FingerprintSettings consumer path needed the mandatory biometrics / identity check flow: credential confirmation, then the biometric prompt launched for BIOMETRIC_AUTH_REQUEST, whose RESULT_OK Intent was trusted for the gatekeeper password handle."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29 only 3/8 lines of the patched region are present, which suggests the EXTRA_DATA / mandatory biometrics code is largely absent.",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17 only 3/8 lines of the patched region are present, which suggests the EXTRA_DATA pass-through is largely absent.",
      "16": "Same fix: commit 192ccfdcab3a in android-security-16.0.0_r8 (resolved by bug_id), covering the three files described above.",
      "17": "Same fix: commit d800aed5a84f in android-security-17.0.0_r1 (resolved by bug_id). The changes are the same; only the surrounding code differs: on 17, getIntentForBiometricAuthentication has no mandatoryBiometrics flag check, and the FingerprintSettings branch has no IdentityCheckBiometricErrorDialog else-if."
     },
     "backport_notes": [
      "The FingerprintSettings change needs long-based GatekeeperPasswordProvider methods: requestGatekeeperHat(long, long, int) and removeGatekeeperPasswordHandle(long). On Android 16 the index shows a test calling requestGatekeeperHat(long, long, int) (per index search). The removeGatekeeperPasswordHandle(long) overload is only implied by the patch. Check both on any target before porting.",
      "Forks that added other callers of the removed Utils.launchBiometricPromptForMandatoryBiometrics(..., Intent data) overload must change those callers to keep their own state, because the EXTRA_DATA round trip no longer exists."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show whether ConfirmDeviceCredentialActivity (as opposed to InternalActivity) is exported and reachable from third-party apps.",
      "Neither the bulletin nor the diff describes the exact privilege gained. Only the removal of the pass-through is evident.",
      "Whether the FingerprintSettings path that trusted the result was itself the exploited path, or was only hardened alongside the activity change.",
      "Whether ConfirmDeviceCredentialActivity returns any other launcher-supplied extras on other result paths. The diff context only shows the success setResult call that was changed."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28626": {
     "cve": "CVE-2026-28626",
     "summary": "SetupPassthroughActivity in LiveTv now checks, on SDK 34+ and via getLaunchedFromPackage(), that it was launched by CommonConstants.BASE_PACKAGE before forwarding the embedded setup Intent. If the check fails, it logs a warning and finishes without forwarding.",
     "component": "platform/packages/apps/TV (LiveTv), SetupPassthroughActivity",
     "weakness": "Intent redirection due to insufficient caller verification",
     "root_cause": "SetupPassthroughActivity takes a setup Intent from its own incoming extras (InputSetupActionUtils.removeSetupIntent(extras); setupIntent.putExtras(extras)) and forwards it. The new log message ('Not forwarding intent') implies the forwarding. Before the fix, the only caller-related check visible in the diff compared getCallingActivity()'s package with CommonConstants.BASE_PACKAGE, and the body of that branch is not shown. The commit title calls this an Intent redirection issue, and the bulletin classifies it as EoP. The material does not explain why the getCallingActivity() check was insufficient.",
     "fix_analysis": [
      "src/com/android/tv/SetupPassthroughActivity.java, in the forwarding try block just before the existing getCallingActivity() check: when Build.VERSION.SDK_INT >= 34 (commented 'Android 14(U)'), call getLaunchedFromPackage() and require it to be non-null and equal to CommonConstants.BASE_PACKAGE. Otherwise log a warning ('Calling package ... is not trusted. Not forwarding intent.'), call finish() and return, so the setup Intent is not forwarded.",
      "The existing getCallingActivity() check is kept and runs after the new check.",
      "The commit adds import android.os.Build for the SDK gate. It is a single commit touching one file with 12 added lines, and the diff is identical on all branches."
     ],
     "completeness": [
      "The new check is gated on Build.VERSION.SDK_INT >= 34. Below SDK 34 it is skipped and only the existing getCallingActivity() logic applies. Android 14 and later are API 34+ (general platform knowledge), so the gate is met on all bulletin-affected versions.",
      "Trust is based only on package-name equality with CommonConstants.BASE_PACKAGE. No signature check is added.",
      "The material does not show the branch after the existing getCallingActivity() check, the rest of the activity, or whether other code paths in this activity consume the incoming extras."
     ],
     "behavior_changes": [
      "On SDK 34+, if SetupPassthroughActivity is launched by any package other than CommonConstants.BASE_PACKAGE, or the launching package is null, it now logs a warning and finishes immediately without forwarding the setup Intent.",
      "No behavior change on SDK < 34."
     ],
     "preconditions": [
      "The caller must be able to reach SetupPassthroughActivity with an Intent whose extras carry a setup Intent, as processed by InputSetupActionUtils.",
      "The device runs LiveTv (platform/packages/apps/TV). The bulletin lists Android 14, 15, 16, 16-qpr2, 17 and 17-next as affected."
     ],
     "per_major": {
      "14": "same fix: commit 738d2cc1f03e in android-security-14.0.0_r29, identical diff (same blob indices 2a4a556f..87a66a0e), 8/8 patched lines present.",
      "15": "same fix: commit f23429d7cad6 in android-security-15.0.0_r17, identical diff, 8/8 patched lines present.",
      "16": "same fix: commit 856a260c2504 in android-security-16.0.0_r8, identical diff, 8/8 patched lines present.",
      "17": "same fix: commit 25c98c910675 in android-security-17.0.0_r1, identical diff, 8/8 patched lines present."
     },
     "backport_notes": [
      "This is a single self-contained commit. The diffs show the same pre- and post-change blob hashes on 14 through 17, so it should apply cleanly. Activity.getLaunchedFromPackage() was added in API 34 (general platform knowledge), and the fix guards it with an SDK_INT >= 34 check, so forks must keep the gate and compile against SDK 34+."
     ],
     "confidence": "high",
     "unknowns": [
      "What the existing branch does when getCallingActivity() is null or not from BASE_PACKAGE (the diff context is truncated), and how the setup Intent is ultimately started.",
      "Which method contains the forwarding code, and whether it is the only forwarding path.",
      "Why the getCallingActivity() check was insufficient (bug 332883816 is not described in the material).",
      "Whether SetupPassthroughActivity is exported in the manifest."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28631": {
     "cve": "CVE-2026-28631",
     "summary": "IntentForwarderActivity now calls getWindow().setHideOverlayWindows(true) right after setContentView(R.layout.miniresolver), so other apps' overlay windows are hidden while the cross-profile mini resolver consent dialog is shown.",
     "component": "platform/frameworks/base, core/java/com/android/internal/app/IntentForwarderActivity (cross-profile intent forwarding mini resolver)",
     "weakness": "Missing overlay-window suppression on a security-relevant consent UI (UI layer / overlay restriction not applied, tapjacking class)",
     "root_cause": "IntentForwarderActivity.buildMiniResolver (inline in the forwarding method on Android 14) inflated R.layout.miniresolver without asking for overlay windows to be hidden. This layout is the dialog that asks the user to confirm opening an intent in another profile (for example 'open in personal' for telephony/text-message intents, per the surrounding code). As a result, windows of other applications could be drawn over the consent dialog and its open/cancel buttons.",
     "fix_analysis": [
      "Commit de08a320e58b 'Set hide overlays on mini resolver': in core/java/com/android/internal/app/IntentForwarderActivity.java, buildMiniResolver adds getWindow().setHideOverlayWindows(true) right after setContentView(layoutId). This one line is the only functional change for the CVE.",
      "Commit 71d6dc109528 'Fix & speed up IntentForwarderActivityTest' (linked by the same bug 461500706) refactors IntentForwarderActivity so the test can inject behavior. onCreate now gets mExecutorService from mInjector.getExecutorService(), and isDeviceProvisioned() delegates to mInjector.isDeviceProvisioned(). The Injector interface gains getExecutorService() and isDeviceProvisioned(). The default production injector returns Executors.newSingleThreadExecutor() and does the same Settings.Global.DEVICE_PROVISIONED lookup as before, so production behavior is unchanged.",
      "Same commit, test side: core/tests/coretests/AndroidManifest.xml adds the android.permission.HIDE_OVERLAY_WINDOWS uses-permission because, per the commit message, IntentForwarderActivity crashes in the test when it calls setHideOverlayWindows(). core/tests/coretests/Android.bp removes IntentForwarderActivityTest from the exclude_filters of FrameworksCoreTests_internal_app. IntentForwarderActivityTest.java drops the 30-second awaitTermination wait, uses a synchronous AbstractExecutorService and an injected isDeviceProvisioned() instead of writing Settings.Global, and fixes assertions that were backwards (assertNull where assertNotNull was meant)."
     ],
     "completeness": [
      "The fix covers one window: the mini resolver built by buildMiniResolver (all callers of that helper on 15/16, and the single inline site on 14). UI that IntentForwarderActivity shows through other components, such as the full ResolverActivity referenced by RESOLVER_COMPONENT_NAME, is outside this change. The material does not show whether those windows already hide overlays.",
      "The flag is set right after setContentView with no condition, so it applies to every flow that goes through buildMiniResolver. The material explicitly shows the telephony/text-message 'open in personal' flow.",
      "The test-refactor commit adds no protection. It only makes the executor and the device-provisioned lookup injectable and re-enables the test."
     ],
     "behavior_changes": [
      "While the mini resolver dialog is shown, overlay windows from other apps are hidden over it (effect of Window.setHideOverlayWindows(true)).",
      "Per the commit message, calling setHideOverlayWindows() without android.permission.HIDE_OVERLAY_WINDOWS makes the activity crash in the test, which is why the coretests manifest now declares that permission.",
      "No change to which intents are forwarded, how they are sanitized, or when the disclosure is skipped."
     ],
     "preconditions": [
      "IntentForwarderActivity is started through one of its forwarding components (FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE) and reaches a path that shows the mini resolver consent UI, for example the 'open in personal' flow for telephony/text-message intents, which calls buildMiniResolver.",
      "Another window has to be drawn on top of that dialog; the fix now suppresses such windows."
     ],
     "per_major": {
      "14": "Adapted fix: commit cb471de52128 in android-security-14.0.0_r29 adds getWindow().setHideOverlayWindows(true) after setContentView(layoutId), inline in the forwarding method (the diff shows no buildMiniResolver helper on 14). 4/4 lines of the patched region present.",
      "15": "Same fix: commit 8e66b48894f7 in android-security-15.0.0_r17 adds the line in buildMiniResolver. 4/4 lines present.",
      "16": "Same fix: commit de08a320e58b in android-security-16.0.0_r8 adds the line in buildMiniResolver, plus test-refactor commit 71d6dc109528. 8/8 lines present.",
      "17": "Resolved by change_id in android-security-17.0.0_r1. Only the test-refactor commit 323c8b968a88 is shown, with 8/8 lines of the patched region present. The material does not directly show the hide-overlay line on 17."
     },
     "backport_notes": [
      "The security fix is the single setHideOverlayWindows(true) line placed right after setContentView(R.layout.miniresolver). On trees without buildMiniResolver (like Android 14), put it at the inline setContentView site. It does not depend on the test-refactor commit.",
      "If IntentForwarderActivityTest runs (for example, re-enabled as in 71d6dc109528) against code that has the hide-overlay line, the coretests manifest needs android.permission.HIDE_OVERLAY_WINDOWS. Per the commit message, the activity crashes in the test without it."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not describe the concrete attack scenario or which overlay-based interaction the bulletin's EoP rating refers to.",
      "Whether other IntentForwarderActivity UI paths (for example the hand-off to ResolverActivity) already hide overlay windows is not shown.",
      "For Android 17 only the test-refactor diff is shown. The '8/8 lines present' fact may refer to the refactor region, so presence of the hide-overlay line on 17 is not directly shown.",
      "Whether setHideOverlayWindows() runs without a permission failure in the production process is not shown in the material.",
      "Which flows other than the telephony/text-message 'open in personal' flow call buildMiniResolver is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28633": {
     "cve": "CVE-2026-28633",
     "summary": "In safe mode, VoiceInteractionManagerService no longer parses the recognition-service metadata of third-party (non-FLAG_SYSTEM) recognizers in RecognitionServiceInfo.getAvailableServices, nor the metadata of the already-configured recognizer. A large malicious metadata resource therefore cannot exhaust memory in system_server during a safe-mode boot through these paths.",
     "component": "platform/frameworks/base, services/voiceinteraction (VoiceInteractionManagerService / RecognitionServiceInfo)",
     "weakness": "Uncontrolled resource consumption: untrusted third-party XML metadata parsed in system_server, including in safe mode (DoS via OOM, per commit message)",
     "root_cause": "While setting up recognizers, VoiceInteractionManagerService called RecognitionServiceInfo.parseInfo on the metadata of every resolved recognition service, third-party ones included, and also on the currently configured recognizer. It did this even in safe mode, where third-party apps cannot be launched. Per the commit message and code comments, a malicious app can ship or update a very large recognition-service metadata XML whose loading can cause an OOM, crash the system and cause boot issues, and safe mode did not avoid that parsing.",
     "fix_analysis": [
      "RecognitionServiceInfo.java:getAvailableServices gains a boolean isSafeMode parameter. In the loop over resolveInfos, when isSafeMode is true and the service's applicationInfo.flags lacks ApplicationInfo.FLAG_SYSTEM, the code skips parseInfo. It adds a RecognitionServiceInfo built directly from serviceInfo with selectableAsDefault=true and parseError=\"\", then continues. System (FLAG_SYSTEM) services are still parsed in safe mode. A new import of android.content.pm.ApplicationInfo is added.",
      "VoiceInteractionManagerService.java (curRecognizer != null branch, around line 654 at 16.0.0_r8): the condition `if (recognizerInfo != null)` becomes `if (recognizerInfo != null && !mSafeMode)`. In safe mode, the configured recognizer's metadata is no longer passed to RecognitionServiceInfo.parseInfo, its isSelectableAsDefault check is skipped, and recognizerInfo is not cleared.",
      "VoiceInteractionManagerService.java (recognizer lookup, around line 918 at 16.0.0_r8): the call RecognitionServiceInfo.getAvailableServices(mContext, userHandle) now passes mSafeMode as the new third argument."
     ],
     "completeness": [
      "The fix only mitigates safe mode. On a normal boot, parseInfo still runs on the metadata of every resolved recognition service, third-party ones included, and on the configured recognizer. The diff adds no size limit or other hardening to parseInfo. The commit's stated goal is to make sure safe mode boots.",
      "In getAvailableServices, the skip applies only to services whose applicationInfo lacks FLAG_SYSTEM, so metadata of system-flagged recognizers is still parsed in safe mode. An updated system app keeps FLAG_SYSTEM (general platform knowledge), so the code would still parse its updated metadata in safe mode.",
      "In the curRecognizer path, the skip applies to every app in safe mode, not just third-party ones, so that path parses no metadata in safe mode.",
      "The fix covers both parse sites visible in the material (the getAvailableServices loop and the configured-recognizer check) for safe mode. The material does not show any other callers of RecognitionServiceInfo.parseInfo."
     ],
     "behavior_changes": [
      "In safe mode, third-party recognition services are listed as available with selectableAsDefault=true and an empty parse error, without their metadata being read. Per the code comment, they are still not auto-selected because only preinstalled recognizers are auto-selected.",
      "In safe mode, the already-configured recognizer is kept without checking its selectableAsDefault metadata and without logging parse errors.",
      "Signature change: RecognitionServiceInfo.getAvailableServices now requires a boolean isSafeMode argument. The class is package-private, so only callers in the same package are affected.",
      "No behavior change on normal (non-safe-mode) boots."
     ],
     "preconditions": [
      "An installed app that declares a recognition service with a recognition-service XML metadata resource large enough to cause excessive memory use when loaded in system_server (per commit message).",
      "VoiceInteractionManagerService sets up recognizer state for the user, which resolves recognition service providers and parses their metadata. Before the fix this also happened in safe mode, which is the case the fix addresses.",
      "The material does not show the exact resource sizes involved or whether a crash loop occurs outside safe mode."
     ],
     "per_major": {
      "14": "same fix: commit 20ecd72dd4c8 in android-security-14.0.0_r29. The RecognitionServiceInfo.java diff is identical (same blob hashes); the VoiceInteractionManagerService hunks are at lines 567 and 831. 8/8 patched lines present.",
      "15": "same fix: commit df4d44965279 in android-security-15.0.0_r17. Identical content; VoiceInteractionManagerService hunks at lines 648 and 912. 8/8 patched lines present.",
      "16": "same fix: commit a660d0c46482 in android-security-16.0.0_r8 (cherry-pick of a81b51b6a6e0). Hunks at lines 648 and 912. 8/8 patched lines present.",
      "17": "same fix: commit 4c5500a0d604 in android-security-17.0.0_r1. Identical content; VoiceInteractionManagerService hunks at lines 818 and 1101. 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit. It depends only on the existing mSafeMode field in VoiceInteractionManagerService and on FLAG_SYSTEM in ApplicationInfo. Forks that call RecognitionServiceInfo.getAvailableServices from other code in the same package must update those call sites for the new isSafeMode parameter."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the body of RecognitionServiceInfo.parseInfo, whether it applies any size bound, or the metadata size that triggers the OOM.",
      "The commit does not say whether a normal boot was also affected (crash loop); it only addresses recovery through safe mode.",
      "The material does not show whether the names of the methods containing the two VoiceInteractionManagerService hunks differ across branches."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28634": {
     "cve": "CVE-2026-28634",
     "summary": "Adds a check in PhoneInterfaceManager.handleUssdRequest that the calling user is associated with the target subscription ID, throwing SecurityException otherwise.",
     "component": "platform/packages/services/Telephony, PhoneInterfaceManager (ITelephony binder service, USSD handling)",
     "weakness": "Missing authorization check (caller user vs. subscription association)",
     "root_cause": "PhoneInterfaceManager.handleUssdRequest accepted a subId supplied by the caller. After its existing checks (enforceTelephonyFeatureWithException for FEATURE_TELEPHONY_RADIO_ACCESS on 15+, enforceCallPermission() on 14), it cleared the calling identity and went on to handle the USSD request for that subscription. It never checked that the subscription is associated with the calling user, so a caller running as a user not associated with the subscription could have USSD requests handled on it.",
     "fix_analysis": [
      "src/com/android/phone/PhoneInterfaceManager.java:handleUssdRequest: the new check sits after enforceTelephonyFeatureWithException(..., FEATURE_TELEPHONY_RADIO_ACCESS, \"handleUssdRequest\") (after enforceCallPermission() on 14) and before Binder.clearCallingIdentity(). It calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mApp, subId, Binder.getCallingUserHandle()) and throws SecurityException(\"The subscription <subId> is not associated with the calling user\") when that returns false. Because the check runs before clearCallingIdentity, Binder.getCallingUserHandle() reflects the real caller.",
      "tests/src/com/android/phone/PhoneInterfaceManagerTest.java: adds testHandleUssdRequest_SubscriptionNotAssociatedWithUser_ThrowsSecurityException and testHandleUssdRequest_ValidSubscription_Success. setUp now installs mSubscriptionManagerService as SubscriptionManagerService.sInstance via replaceInstance and stubs getActiveSubIdList(anyBoolean()) to return an empty array, so isSubscriptionAssociatedWithUser mocks can drive the new check. On 16 the test diff also removes the local @Mock Phone mPhone field and adds the needed imports."
     ],
     "completeness": [
      "The fix covers only the handleUssdRequest entry point in PhoneInterfaceManager. It adds a single user-association gate on the supplied subId.",
      "The new check runs before the existing SubscriptionManager.isValidSubscriptionId(subId) check inside the try block. The material does not show how checkSubscriptionAssociatedWithUser treats invalid or default subIds.",
      "This commit modifies no other ITelephony method that takes a subId. The material does not show whether those methods already have an equivalent check."
     ],
     "behavior_changes": [
      "handleUssdRequest now throws SecurityException to callers whose user is not associated with the given subscription, instead of going on to handle the USSD request.",
      "The new check runs before the subId validity check. A caller that used to reach the invalid-subId path may now get a SecurityException instead, depending on how checkSubscriptionAssociatedWithUser behaves."
     ],
     "preconditions": [
      "The caller reaches ITelephony.handleUssdRequest and passes the checks that already exist in that method (enforceCallPermission(), as shown in the Android 14 diff; enforceTelephonyFeatureWithException for FEATURE_TELEPHONY_RADIO_ACCESS on 15+).",
      "Multi-user context: the calling user is not one of the users associated with the targeted subscription, and the caller supplies that subscription's subId."
     ],
     "per_major": {
      "14": "adapted fix: the same 5-line check is inserted in handleUssdRequest, directly after enforceCallPermission(), because on 14 no enforceTelephonyFeatureWithException call precedes it. The test file is adapted with extra mocks and fields (AppOpsManager, AudioManager, SatelliteController, SetFlagsRule, UiccController, PinStorage). Commit 2a177c06ec48 in android-security-14.0.0_r29, 8/8 lines present.",
      "15": "same fix: identical source hunk after enforceTelephonyFeatureWithException. Test setUp also stubs HalVersion, getContext and DataNetworkController on the default phone. Commit 83d27ac3f32f in android-security-15.0.0_r17, 8/8 lines present.",
      "16": "same fix: commit 03cc6ee76027 in android-security-16.0.0_r8, 8/8 lines present.",
      "17": "same fix: identical source hunk. The test diff removes the mPhone and DataNetworkController mocks, reorders the AppOpsManager mock, and adds the setUp stubs and the two tests. Commit 5ef227b0a2f7 in android-security-17.0.0_r1, 8/8 lines present."
     },
     "backport_notes": [
      "The source hunk is self-contained apart from calling TelephonyPermissions.checkSubscriptionAssociatedWithUser(mApp, subId, Binder.getCallingUserHandle()). Google's fixes use this call on 14 through 17. Where handleUssdRequest has no preceding enforceTelephonyFeatureWithException call, insert the check right after enforceCallPermission() and before Binder.clearCallingIdentity(), as Google did on 14.",
      "The new tests use replaceInstance and SubscriptionManagerService.getActiveSubIdList(boolean). Forks lacking these will need to adapt the test hunk locally."
     ],
     "confidence": "high",
     "unknowns": [
      "How TelephonyPermissions.checkSubscriptionAssociatedWithUser behaves for invalid or default subscription IDs (its implementation is not in the material).",
      "Where TelephonyPermissions.checkSubscriptionAssociatedWithUser is defined and whether it exists on fork branches older than the shown security tags.",
      "Whether other checks (e.g. enforceCallPermission) precede the shown hunk on 15+; only the 14 diff shows that line.",
      "What concrete impact a USSD request from an unassociated user had, beyond the bulletin's EoP rating."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28638": {
     "cve": "CVE-2026-28638",
     "summary": "MediaProvider now keeps 'XMP_' (BOX_XMP) boxes in IsoInterface's flattened box list, and XmpDataParser now redacts XML elements whose attributes, not only whose tag names, carry sensitive EXIF fields. As a result, GPS data in ISO/MP4 XMP metadata is redacted for apps without ACCESS_MEDIA_LOCATION.",
     "component": "platform/packages/providers/MediaProvider, util location redaction (IsoInterface, XmpDataParser / XmpInterface)",
     "weakness": "Incomplete sanitization / redaction bypass (information disclosure): the parser dropped one container box type, and sensitive fields encoded as attributes were not matched.",
     "root_cause": "There were two independent gaps in the XMP location redaction path. (1) IsoInterface.parseNextBox adds only a whitelisted set of box types to the flattened list (BOX_META, BOX_HDLR, BOX_XYZ, BOX_LOCI, BOX_GPS, BOX_GPS0 and the XMP UUID box). BOX_XMP (0x584d505f, 'XMP_') was missing from that switch, so lookups by BOX_XMP found nothing, and XMP stored in an 'XMP_' box was never parsed or redacted. (2) XmpDataParser.createXmpInterface and XmpDataParser.getRedactionRanges treated a START_TAG as sensitive only when the element's own namespace was NS_EXIF and its name was in RedactionUtils.getsRedactedExifTags(). XMP can store the same fields as attributes on an element (for example rdf:Description exif:GPSLatitude=\"...\"). A code comment even notes that values may be in attributes or tags, but attributes were never inspected, so such GPS data survived redaction.",
     "fix_analysis": [
      "src/com/android/providers/media/util/IsoInterface.java:parseNextBox: adds `case BOX_XMP:` to the switch that selects boxes for `flatten.add(box)`, so 'XMP_' boxes become visible to BOX_XMP lookups. The unused ArrayDeque/Queue imports are removed, with no functional effect.",
      "src/com/android/providers/media/util/XmpDataParser.java: a new private isSensitiveTagOrAttribute() returns true in two cases: the current element is NS_EXIF with a name in RedactionUtils.getsRedactedExifTags(), or any attribute (mParser.getAttributeNamespace(i)/getAttributeName(i)) is NS_EXIF with a name in that set.",
      "XmpDataParser.createXmpInterface: the former `else if (NS_EXIF ... contains(name))` branch becomes a standalone `if (isSensitiveTagOrAttribute())` after the metadata else-if chain. An rdf:Description element therefore still passes its format/documentId/instanceId/originalDocumentId attribute values to the builder. If it is sensitive, it is then blanked with Arrays.fill ' ' in builder.mRedactedXmp, from its start offset to the first END_TAG with the same name.",
      "XmpDataParser.getRedactionRanges: the same check is substituted, so elements with sensitive attributes enter the redaction-range branch.",
      "Tests: adds tests/res/raw/gps_test_xmp.mp4 and XmpDataParserTest.testRedactXmp_RemovesExifGpsAttributes, which asserts that the redacted XMP contains neither exif:GPSLatitude nor exif:GPSLongitude."
     ],
     "completeness": [
      "Covers both reported flaws on the ISO path: BOX_XMP boxes are now flattened, and both createXmpInterface and getRedactionRanges check attributes as well as tag names.",
      "Attribute matching is limited to the NS_EXIF namespace and the RedactionUtils.getsRedactedExifTags() set. Attributes in other namespaces are not considered, which matches the existing tag-based rule.",
      "When a sensitive attribute is found, the enclosing element is redacted as a whole (for example a whole rdf:Description), including non-sensitive content inside it.",
      "In XmpDataParser, the end-of-element scan still stops at the first END_TAG whose name equals the start tag's name (name match, not depth tracking). This existed before and is not changed in the 15/16/17 commits. The Android 14 variant uses depth counting for the attribute case.",
      "The Android 15 code shows XmpData.extractXmpData reading only the first XMP UUID box and falling back to the first BOX_XMP. The Android 14 fix instead processes every XMP box through the new getBoxBytesList/getBoxRangesList. The Android 16 diff does not touch extractXmpData, and its body on 16 is not shown."
     ],
     "behavior_changes": [
      "The contents of 'XMP_' boxes are now kept in IsoInterface's flattened list and parsed for redaction. Before the fix they were skipped.",
      "Elements carrying EXIF GPS attributes (or other redacted-tag attributes) are now blanked with spaces in the redacted XMP and go through the redaction-range path. This can blank an entire rdf:Description element.",
      "For ISO files with attribute-style XMP, redaction may cover more bytes than before."
     ],
     "preconditions": [
      "An ISO base media file (for example MP4) with XMP metadata either in an 'XMP_' box, or with EXIF GPS fields encoded as XML attributes.",
      "The file is read through MediaProvider's redaction path (IsoInterface + XmpDataParser) by an app that should get location-redacted content. Per the commit message, this means an app without ACCESS_MEDIA_LOCATION.",
      "The exact entry points into XmpDataParser.getRedactionRanges/createXmpInterface are not shown in the material."
     ],
     "per_major": {
      "14": "Adapted fix (15168c2e6f84, android-security-14.0.0_r29). The code is in XmpInterface.java, not XmpDataParser.java, and the diff makes no change to a BOX_XMP flatten switch. The 14 fix: adds IsoInterface.getBoxRangesList/getBoxBytesList (by type and by UUID); makes XmpInterface.fromContainer process every XMP UUID box and every BOX_XMP through a new process() method; adds depth-tracked attribute-based redaction in process(). The test is added to XmpInterfaceTest.",
      "15": "Adapted fix (cb04f3531ff2, android-security-15.0.0_r17). It bundles the IsoInterface selective-flattening refactor, with BOX_XMP included in the switch. The refactor adds MAX_XMP_SIZE_BYTES, XMP_UUID and allocateBuffer, builds the flatten list during parseNextBox, and replaces the UUID-parameter getters with getBoxBytesForXmpUuid/getBoxRangesForXmpUuid. The commit also carries the identical XmpDataParser.isSensitiveTagOrAttribute change and updates extractXmpData to use the new getters.",
      "16": "This commit (4bdbc2fa9f53, android-security-16.0.0_r8): a one-line BOX_XMP case in IsoInterface plus isSensitiveTagOrAttribute in XmpDataParser.",
      "17": "Same fix (11009f3800f9, android-security-17.0.0_r1). The diff, including blob hashes, is identical to the Android 16 commit."
     },
     "backport_notes": [
      "The Android 16/17 IsoInterface hunk assumes the selective-flatten refactor is already present (a switch in parseNextBox that feeds the flattened list). The Android 15 commit shows Google carrying that refactor along where it was missing.",
      "Pick the variant that matches the branch's XMP class: XmpDataParser.java (15+) or XmpInterface.java (14). The Android 14 variant also iterates over all XMP boxes, which the 15+ variants do not."
     ],
     "confidence": "high",
     "unknowns": [
      "How IsoInterface on Android 14 builds its flattened list, and so why no BOX_XMP switch change was needed there, is not shown.",
      "When the BOX_XMP gap appeared on the 16 branch is not shown. The Android 15 diff suggests the selective-flatten refactor introduced the switch without BOX_XMP.",
      "Whether the single-box behavior of XmpData.extractXmpData is in scope on 15/16/17 is not shown. Its body on 16 and 17 is not included.",
      "The callers of XmpDataParser.getRedactionRanges/createXmpInterface and how redaction ranges are applied are not in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28642": {
     "cve": "CVE-2026-28642",
     "summary": "In ActivityStarter (frameworks/base WindowManager), right after the ActivityRecord is built, the fix clears r.mLaunchTaskBehind when the background activity launch (BAL) verdict blocks the start. Per the fix comment, this keeps a blocked launch from being made visible through the launch-behind path.",
     "component": "platform/frameworks/base, services/core WindowManager (ActivityStarter, background activity launch control)",
     "weakness": "Missing enforcement of an authorization decision on an alternate path (the BAL block was not applied to the launch-task-behind flag)",
     "root_cause": "ActivityStarter computed a BAL verdict for the start request (balVerdict, or balCode on Android 14), but did not reset the newly built ActivityRecord's mLaunchTaskBehind flag when that verdict was a block. Per the fix comment, a launch-behind record could therefore still be made visible even though the BAL check said the launch should be blocked.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java (around lines 1484-1510 at android-security-16.0.0_r8): right after ActivityRecord.Builder(...).build() and before mLastStartActivityRecord = r, a new check tests r.mLaunchTaskBehind && balVerdict.blocks().",
      "If both are true, it logs Slog.w(TAG, \"Disallowed launching behind for a background launch\") and sets r.mLaunchTaskBehind = false. Per the fix comment, this keeps the blocked launch from being made visible through launch-behind.",
      "This is one commit per branch. On Android 14 the condition is written as balCode == BAL_BLOCK instead of balVerdict.blocks()."
     ],
     "completeness": [
      "The fix covers only the record-creation path in ActivityStarter, and it clears mLaunchTaskBehind only when the BAL verdict is a block. Launches that BAL allows keep the launch-behind flag.",
      "The check applies only to the newly built ActivityRecord r. The diff does not change how mLaunchTaskBehind is used later or how ActivityOptions are parsed. The material does not show whether any other code path sets mLaunchTaskBehind on an existing record.",
      "The commit includes no test (Test: locally verified)."
     ],
     "behavior_changes": [
      "When BAL blocks a start whose record has mLaunchTaskBehind set, the launch-behind flag is now cleared and a warning is logged: 'Disallowed launching behind for a background launch'.",
      "The diff adds no new error code or exception."
     ],
     "preconditions": [
      "An activity start that produces an ActivityRecord with mLaunchTaskBehind set (likely through the launch-task-behind ActivityOption; the material does not show this).",
      "The BAL check for that start returns a block (balVerdict.blocks(), or balCode == BAL_BLOCK on Android 14)."
     ],
     "per_major": {
      "14": "adapted fix: commit d94f8b27bc84 in android-security-14.0.0_r29 inserts the same block with the condition balCode == BAL_BLOCK; 3/3 lines of the patched region present.",
      "15": "same fix: commit d98cb1490fc3 in android-security-15.0.0_r17, identical hunk using balVerdict.blocks(); 3/3 lines present.",
      "16": "same fix: commit 2b8a07bf7437 in android-security-16.0.0_r8 (this commit); 3/3 lines present.",
      "17": "not affected (not listed by the bulletin); the patched region is present (3/3 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The fix is a single hunk right after ActivityRecord.Builder(...).build() in ActivityStarter. The only adaptation is how BAL is represented: use balVerdict.blocks() where it exists, or balCode == BAL_BLOCK as in the Android 14 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how mLaunchTaskBehind is used downstream, which code made the blocked launch visible, or how a blocked start is handled afterwards. The visible effect is inferred only from the fix comment.",
      "The material does not show where mLaunchTaskBehind is set on the record (presumably from ActivityOptions).",
      "The name of the method enclosing the hunk is not shown.",
      "It is not shown whether the two Cherrypick-From internal commits contained more changes (for example tests) beyond this hunk."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28644": {
     "cve": "CVE-2026-28644",
     "summary": "ActivityTaskManagerService.startNextMatchingActivity() now forwards the original caller's identity (launchedFromUid/launchedFromPackage/launchedFromFeatureId) to the next resolved activity only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID. Otherwise the calling identity is reset to the intermediary's own uid and package.",
     "component": "platform/frameworks/base, WindowManager / ActivityTaskManagerService (activity start, startNextMatchingActivity)",
     "weakness": "Improper propagation of caller identity (caller-identity spoofing / confused deputy): no trust check before forwarding a privileged identity",
     "root_cause": "In services/core/java/com/android/server/wm/ActivityTaskManagerService.java, startNextMatchingActivity() always built the ActivityStarter for the next matching activity with setCallingUid(r.launchedFromUid), setCallingPackage(r.launchedFromPackage) and setCallingFeatureId(r.launchedFromFeatureId). Here r is the currently running (intermediary) activity. The start of the next activity therefore carried the identity of whichever app launched the intermediary, not the intermediary's own identity. Per the commit message, this let a malicious intermediary trampoline an implicit intent from a privileged app to a victim app and bypass permission checks (REQUEST_INSTALL_PACKAGES is the example given). Per the code comment, only realCallingPid/realCallingUid had previously been set to the calling activity, for the BAL fix b/230492947 and b/337726734. callingUid and callingPackage had not.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityTaskManagerService.java:startNextMatchingActivity(): the builder chain is split so the ActivityStarter is held in a local `starter`. The calls setCaller, setResolvedType, setActivityInfo, setResultTo, setResultWho, setRequestCode, setCallingPid(-1), setRealCallingPid(origCallingPid), setRealCallingUid(origCallingUid), setActivityOptions and setUserId are unchanged.",
      "Same function: `final int intermediaryUid = r.getUid();` is followed by `if (!UserHandle.isSameApp(intermediaryUid, r.launchedFromUid) && UserHandle.getAppId(intermediaryUid) != Process.SYSTEM_UID)`. In that case the identity is squashed with starter.setCallingUid(intermediaryUid).setCallingPackage(r.packageName), and setCallingFeatureId is not called. Otherwise (same app ID, or the intermediary's app ID is SYSTEM_UID) the previous behaviour is kept: setCallingUid(r.launchedFromUid).setCallingPackage(r.launchedFromPackage).setCallingFeatureId(r.launchedFromFeatureId). Then `starter.execute()` is called.",
      "core/java/android/app/Activity.java: the javadoc of startNextMatchingActivity(Intent, Bundle) gains a note. It says the propagated caller identity reflects the intermediary unless it shares the original caller's UID, and that receivers should validate the immediate caller.",
      "services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java: three new tests (differentAppId_propagatesIntermediary, sameAppId_propagatesOriginalCaller, systemUid_propagatesOriginalCaller) use a spied ActivityStarter and a mocked IPackageManager.queryIntentActivities. In services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java, ActivityBuilder gains setIntent(Intent) so the tests can build an ActivityRecord with a specific intent."
     ],
     "completeness": [
      "The fix covers the single identity-forwarding site in ActivityTaskManagerService.startNextMatchingActivity(). The decision is made on app ID (UserHandle.isSameApp / UserHandle.getAppId), so the same package in a different user profile is treated as trusted. Any intermediary whose app ID is SYSTEM_UID (the commit message names system trampolines like ChooserActivity) still forwards the original identity.",
      "In the squashed branch setCallingFeatureId is not called, while callingUid and callingPackage are set to the intermediary. realCallingPid/realCallingUid remain origCallingPid/origCallingUid as before, so the fix changes only the 'calling' identity, not the 'real calling' identity.",
      "The trust decision relies on r.launchedFromUid recorded on the intermediary ActivityRecord. The material does not show other activity-start paths that forward launchedFrom* identity, so none are judged here.",
      "The tests only exercise the three decision outcomes, with execute() stubbed. They do not verify downstream permission checks in the target."
     ],
     "behavior_changes": [
      "When an activity whose app ID differs from its launcher's (and is not SYSTEM_UID) calls Activity.startNextMatchingActivity(), the next activity is now started with the intermediary's uid and package as callingUid/callingPackage instead of the original launcher's identity. The launcher's feature ID is no longer forwarded in that case.",
      "Intermediaries with the same app ID as the launcher, and SYSTEM_UID intermediaries, keep the previous identity forwarding. The diff adds no new error codes or exceptions.",
      "The public API documentation for Activity.startNextMatchingActivity(Intent, Bundle) now states the identity rule."
     ],
     "preconditions": [
      "An app's activity must have been launched by another app (r.launchedFromUid/launchedFromPackage recorded on the ActivityRecord) and must then call Activity.startNextMatchingActivity(). That call reaches IActivityTaskManager.startNextMatchingActivity(mToken, intent, options) in ActivityTaskManagerService.",
      "The code path needs a valid activity token with a live process (r.app.getThread()) and an intent that resolves to a further matching activity (aInfo). Per the code context, options defaults to SafeActivityOptions when null.",
      "The identity squash applies only if the intermediary's app ID differs from that of r.launchedFromUid and is not SYSTEM_UID."
     ],
     "per_major": {
      "14": "Adapted fix: commit bb7413247455 in android-security-14.0.0_r29. The identity check is identical, but the builder chain has no .setUserId(userId) line and ends at .setActivityOptions(options). The test imports also differ (atLeast and when are already present). Patched region: 8/8 lines present.",
      "15": "Same fix: commit ba9c05f37043 in android-security-15.0.0_r17, with the identical ATMS change including .setUserId(userId). Patched region: 8/8 lines present.",
      "16": "Same fix: commit e95170c6378e in android-security-16.0.0_r8 (the analysed commit). Patched region: 8/8 lines present.",
      "17": "The per-major facts say the bulletin does not list 17 as affected, although the bulletin's affected list includes 17-next. At android-security-17.0.0_r1, 8/8 lines of the patched region are present."
     },
     "backport_notes": [
      "The core change is self-contained in startNextMatchingActivity(). It only needs ActivityStarter's setCallingUid/setCallingPackage/setCallingFeatureId builder setters, UserHandle.isSameApp/getAppId and Process.SYSTEM_UID. On branches without .setUserId(userId), use the Android 14 variant of the chain. The new tests also need the ActivityBuilder.setIntent() addition in WindowTestsBase.java and mockitoSession/spyStatic support in wmtests."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the concrete victim activity or permission check used in the reported attack, beyond the commit message's REQUEST_INSTALL_PACKAGES example.",
      "The commit lists two internal Cherrypick-From changes, but the material shows only one combined AOSP commit. Whether the second internal change contained anything beyond what is shown is unknown.",
      "The material does not show whether other privileged (non-SYSTEM_UID) system trampolines rely on identity forwarding through startNextMatchingActivity and are affected by the new squash.",
      "The ActivityStarter default value for callingFeatureId, and which receiving-side APIs expose the changed calling identity, are not shown.",
      "The bulletin's affected list includes 17-next, while the per-major facts treat 17 as not listed. The material does not resolve this."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28650": {
     "cve": "CVE-2026-28650",
     "summary": "WindowState.setHiddenWhileSuspended now evaluates the window's base type (getBaseType()) instead of mAttrs.type, so sub-windows whose top parent is a system alert window or toast are hidden/shown when the owning package is suspended/unsuspended.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/WindowState.java)",
     "weakness": "Incorrect type check (sub-window type checked instead of base/parent window type), leading to a bypass of the suspended-package overlay hiding policy",
     "root_cause": "setHiddenWhileSuspended() early-returned when !isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST. For a sub-window (for example TYPE_APPLICATION_PANEL) whose top parent is a system alert window such as TYPE_APPLICATION_OVERLAY, mAttrs.type is the sub-window type, so per the commit message isSystemAlertWindowType() did not match. The method then returned before setting mHiddenWhileSuspended and calling hide()/show(). As a result, when the owning package was suspended (RootWindowContainer.updateHiddenWhileSuspendedState calls setHiddenWhileSuspended on every window of the suspended packages, per index search), the sub-windows were not hidden. The sibling method setForceHideNonSystemOverlayWindowIfNeeded() already used getBaseType() for the same check (Android 16 context).",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowState.java:setHiddenWhileSuspended: adds `final int baseType = getBaseType();` and replaces both uses of mAttrs.type in the early-return condition with baseType, so the check `!isSystemAlertWindowType(baseType) && baseType != TYPE_TOAST` is evaluated against the base type. Per the commit message, this is the type of the top parent window. Sub-windows of a system alert window or toast therefore pass the gate, and the method sets mHiddenWhileSuspended and calls hide()/show() on them.",
      "services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java: adds testSubWindowHiddenWhenSuspended, which builds a TYPE_APPLICATION_OVERLAY parent with a TYPE_APPLICATION_PANEL sub-window and verifies that setHiddenWhileSuspended(true/false) calls hide()/show() on the sub-window."
     ],
     "completeness": [
      "The fix only changes the type check in setHiddenWhileSuspended; the rest of the method (mHiddenWhileSuspended state, hide/show calls) is unchanged.",
      "The exemption for windows whose owner can add internal system windows (mOwnerCanAddInternalSystemWindow) is preserved; such windows are still never hidden by this path.",
      "The sibling path setForceHideNonSystemOverlayWindowIfNeeded() in the same file already uses getBaseType() in the shown Android 16 context, so it is consistent with the fixed method and did not need a change.",
      "The added test covers a single level of sub-window only."
     ],
     "behavior_changes": [
      "Sub-windows (e.g. TYPE_APPLICATION_PANEL) whose top parent is a system alert window type or TYPE_TOAST are now hidden when the owning package becomes suspended and shown again when it is unsuspended.",
      "mHiddenWhileSuspended is now tracked for such sub-windows; previously the early return prevented it from being set for them.",
      "Top-level windows are expected to behave as before, assuming getBaseType() returns the window's own type when it has no parent. The material does not show this."
     ],
     "preconditions": [
      "A package owns a system alert window type window (e.g. TYPE_APPLICATION_OVERLAY) or TYPE_TOAST with one or more attached sub-windows.",
      "The window's owner cannot add internal system windows (mOwnerCanAddInternalSystemWindow is false).",
      "The package is suspended, which triggers RootWindowContainer.updateHiddenWhileSuspendedState -> WindowState.setHiddenWhileSuspended for each of its windows (per index search)."
     ],
     "per_major": {
      "14": "same fix: commit ce479a9a57bd in android-security-14.0.0_r29; identical WindowState.java change, test adapted to the createWindow(parent, type, name) helper",
      "15": "same fix: commit 8130b6057817 in android-security-15.0.0_r17; identical WindowState.java change, test adapted to the createWindow helper",
      "16": "same fix: commit b61562e1abe6 in android-security-16.0.0_r8; test uses newWindowBuilder().setParent()",
      "17": "same fix: commit 236258a3e304 in android-security-17.0.0_r1; identical WindowState.java change and newWindowBuilder-based test"
     },
     "backport_notes": [
      "Single-commit, two-line functional change. The only per-branch adaptation is the test helper (createWindow on 14/15, newWindowBuilder on 16/17). Google's fix commits call getBaseType() on every branch, and on 16 the sibling setForceHideNonSystemOverlayWindowIfNeeded already uses it."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the concrete security impact. It only has the commit message's statement that skipping hide/show 'will lead to security concerns' and the bulletin's EoP/High rating.",
      "The implementations of getBaseType() and isSystemAlertWindowType() are not included in the material. In particular, the material does not show how getBaseType() handles multi-level sub-windows or windows with no parent.",
      "Per-major facts report 6/7 lines of the patched region present at the 14, 15 and 16 tags (7/7 at 17); the material does not explain the missing line."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28652": {
     "cve": "CVE-2026-28652",
     "summary": "Adds enforceRangingPermission() to five out-of-band (OOB) ranging Binder entry points in RangingServiceImpl (oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed, registerOobSendDataListener). Before the fix these methods passed calls to RangingServiceManager without any permission check.",
     "component": "platform/packages/modules/Uwb, ranging service (com.android.server.ranging.RangingServiceImpl, IRangingAdapter.Stub)",
     "weakness": "missing permission check on Binder interface methods",
     "root_cause": "RangingServiceImpl extends IRangingAdapter.Stub and implements the Binder interface of the ranging service. In the shown region, addRawDevice, addOobDevice, removeDevice and stopRanging call enforceRangingPermission() before passing the call on to mRangingInjector.getRangingServiceManager(), and so does the method that calls RangingServiceManager.reconfigureInterval. The five OOB methods oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed and registerOobSendDataListener had no such check: they passed caller input (OobHandle, data, IOobSendDataListener) to RangingServiceManager without requiring the ranging permission. The bulletin classifies the result as information disclosure (ID), severity High.",
     "fix_analysis": [
      "ranging/service/java/com/android/server/ranging/RangingServiceImpl.java:oobDataReceived - enforceRangingPermission() is now the first statement, before the call to RangingServiceManager.oobDataReceived(oobHandle, data).",
      "RangingServiceImpl.java:deviceOobDisconnected, deviceOobReconnected, deviceOobClosed - the same enforceRangingPermission() call is added before each call to the matching RangingServiceManager method.",
      "RangingServiceImpl.java:registerOobSendDataListener - enforceRangingPermission() is added before RangingServiceManager.registerOobSendDataListener(oobSendDataListener).",
      "The fix reuses the enforceRangingPermission() helper that the other methods of the class already use. The diff adds no new helper, AIDL change or manifest change. It is a single commit with 5 insertions."
     ],
     "completeness": [
      "Covers all five OOB methods named in the commit message. After the fix, every method visible in the shown region of RangingServiceImpl (lines 74-132) calls enforceRangingPermission().",
      "Methods of RangingServiceImpl above line 74 are not shown, so the code cannot confirm that every IRangingAdapter method is now gated.",
      "The fix adds only a caller permission check. It does not validate the OobHandle or data payload, and it does not check that the OobHandle belongs to the calling uid. RangingServiceManager itself is unchanged."
     ],
     "behavior_changes": [
      "Callers without the permission checked by enforceRangingPermission() now fail at that check in the five OOB Binder methods, in the same way as in the other gated methods, and the call is no longer passed to RangingServiceManager. The body of the helper (and so the exact exception) is not shown.",
      "No change for callers that already hold the permission."
     ],
     "preconditions": [
      "The caller can obtain and call the IRangingAdapter Binder interface exposed by RangingServiceImpl (the Uwb module ranging service is present on the device).",
      "The caller does not hold the ranging permission checked by enforceRangingPermission(). Before the fix, the five OOB methods did not require it.",
      "For registerOobSendDataListener, a listener only gets something if the service actually sends OOB data. The material does not show this flow."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. Code absent: RangingServiceImpl.java does not exist at android-security-14.0.0_r29.",
      "15": "Not affected per the bulletin. Code absent: RangingServiceImpl.java does not exist at android-security-15.0.0_r17.",
      "16": "Same fix: commit 618d9adfcd65 in android-security-16.0.0_r8, a cherry-pick of internal changes 1a1f98ef and d9680b2f (Change-Id I9d2f34330258d00b4d19e57f330c58fdde021846). 8/8 lines of the patched region present.",
      "17": "Same fix: commit 3be00374c294 in android-security-17.0.0_r1. The hunk is identical but starts at a different line (113 instead of 102). 8/8 lines present."
     },
     "backport_notes": [
      "One file, five one-line insertions that reuse the existing enforceRangingPermission() helper. It should apply cleanly to any tree where RangingServiceImpl has these five OOB methods. Uwb is a mainline module (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of enforceRangingPermission() and the exact permission string it checks are not in the material.",
      "The material does not show which internal state RangingServiceManager.oobDataReceived and the deviceOob* methods change, or what data an IOobSendDataListener receives. The information-disclosure path stated by the bulletin cannot be traced from the diff alone.",
      "Whether the methods of RangingServiceImpl outside the shown region were already gated.",
      "What the second internal cherry-pick (d9680b2f) contained beyond what was merged here. The AOSP commit shows only the five insertions.",
      "Whether this fix reaches devices through a mainline module update or through a platform OTA."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28655": {
     "cve": "CVE-2026-28655",
     "summary": "Reworks RemoteViews parceling so that nested RemoteViews refer by int id to an ApplicationInfoCache parceled at the root, instead of each node writing a full ApplicationInfo. It also removes RemoteViews' use of Parcel.allowSquashing and the ReadWriteHelper trick that forced eager Bundle unparceling in ReflectionAction.",
     "component": "platform/frameworks/base, core/java/android/widget/RemoteViews.java (RemoteViews parceling, ApplicationInfoCache, ReflectionAction)",
     "weakness": "unknown. The diff shows a Parcel serialization/deserialization inconsistency: RemoteViews.writeToParcel enabled Parcel.allowSquashing for per-node ApplicationInfo objects, which also changed how Bundle values were written and needed a workaround to force immediate Bundle unparceling. The material does not state the exact flaw class.",
     "root_cause": "Before the fix:\n- RemoteViews.writeToParcel called dest.allowSquashing() for the whole write.\n- Every RemoteViews node in MODE_NORMAL wrote its own ApplicationInfo with dest.writeTypedObject(mApplication, flags), and the reader called parcel.readTypedObject(ApplicationInfo.CREATOR) for each node.\n- The in-memory ApplicationInfoCache (a Map keyed by packageName and uid) was not parceled.\n- Because squashing changes how Bundle contents are written, ReflectionAction's BUNDLE case installed a private Parcel.ReadWriteHelper (ALTERNATIVE_DEFAULT) so that readBundle() unparceled immediately instead of lazily.\n\nThe second commit message says squashing was added for application info but \"doesn't work\", so mApplicationInfoCache was also added, and calls the squashing path redundant. The material does not describe the concrete failure (for example, which object ends up mismatched). It only shows that this serialization scheme is what the fix removes.",
     "fix_analysis": [
      "Commit 0e9656e2222e (Revert^2 \"Writing ApplicationInfoCache instead of multiple ApplicationInfos for nested RemoteViews\"), RemoteViews.java:ApplicationInfoCache. Replaces the Map<Pair<String,Integer>, ApplicationInfo> with a List<ApplicationInfo> (mCachedApplicationInfos). Adds ApplicationInfoCache(Parcel), which reads the list with parcel.createTypedArrayList(ApplicationInfo.CREATOR), and writeToParcel(Parcel,int), which writes it with dest.writeTypedList. getOrPut and updateAndGetId now return an int index. findId matches on packageName (Objects.equals) and uid. get(int id) checks the index bounds and returns null when it is out of range. getPackageUserKey is deleted.",
      "Same commit, RemoteViews.java: adds the field mApplicationId (default -1). Every path that sets mApplication now also sets mApplicationId = mApplicationInfoCache.updateAndGetId(mApplication). These paths are: the RemoteViews(ApplicationInfo,int) constructor, the landscape/portrait constructor, the sized constructor, the copy constructor, the sized and landscape/portrait parcel read paths, and the proto path that sets rv.mApplication. In the configureDescendantsAsChildren path and in the root reset path (which creates a new BitmapCache and ApplicationInfoCache), the code sets mApplicationId = getOrPut(mApplication) and then mApplication = get(mApplicationId). updateAppInfo now uses get(findId(info)) and updateAndGetId(info).",
      "Same commit, RemoteViews.java:RemoteViews(Parcel,...) and writeToParcel. In all three modes, the root now calls mApplicationInfoCache.writeToParcel(dest, flags) after mBitmapCache.writeBitmapsToParcel and before mCollectionCache.writeToParcel. In MODE_NORMAL, each node writes dest.writeInt(mApplicationId) instead of writeTypedObject(mApplication). On read, the root builds mApplicationInfoCache = new ApplicationInfoCache(parcel), and MODE_NORMAL nodes read mApplicationId = parcel.readInt() and then set mApplication = mApplicationInfoCache.get(mApplicationId). Children share the root cache through HierarchyRootData/configureAsChild.",
      "Commit 6fe346abe6fb (Remove usage of Parcel.allowSquashing in RemoteViews and immediate unparceling of Bundle), RemoteViews.java:writeToParcel(Parcel,int,SparseArray<Intent>). Removes boolean prevSquashingAllowed = dest.allowSquashing() at the start and dest.restoreAllowSquashing(prevSquashingAllowed) at the end, so RemoteViews no longer enables Parcel squashing while writing.",
      "Same commit, RemoteViews.java:ReflectionAction(Parcel), BUNDLE case. Removes the static ALTERNATIVE_DEFAULT ReadWriteHelper and the hasReadWriteHelper / setReadWriteHelper(ALTERNATIVE_DEFAULT) / setReadWriteHelper(null) sequence. The value is now read with a plain in.readBundle()."
     ],
     "completeness": [
      "Covers all three RemoteViews parcel modes. In each mode the root writes and reads the ApplicationInfoCache list. Only MODE_NORMAL nodes carry their own int id; the sized and landscape/portrait paths take mApplication from their child views and register it with updateAndGetId.",
      "ApplicationInfoCache.get(int) checks the id bounds (id < 0 or id >= size returns null), so a malformed or -1 id gives mApplication == null instead of an exception. The code shown adds no new validation of the ApplicationInfo objects in the parceled list.",
      "The squashing removal is complete for RemoteViews.writeToParcel: both the enable call and the restore call are gone. The ReflectionAction Bundle-read workaround is also removed. The diff does not show any other allowSquashing users. The material does not show whether other parcelable types nested in RemoteViews need related handling.",
      "Deduplication still matches on packageName and uid. A null ApplicationInfo now maps to id -1. Unlike the removed getPackageUserKey, findId has no null-packageName check, so an ApplicationInfo with a null packageName is now cached."
     ],
     "behavior_changes": [
      "The RemoteViews parcel layout changes. The root RemoteViews now writes a typed list of ApplicationInfo (the cache) between the bitmap cache and the collection cache, and each MODE_NORMAL node writes an int id instead of an ApplicationInfo object. The writer and the reader change together.",
      "RemoteViews no longer enables Parcel.allowSquashing during writeToParcel. ApplicationInfo deduplication is now done explicitly through the cache list.",
      "Bundle values in ReflectionAction are read with a plain readBundle(), so the forced immediate unparceling is gone.",
      "A nested MODE_NORMAL RemoteViews whose id does not resolve in the root cache gets mApplication == null, because get returns null for out-of-range ids."
     ],
     "preconditions": [
      "A RemoteViews object is written to and read from a Parcel across processes. Per index search, this happens for app widgets (Launcher3 LauncherAppWidgetHostView.updateAppWidget receives RemoteViews) and for collection items returned by IRemoteViewsFactory.getViewAt in RemoteViewsAdapter.updateRemoteViews. RemoteViews is an AIDL parcelable (RemoteViews.aidl).",
      "The affected paths involve nested RemoteViews (addView children, landscape/portrait or sized variants) that each carry an ApplicationInfo, and/or a ReflectionAction with a BUNDLE value. The material does not state the exact trigger conditions."
     ],
     "per_major": {
      "14": "Adapted fix. The material shows only the allowSquashing/eager-Bundle removal (ae484e3d7a20 in android-security-14.0.0_r29). It is applied to the older public writeToParcel(Parcel,int) signature, and ReflectionAction's field is named `value`. The material does not show whether the ApplicationInfoCache parceling rewrite was also applied on 14 (4/8 lines of the patched region present).",
      "15": "Adapted fix. The material shows the Revert^2 ApplicationInfoCache rewrite (04faeb30ca94 in android-security-15.0.0_r17) at different line offsets. It does not show whether the allowSquashing removal was also applied on 15 (6/8 lines present).",
      "16": "Same fix: both commits, 0e9656e2222e (ApplicationInfoCache rewrite) followed by 6fe346abe6fb (allowSquashing and eager-Bundle removal), in android-security-16.0.0_r8.",
      "17": "Adapted fix. The material shows the allowSquashing/eager-Bundle removal (b78e05338801 in android-security-17.0.0_r1), with the extra mOriginalDensity write in writeToParcel. It does not show whether the cache rewrite is present on 17 (4/8 lines present)."
     },
     "backport_notes": [
      "Apply in the Android 16 order. First the ApplicationInfoCache rewrite (0e9656e2222e), which depends on the existing Map-based ApplicationInfoCache, the HierarchyRootData/configureAsChild plumbing and updateAppInfo. Then the allowSquashing removal (6fe346abe6fb). The writer and reader sides of the parcel format change together.",
      "On Android 14, writeToParcel is the public writeToParcel(Parcel,int) without intentsToIgnore, and ReflectionAction uses `value` instead of `mValue` (as the Android 14 diff shows). Adjust the hunks to match."
     ],
     "confidence": "medium",
     "unknowns": [
      "The concrete failure mode of the old scheme (squashing, per-node ApplicationInfo, eager Bundle reads). The commit messages only say that squashing \"doesn't work\" and that the cache approach replaces it.",
      "Whether Android 14 and 17 also received the ApplicationInfoCache parceling rewrite, and whether Android 15 also received the allowSquashing removal. Only one commit is shown for each of these branches.",
      "The content of the original, reverted commit 532a0a90b30a and why it was reverted before this Revert^2."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28656": {
     "cve": "CVE-2026-28656",
     "summary": "Replaces the AppOpsManager-based toggling of OP_SYSTEM_ALERT_WINDOW/OP_TOAST_WINDOW user restrictions around sensitive autofill and device-admin UIs with a per-window Window#setHideOverlayWindows(true) / SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS attribute so non-system overlay windows cannot be shown on top of those windows.",
     "component": "platform/frameworks/base (services/autofill UI: FillUi, SaveUi, DialogFillUi, OverlayControl), platform/packages/apps/Settings, platform/packages/apps/TvSettings, platform/packages/apps/Car/Settings (DeviceAdminAdd screens)",
     "weakness": "Insufficient protection of a sensitive UI against overlay windows (tapjacking protection implemented by a time-windowed AppOps user restriction instead of a window attribute)",
     "root_cause": "Sensitive UIs (autofill fill/save/dialog windows in system_server, and the DeviceAdminAdd activation screens in Settings, TvSettings and Car Settings) tried to block overlays by calling AppOpsManager.setUserRestriction / setUserRestrictionForUser(OP_SYSTEM_ALERT_WINDOW and OP_TOAST_WINDOW, true, token) when the UI was shown or resumed, and releasing the restriction (false) on hide/destroy/pause. The commit message says this mechanism \"didn't stop overlay window showing again without SYSTEM_ALERT_WINDOW permission when the restrictions are released\". Protection therefore depended on lifecycle-driven set/release of an AppOps restriction rather than on a property of the protected window. In autofill, a single OverlayControl instance (one Binder token, UserHandle.USER_ALL) was created in AutoFillUI and shared by FillUi, SaveUi and DialogFillUi, and the hide()/destroy() paths called showOverlays() in finally blocks.",
     "fix_analysis": [
      "frameworks/base c9290fc206ca, FillUi.java: in AnchoredWindow's show path, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS is ORed into params.privateFlags before the mShowing check, so it applies before mWm.addView(). The OverlayControl parameter is removed from the FillUi and AnchoredWindow constructors. The hideOverlays() call after addView and the finally { showOverlays() } in the AnchoredWindow method that handles destroyCallbackOnError are deleted.",
      "frameworks/base c9290fc206ca, DialogFillUi.java: after mDialog.setContentView(decor), the constructor calls mDialog.getWindow().setHideOverlayWindows(true). The OverlayControl constructor argument and field are removed, as are the hideOverlays()/showOverlays() calls in show()/hide()/destroy().",
      "frameworks/base c9290fc206ca, SaveUi.java: the window setup adds window.setHideOverlayWindows(true) next to window.setCloseOnTouchOutside(true). The OverlayControl argument and field are removed, as is the AppOps toggling in show()/hide()/destroy().",
      "frameworks/base c9290fc206ca, AutoFillUI.java: drops the shared mOverlayControl field and its construction; FillUi, SaveUi and DialogFillUi are created without it. OverlayControl.java is deleted.",
      "packages/apps/Settings f6b6ecb4b6e1, applications/specialaccess/deviceadmin/DeviceAdminAdd.java: in onCreate(), mAppOps = getSystemService(AppOpsManager.class) is replaced with getWindow().setHideOverlayWindows(true). The setUserRestriction(true) calls in onResume() and setUserRestriction(false) calls in onPause() are removed; mActionButton.setEnabled(false) in onPause is kept.",
      "packages/apps/TvSettings 7a21141ea7ef, Settings/src/com/android/tv/settings/deviceadmin/DeviceAdminAdd.java: same change as in Settings. setHideOverlayWindows(true) is called in onCreate(), and the AppOps field, mToken and the onResume/onPause toggling are removed.",
      "packages/apps/Car/Settings 6ee22d7bc352: DeviceAdminAddActivity gains an onCreate() override that calls getWindow().setHideOverlayWindows(true). DeviceAdminAddHeaderPreferenceController drops mAppOps/mToken and the setUserRestriction calls in onResumeInternal()/onPauseInternal(); preference.setEnabled(false) in onPauseInternal is kept.",
      "packages/apps/Car/Settings 770d737414c8 (test-only): removes the AppOpsManager mock and the verifyAppOpsMgrSetUserRestriction helper from BaseEnterpriseTestCase, and the matching assertions from DeviceAdminAddHeaderPreferenceControllerTest."
     ],
     "completeness": [
      "Covers the three autofill UI surfaces in system_server: the FillUi anchored popup (both AnchoredWindow construction sites in the FillUi constructor share the flag set in AnchoredWindow), the SaveUi dialog and the DialogFillUi bottom sheet. Also covers the DeviceAdminAdd screens in Settings, TvSettings and Car Settings.",
      "The protection is now set at window setup (onCreate, the dialog constructor or setup, or each AnchoredWindow show call) as a property of the window, instead of being toggled around show/hide.",
      "The old code also restricted OP_TOAST_WINDOW; the new code only sets the hide-non-system-overlay window attribute. The material does not show whether toast windows are covered by that attribute.",
      "The material shows no other AppOps-based overlay suppression sites, and does not show whether other sensitive UIs used the same pattern."
     ],
     "behavior_changes": [
      "Autofill UI no longer sets an AppOps user restriction (UserHandle.USER_ALL) on SYSTEM_ALERT_WINDOW and TOAST_WINDOW while shown. Non-system overlays are instead hidden through the window attribute of the autofill windows.",
      "DeviceAdminAdd screens (Settings, TvSettings, Car Settings) no longer toggle AppOps user restrictions on resume/pause. The hide-overlay attribute is applied to the activity window from onCreate().",
      "The OverlayControl class is removed from com.android.server.autofill.ui, and the FillUi, SaveUi, DialogFillUi and AnchoredWindow constructors lose the OverlayControl parameter.",
      "Car Settings tests no longer assert AppOps interactions."
     ],
     "preconditions": [
      "An autofill session reaches the point where system_server shows a fill popup, fill dialog or save dialog (per index search, AutoFillUI.showFillDialog constructs DialogFillUi), or a DeviceAdminAdd activity is shown in Settings, TvSettings or Car Settings.",
      "An app that shows overlay windows while the sensitive UI is displayed. Beyond the commit message, the material does not detail when the old AppOps restriction failed."
     ],
     "per_major": {
      "14": "Adapted fix: frameworks/base c441211a26c9 in android-security-14.0.0_r29 applies the same autofill change (OverlayControl deleted, setHideOverlayWindows/SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS added) with context differences: the FillUi constructor has no maxInputLengthForAutofill, and mCallback.onShown() takes no count. The resolver reports 6/8 patched lines present. The material does not show whether the Settings/TvSettings/Car Settings counterparts landed on 14.",
      "15": "Resolved in android-security-15.0.0_r17. The commit shown is TvSettings 7a21141ea7ef (setHideOverlayWindows in DeviceAdminAdd.onCreate); 8/8 patched lines present. The frameworks/base, Settings and Car Settings counterparts for 15 are not shown in the material.",
      "16": "Same fix: frameworks/base c9290fc206ca, Settings f6b6ecb4b6e1, Car/Settings 6ee22d7bc352 and test fix 770d737414c8 in android-security-16.0.0_r8; 8/8 patched lines present.",
      "17": "Not affected per the bulletin (Android 17 is not listed). For reference, the resolver shows 6/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a multi-project fix: frameworks/base (autofill), Settings, TvSettings and Car/Settings must each be patched. For Car/Settings, land 770d737414c8 together with 6ee22d7bc352; otherwise the multivalent tests still assert AppOps setUserRestriction calls that production code no longer makes, and they will fail.",
      "On Android 14 the autofill constructor signatures differ; use c441211a26c9 as the adapted version."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not demonstrate the exact sequence by which the old AppOps restriction could be bypassed; the commit message only summarizes it.",
      "Whether toast windows are hidden by the new window attribute (the old code restricted OP_TOAST_WINDOW explicitly).",
      "Which of the Settings/TvSettings/Car Settings and frameworks/base commits landed on Android 14 and 15 beyond the ones shown.",
      "Whether each patched app (Settings, TvSettings, Car Settings) holds the permission required for setHideOverlayWindows to take effect; this is not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28658": {
     "cve": "CVE-2026-28658",
     "summary": "AccountsDb.findMetaAuthUid now gets the account type from a meta_key by removing the fixed \"auth_uid_for_type:\" prefix with substring. Before, it split the key on \":\" and took element [1], which cut short any type that contains \":\".",
     "component": "platform/frameworks/base, AccountManagerService / AccountsDb (services/core/java/com/android/server/accounts)",
     "weakness": "Improper parsing of a delimited key: the code splits on a delimiter that may also appear inside the payload (the account type), which cuts the type short and maps the value to the wrong key",
     "root_cause": "Per the commit message, AccountsDb stores authenticator UID metadata under keys that start with the prefix \"auth_uid_for_type:\" (META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX). findMetaAuthUid got the account type back with TextUtils.split(metaCursor.getString(0), META_KEY_DELIMITER)[1], using \":\" as the delimiter. If the type part of a key contains \":\", the split returns more than two elements, and element [1] holds only the part of the type before its first \":\". The type -> uid map returned by findMetaAuthUid could then be keyed by a shortened type, linking the recorded authenticator UID to a different (shorter) account type. The bulletin rates this as High, type EoP.",
     "fix_analysis": [
      "services/core/java/com/android/server/accounts/AccountsDb.java: the constant META_KEY_DELIMITER (\":\") is removed. It is replaced by META_KEY_PREFIX_LEN = META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX.length().",
      "services/core/java/com/android/server/accounts/AccountsDb.java:findMetaAuthUid: each row is now parsed as typeWithPrefix = metaCursor.getString(0); type = typeWithPrefix.substring(META_KEY_PREFIX_LEN). Everything after the fixed prefix is used as the account type, so ':' characters inside the type are kept.",
      "The existing check if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) is unchanged and still runs after the new parsing."
     ],
     "completeness": [
      "The fix covers the single read-side parse in findMetaAuthUid. The commit message says entries are stored with the prefix; the write code is not in the material.",
      "The new code assumes every row returned to findMetaAuthUid starts with the prefix: substring(META_KEY_PREFIX_LEN) would throw StringIndexOutOfBoundsException on a shorter key. The query that builds metaCursor is not shown, and the fix adds no guard for this case.",
      "No other code in the material parses meta_key, so the material shows no untouched sibling path."
     ],
     "behavior_changes": [
      "findMetaAuthUid now returns account types containing ':' in full, instead of cutting them at the first ':' after the prefix.",
      "Entries are no longer merged with, or mistaken for, a shorter type that has the same leading segment.",
      "The diff adds no new error codes, logging or API surface. The change is internal to the system server's accounts database layer."
     ],
     "preconditions": [
      "A row with key auth_uid_for_type:<type> exists, where <type> contains a ':' character. The write path is not shown in the material.",
      "findMetaAuthUid must be called. Its callers in AccountManagerService are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 051431afaec5 in android-security-14.0.0_r29. The diff and hunk offsets (lines 177 / 1055) are identical to the Android 16 commit.",
      "15": "same fix: platform/frameworks/base 38dbed092b9d in android-security-15.0.0_r17. The diff and hunk offsets are identical to the Android 16 commit.",
      "16": "same fix: platform/frameworks/base 8c2ce8c567a2 in android-security-16.0.0_r8 (cherry-pick of 6a2d8b6bc382, Change-Id I3c028c0cfdbdc44a09fffee3542ba8bc782c1435).",
      "17": "same fix: platform/frameworks/base 31e7981c9d23 in android-security-17.0.0_r1. The code change is the same; only the hunk offsets differ (lines 173 / 1094)."
     },
     "backport_notes": [
      "Single-commit, self-contained change to AccountsDb.java with no dependencies visible in the material. The diff is identical on 14 through 16 and differs only in offsets on 17. It should carry over to any tree that still has META_KEY_DELIMITER and the TextUtils.split(...)[1] parse in findMetaAuthUid."
     ],
     "confidence": "high",
     "unknowns": [
      "Which callers in AccountManagerService use findMetaAuthUid's result, and how the misattributed UID leads to the EoP in the bulletin.",
      "Whether an arbitrary authenticator package can register an account type containing ':'. The write path and any type validation are not in the material.",
      "The query that selects the rows findMetaAuthUid iterates over, and whether it guarantees that every key starts with the prefix.",
      "Test coverage: the commit only says Test: manual."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28660": {
     "cve": "CVE-2026-28660",
     "summary": "Makes LauncherAppsService.getAllSessions pass the real caller's UID into a new PackageInstallerService.getAllSessions(userId, filterCallingUid) overload. SessionInfo objects are then generated and filtered for the launcher-side caller instead of for system_server after Binder.clearCallingIdentity().",
     "component": "platform/frameworks/base, package manager (LauncherAppsService / PackageInstallerService)",
     "weakness": "Information disclosure: the wrong identity was used for per-caller data scrubbing (identity was cleared before the privilege-dependent SessionInfo generation)",
     "root_cause": "LauncherAppsService.LauncherAppsImpl.getAllSessions(String callingPackage) calls Binder.clearCallingIdentity() and then invokes PackageInstallerService.getAllSessions(int userId). That method took the UID for session.generateInfoForCaller(false, callingUid) and for shouldFilterSession(snapshot, callingUid, info) from Binder.getCallingUid(). Under the cleared identity this is system_server's UID. As a result, the SessionInfo objects were generated for the system identity rather than for the real caller, and the commit states that originatingUri and referrerUri were exposed. Afterwards LauncherAppsService only applied its own shouldFilterSession(uid, info), which keeps a session if the caller is the installer UID or can query the package. This check drops whole sessions but does not scrub fields. So any app with package visibility to the session owner received the URIs through LauncherApps.getAllPackageInstallerSessions().",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java: getAllSessions(int userId) now only reads Binder.getCallingUid() and delegates to a new package-private overload, getAllSessions(int userId, int filterCallingUid).",
      "PackageInstallerService.getAllSessions(int, int) still uses the actual Binder.getCallingUid() for snapshot.enforceCrossUserPermission(...). It uses filterCallingUid for session.generateInfoForCaller(false /* includeIcon */, filterCallingUid) and for result.removeIf(info -> shouldFilterSession(snapshot, filterCallingUid, info)).",
      "services/core/java/com/android/server/pm/LauncherAppsService.java: LauncherAppsImpl.getAllSessions(String) now calls getPackageInstallerService().getAllSessions(userId, callingUid). callingUid is captured with Binder.getCallingUid() before Binder.clearCallingIdentity(). The existing LauncherAppsService-side sessionInfos.removeIf(shouldFilterSession(callingUid, info)) is kept.",
      "The Android 17 commit also adds PackageInstallerServiceTest.testGetAllSessions_filtersSessionsCorrectly in services/tests/mockingservicestests. The test exercises the public getAllSessions(int) path with canQueryPackage returning false and then true."
     ],
     "completeness": [
      "Covers the internal caller that clears identity before calling PackageInstallerService.getAllSessions: LauncherAppsService.getAllSessions. Per index search, this is the only caller found.",
      "The direct binder entry points PackageInstallerService.getAllSessions(int), getSessionInfo(int) and getStagedSessions() keep using Binder.getCallingUid(), which is the binder caller when they are invoked directly. They are unchanged.",
      "The actual scrubbing logic is in PackageInstallerSession.generateInfoForCaller, which is not in the diff. The fix only corrects the UID passed into it.",
      "On the LauncherApps path, the cross-user check (enforceCrossUserPermission) is still evaluated with the cleared identity. The set of users is bounded by LauncherAppsService's own profile-id selection (getProfileIds/getProfileIdsExcludingHidden). The fix does not change this.",
      "The Android 15 and 16 commits do not include the regression test that the Android 17 commit adds."
     ],
     "behavior_changes": [
      "LauncherApps.getAllPackageInstallerSessions() now returns SessionInfo objects generated for the calling app's UID, so originatingUri and referrerUri are scrubbed for ordinary callers.",
      "On the LauncherApps path, PackageInstallerService.shouldFilterSession is now applied with the real caller's UID, so more sessions may be dropped from the list than before.",
      "Adds a new package-private overload, PackageInstallerService.getAllSessions(int, int). There are no public API or AIDL changes."
     ],
     "preconditions": [
      "The caller invokes LauncherApps.getAllPackageInstallerSessions(), which routes to LauncherAppsImpl.getAllSessions(String) in LauncherAppsService (per index search). The code shown performs verifyCallingPackage and a hidden-profile access check only.",
      "A package install session exists for one of the caller's profile users, with originatingUri and/or referrerUri set.",
      "The caller is either the session's installer UID or can query the session's appPackageName (mPackageManagerInternal.canQueryPackage). Otherwise LauncherAppsService.shouldFilterSession drops the session."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14, and the material has no fix for it.",
      "15": "Same fix: platform/frameworks/base 498791aa27ab in android-security-15.0.0_r17. Identical code change, no test file.",
      "16": "Same fix: platform/frameworks/base 3d614f9e98a3 in android-security-16.0.0_r8. Identical code change, no test file.",
      "17": "Same fix: platform/frameworks/base 6acdd7f1a72d in android-security-17.0.0_r1. Adds testGetAllSessions_filtersSessionsCorrectly to PackageInstallerServiceTest. Differs in PackageInstallerService only by a blank line in the context."
     },
     "backport_notes": [
      "Single commit touching two files. The hunks are small and identical in content across 15, 16 and 17. If a fork has other internal callers that clear identity before calling getAllSessions(int), they should be switched to the new two-argument overload as well."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact scrubbing PackageInstallerSession.generateInfoForCaller performs for non-privileged UIDs (the file is not in the material).",
      "Why the bulletin does not list Android 14, given that the per-major facts report the patched region (8/8 lines) at android-security-14.0.0_r29.",
      "Whether the Android 15 and 16 branches received the regression test separately."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28663": {
     "cve": "CVE-2026-28663",
     "summary": "LauncherAppsService.buildIntentSenderForUser now passes ActivityOptions with PendingIntentCreatorBackgroundActivityStartMode set to MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE when it creates the system-owned PendingIntent that getAppMarketActivityIntent returns. The creator's background-activity-start privilege therefore no longer applies when the app sending the IntentSender is not visible.",
     "component": "platform/frameworks/base, LauncherAppsService (services/core/java/com/android/server/pm), PendingIntent / background activity launch (BAL) restrictions",
     "weakness": "Missing restriction on a privileged PendingIntent handed to unprivileged callers (confused deputy enabling background activity launch bypass)",
     "root_cause": "LauncherAppsService.buildIntentSenderForUser (used by buildAppMarketIntentSenderForUser for getAppMarketActivityIntent) called PendingIntent.getActivityAsUser with options == null. Per the commit message, the call runs with cleared calling identity, so the PendingIntent is created on behalf of system_server (UID 1000) and inherits the creator's background-activity-start privileges. The resulting IntentSender was returned to arbitrary apps. An unprivileged app that triggered its send (the commit cites use as a notification deleteIntent) could start an activity from the background, bypassing BAL restrictions.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/LauncherAppsService.java:buildIntentSenderForUser builds a Bundle via ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE).toBundle() and passes it as the options argument of PendingIntent.getActivityAsUser instead of null.",
      "The flags (FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT), request code, intent and user are unchanged. The system still creates the PendingIntent (which preserves cross-profile behaviour, per the commit message), but it now carries a creator-side BAL mode that only permits activity starts when the sending app is visible.",
      "Single commit. The Android 17 commit b8e217c4e6cd has identical hunk content at different line offsets: new-side hunks start at 1867/1880 on Android 17 versus 1824/1837 on Android 16."
     ],
     "completeness": [
      "Covers every PendingIntent produced through buildIntentSenderForUser, i.e. the app-market IntentSender from buildAppMarketIntentSenderForUser / getAppMarketActivityIntent. Per index search, there are no other callers of buildIntentSenderForUser.",
      "The code context shows a second, separate PendingIntent.getActivityAsUser call at lines 1803-1809 (psSettingsIntent, inside a try block whose finally calls Binder.restoreCallingIdentity). It still passes null options and this commit does not change it. The material does not show whether unprivileged callers can reach it or whether other mitigations apply.",
      "The fix relies on the ALLOW_IF_VISIBLE creator mode rather than removing the privileged creation. It does not add caller checks to getAppMarketActivityIntent."
     ],
     "behavior_changes": [
      "The IntentSender returned by getAppMarketActivityIntent now carries MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE as the creator BAL mode. If the sending app is not visible, the creator's privilege no longer allows the app-market activity to start from the background. Visible senders are unaffected.",
      "No API signature, flag, or return-value changes. null is still returned when PendingIntent creation fails."
     ],
     "preconditions": [
      "An app can call LauncherApps.getAppMarketActivityIntent (the commit message states an unprivileged app could request this IntentSender).",
      "The caller obtains the IntentSender and causes it to be sent while in the background (the commit message cites a notification deleteIntent as the vector)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. The resolver reports 6/8 lines of the patched region at android-security-14.0.0_r29, so some form of the surrounding code exists there, but no fix is recorded. The material does not show the status of the fix in 14.",
      "15": "Not affected per the bulletin. The resolver reports 8/8 lines of the patched region at android-security-15.0.0_r17. The material does not show whether this reflects the fixed code or only matching context lines.",
      "16": "Same fix: commit 05c5903d27b8 in android-security-16.0.0_r8 (cherry-pick of internal changes 0c7caef3 / 341e2f0e, Change-Id Ida112cd1).",
      "17": "Same fix: commit b8e217c4e6cd in android-security-17.0.0_r1, identical hunk content with new-side hunks at lines 1867/1880."
     },
     "backport_notes": [
      "Single self-contained hunk in buildIntentSenderForUser. It needs ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode and the MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE constant on the target branch. The ALLOW_IF_VISIBLE constant is newer than the setter, so older branches may need a different mode or a backport of the constant (general platform knowledge).",
      "If the target branch lacks the buildIntentSenderForUser helper, apply the same options Bundle to the PendingIntent.getActivityAsUser call behind getAppMarketActivityIntent."
     ],
     "confidence": "high",
     "unknowns": [
      "The shown context does not include where clearCallingIdentity is called around buildIntentSenderForUser, or the permission checks on getAppMarketActivityIntent. Only the commit message describes them.",
      "Whether the Android 14/15 branches contain equivalent vulnerable code or the fix: the bulletin does not list them, and the resolver line counts (6/8, 8/8) are ambiguous.",
      "Which method the second null-options PendingIntent at lines 1803-1809 belongs to, and who can call it."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28664": {
     "cve": "CVE-2026-28664",
     "summary": "RuntimeImage::WriteImageToDisk in ART now fchmod()s the runtime-generated app image (.art) file descriptor to 0444 right after the file is created, and fails image generation if that call does not succeed, so the on-disk image is no longer left writable.",
     "component": "platform/art, runtime (runtime-generated app image, runtime/runtime_image.cc)",
     "weakness": "Insecure file permissions on a runtime-generated artifact (app image file left writable, allowing tampering per the commit message)",
     "root_cause": "RuntimeImage::WriteImageToDisk created the app image file and filled it with the serialized image data without restricting its permissions. Per the commit message the file stayed writable after it was done, there was no need for that, and making it read-only helps prevent tampering. The diff does not show the create call or the move to the final path, so the original mode and directory are not visible in the material.",
     "fix_analysis": [
      "runtime/runtime_image.cc: adds #include <sys/stat.h> for fchmod and the S_IR* mode constants.",
      "runtime/runtime_image.cc:RuntimeImage::WriteImageToDisk: immediately after the image file is created (per the added comment, just after an existing 'return false' error path), calls fchmod(image_file->Fd(), S_IRUSR | S_IRGRP | S_IROTH). This makes the file read-only (0444) before full_data is built and filled. The comment notes that writes through the already-open descriptor still succeed, so the later write path is unaffected.",
      "runtime/runtime_image.cc:RuntimeImage::WriteImageToDisk: if fchmod fails, sets *error_msg to 'Failed to make runtime image read-only: <strerror(errno)>' and returns false, aborting generation. The commit message says the image is deleted in that case; the deletion is not in the shown hunk, and the material does not show how it happens.",
      "The commit has two Cherrypick-From lines. The commit message describes making the file read-only once it is written and moved to its final path, but the shipped diff applies the chmod at creation time. Only the creation-time variant is in the hunk."
     ],
     "completeness": [
      "Applies the read-only mode at the creation point shown in RuntimeImage::WriteImageToDisk, before any image bytes are written. Whether other creation points exist is not shown.",
      "Only the file mode is changed. The fix does not stop an actor with write permission on the containing directory from unlinking or replacing the file, and the owner can still chmod it back. Whether such access is possible depends on directory permissions, which the material does not show.",
      "This commit adds no load-side integrity check; how the loader validates the image is not shown.",
      "Other ART-generated artifacts (e.g. files written by dex2oat) are not touched by this commit; whether they have similar handling is not shown in the material."
     ],
     "behavior_changes": [
      "Runtime-generated app image files are set to mode 0444 by fchmod right after creation. Afterwards, unprivileged processes, including the owner, can no longer open the file for writing unless the mode is changed first (privileged processes with CAP_DAC_OVERRIDE are not restricted).",
      "New failure path: if fchmod fails, WriteImageToDisk returns false with the error message 'Failed to make runtime image read-only: ...' and no image is produced for that run.",
      "Writes through the descriptor opened at creation are unaffected, so image generation output is otherwise unchanged."
     ],
     "preconditions": [
      "The runtime must be generating an app image at runtime, i.e. RuntimeImage::WriteImageToDisk is reached and successfully creates the output file; the exact triggering configuration is not shown in the material.",
      "For the original weakness to matter, another actor needed write access to the generated .art file (file or directory permissions); the material does not show which actors had that access."
     ],
     "per_major": {
      "14": "same fix: platform/art f8f793610644 in android-security-14.0.0_r29. Identical hunk; only the include context differs (an existing #include <sstream> comes before the new <sys/stat.h>). 6/6 lines of the patched region present.",
      "15": "same fix: platform/art 19665de3fb4d in android-security-15.0.0_r17. Identical hunk and context. 6/6 lines present.",
      "16": "same fix: platform/art a57f914df7ee in android-security-16.0.0_r8 (the analysed commit). 6/6 lines present.",
      "17": "same fix: platform/art 70be2043e6db in android-security-17.0.0_r1. Identical hunk at a slightly shifted line offset. 6/6 lines present."
     },
     "backport_notes": [
      "One self-contained hunk plus one include, applied unchanged on 14, 15, 16 and 17. Place the fchmod right after the image-file creation error path and before full_data is built, so no data is ever written to a writable file.",
      "ART is delivered as a mainline module (com.android.art) on recent releases, so forks that ship their own ART module build should check that build as well (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The original mode and directory of the image file, and which processes/UIDs could write to it before the fix.",
      "How the partially created file is cleaned up when fchmod fails (the commit message says it is deleted; the deletion is not in the shown hunk).",
      "What each Cherrypick-From change contained, in particular whether an intermediate version did the chmod after the move.",
      "The exact conditions under which runtime app image generation is enabled on each release.",
      "Whether WriteImageToDisk has other file-creation points not covered by this hunk.",
      "Whether the fix is also delivered through the ART mainline module."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28668": {
     "cve": "CVE-2026-28668",
     "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that a realloc rejected by the Android allocation limit returns nullptr without freeing the caller's original pointer, matching realloc semantics.",
     "component": "platform/bionic, libc malloc limit hooks (libc/bionic/malloc_limit.cpp)",
     "weakness": "Invalid free on a failure path (freeing a pointer the caller still owns), which leads to a double free or use-after-free by callers that follow the realloc rules",
     "root_cause": "In libc/bionic/malloc_limit.cpp, LimitRealloc checked whether growing an allocation would cross the configured allocation limit (bytes > old_usable_size && !CheckLimit(bytes - old_usable_size)). If it would, the code logged a warning, called LimitFree(old_mem) and returned nullptr. Under realloc semantics, the original block stays valid and owned by the caller when realloc fails (returns nullptr). The commit message says the same: the original pointer should not be changed. Freeing it inside the limit hook left the caller holding a dangling pointer, so a later free() or use of the original pointer is a double free or use-after-free, even though the caller handled the realloc failure correctly.",
     "fix_analysis": [
      "libc/bionic/malloc_limit.cpp:LimitRealloc: in the limit-exceeded branch, the lines '// Free the old pointer.' and 'LimitFree(old_mem);' are deleted and replaced with the comment '// Do not free the old pointer, it's still valid.'. The function still logs the warning and returns nullptr, and old_mem is no longer freed.",
      "tests/malloc_test.cpp:TEST(android_mallopt, set_allocation_limit_realloc_increase): the over-limit realloc result is now stored in a separate 'new_memory' variable and asserted to be nullptr, then the original 'memory' pointer is passed to free(). Per the commit message, the test fails if the old behaviour is restored."
     ],
     "completeness": [
      "The fix covers the only place in the diff that freed the original block on a rejected realloc: the CheckLimit failure branch of LimitRealloc.",
      "The other failure branch in LimitRealloc (dispatch_table->realloc or Malloc(realloc) returning nullptr, lines 214-217 in context) does not free old_mem, so it does not have the same invalid free. It only adjusts gAllocated.",
      "The other limit hooks visible in context (the posix_memalign hook and LimitAlignedAlloc) have no old pointer to free and simply return an error, so they have no matching path.",
      "The realloc path when the limit is not exceeded is unchanged. The test change only covers the grow-past-limit case."
     ],
     "behavior_changes": [
      "When the Android allocation limit is enabled and a growing realloc would exceed it, realloc now returns nullptr and the original allocation stays valid. Before the fix, the original block was freed.",
      "Callers that wrongly relied on the old block being freed on this failure will now leak it. Callers that follow standard realloc semantics now behave correctly.",
      "The warning_log message is unchanged."
     ],
     "preconditions": [
      "The process must have the bionic allocation limit enabled (the test does this with android_mallopt and M_SET_ALLOCATION_LIMIT_BYTES), so that realloc goes through LimitRealloc.",
      "A realloc of an existing block to a larger size where bytes - old_usable_size fails CheckLimit against gAllocLimit.",
      "The caller then frees or uses the original pointer, which realloc semantics allow after a nullptr return."
     ],
     "per_major": {
      "14": "same fix: platform/bionic cd1be6e8bfb8 in android-security-14.0.0_r29. The malloc_limit.cpp hunk (at line 199) and the test change (at line 1279) are identical. 6/8 lines of the patched region present.",
      "15": "same fix: platform/bionic 10a1e2ce6e59 in android-security-15.0.0_r17. Identical hunks (malloc_limit.cpp at line 200, test at line 1289). 6/8 lines present.",
      "16": "same fix: platform/bionic 50cbdffd6f7e in android-security-16.0.0_r8 (cherry-pick of d00918bfbbe0). malloc_limit.cpp hunk at line 200, test hunk at line 1291. 6/8 lines present.",
      "17": "same fix: platform/bionic 48a7e3362ff9 in android-security-17.0.0_r1. Identical code hunk (at line 201). The test hunk is at line 818, with identical content. 6/8 lines present."
     },
     "backport_notes": [
      "One commit that removes two lines in LimitRealloc. It should apply to any branch where malloc_limit.cpp has the 'LimitFree(old_mem);' call in the CheckLimit failure branch. The test hunk sits at a different line in each branch (818 on 17, 1279-1291 on 14-16) but its content is identical."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which system component or caller enables the allocation limit in production, so it is unclear how the EoP rating is reached. Only the unit test's use of M_SET_ALLOCATION_LIMIT_BYTES is visible.",
      "The body of LimitFree is not shown, so the exact effect of the old path on gAllocated is unknown.",
      "The details of bug 501541998 are not included."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28671": {
     "cve": "CVE-2026-28671",
     "summary": "MediaProvider.update's file-movement path now deletes a stale Files row at the computed destination path, or checks write permission on a live one, before Os.rename. On a UNIQUE(_data) conflict in the following database update, it deletes the conflicting row at the active rename destination and retries in the same transaction.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.update / updateAllowingReplace (MediaStore file placement and pending-file publishing)",
     "weakness": "Race condition / namespace squatting: one app could claim the database row for a path while another app's file was being renamed onto that path, and the database update did not resolve the conflict (CWE-362 style; bulletin type ID)",
     "root_cause": "In MediaProvider.update, when placement columns change and movement is allowed, the code computed afterPath and called Os.rename(beforePath, afterPath). It did not check whether another row in the Files table already had _data == afterPath. The following database update (updateAllowingReplace) could then hit a UNIQUE constraint on _data. For callers with targetSdk >= R the exception was simply rethrown. For legacy callers the replace path ran only if a file existed at the path. So a pre-existing (squatter) row at the destination path was not resolved. The test testPendingUpdate_SquatterRowDeleted covers this case: it inserts a pending row, then a second row with the same DISPLAY_NAME/RELATIVE_PATH and IS_PENDING=0, then publishes the first row.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:update: adds a local String renameAfterPath (null by default). Inside the movement block, after afterPath is finalised and before Os.rename, it calls the new checkIfPathAlreadyExists(helper, beforeVolume, afterPath) and records renameAfterPath = afterPath. The later call becomes updateAllowingReplace(qb, helper, values, userWhere, userWhereArgs, renameAfterPath).",
      "src/com/android/providers/media/MediaProvider.java:checkIfPathAlreadyExists (new): with the local calling identity cleared, it runs queryForSingleItem on MediaStore.Files.getContentUri(volume) for _ID where DATA = afterPath. If there is no row (FileNotFoundException or null cursor) it returns. If a row exists but new File(afterPath).exists() is false, it calls deleteEntryForPath(helper, afterPath) to remove the stale row. If both the row and the file exist, it restores the identity and calls enforceCallingPermission(MediaStore.Files.getContentUri(volume, id), Bundle.EMPTY, true), so the caller must have write access to that row.",
      "src/com/android/providers/media/MediaProvider.java:updateAllowingReplace: new @Nullable renameAfterPath parameter. In the SQLiteConstraintException handler, before the targetSdk >= R check, it reads path = values.getAsString(FileColumns.DATA) and calls handleNonExistingEntryFile(qb, helper, values, userWhere, userWhereArgs, path, renameAfterPath, e). If that returns anything other than -1, the count is returned. The old pre-check 'if (path == null || !new File(path).exists()) throw e;' on the legacy replace path is removed. A null path is now handled in handleNonExistingEntryFile, and the file-existence check is gone.",
      "src/com/android/providers/media/MediaProvider.java:handleNonExistingEntryFile (new): throws e if path is null. If Objects.equals(path, renameAfterPath) and deleteEntryForPath(helper, path) > 0, it re-runs qb.update(helper, values, userWhere, userWhereArgs) inside the enclosing helper.runWithTransaction and returns the count. Otherwise it returns -1 and the existing logic runs.",
      "src/com/android/providers/media/MediaProvider.java:deleteEntryForPath (new): with the local calling identity cleared, it runs db.delete(Files.TABLE, \"_data=?\", new String[]{path}) in helper.runWithTransaction. On an exception it logs and returns 0.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: adds testPendingUpdate_SquatterRowDeleted (a squatter row with no file is removed and the update returns 1) and testPendingUpdate_SquatterRowFileExists (a squatter row with a real file is kept and the published row gets a different display name)."
     ],
     "completeness": [
      "Covers only the update() movement path. renameAfterPath is set only inside the placement/movement block, so handleNonExistingEntryFile deletes a conflicting row only when the conflict path equals the rename destination computed in the same update call. Constraint conflicts from other update paths (renameAfterPath null) fall through to the unchanged legacy behaviour.",
      "checkIfPathAlreadyExists runs before Os.rename. handleNonExistingEntryFile handles any conflict at the rename destination during the database update, including a row that appears after the check. The delete-and-retry runs inside the outer helper.runWithTransaction of updateAllowingReplace.",
      "handleNonExistingEntryFile does not itself check whether a file exists at path. Its only test for deleting the conflicting row is path == renameAfterPath.",
      "checkIfPathAlreadyExists matches only a _data string exactly equal to afterPath, in the Files table of beforeVolume.",
      "This commit does not touch the insert path or the FUSE rename paths. The material does not show whether they handle this case in a similar way.",
      "deleteEntryForPath removes the row with a raw db.delete on Files.TABLE rather than through the provider's normal delete() handling."
     ],
     "behavior_changes": [
      "An update that moves a file may now delete another Files row whose _data equals the destination path, with the calling identity cleared. Before the rename (checkIfPathAlreadyExists) this happens only when no file exists at that path. After a UNIQUE conflict (handleNonExistingEntryFile) it happens whenever the conflict path equals the rename destination, with no file-existence check.",
      "If a row and a file both already exist at the destination path, update now calls enforceCallingPermission(..., forWrite=true) on that row. It can therefore throw a SecurityException where it previously went on to Os.rename.",
      "For legacy (targetSdk < R) callers, the replace-on-conflict path no longer requires a file at the conflicting path. Only a null path still rethrows the SQLiteConstraintException.",
      "After a conflict on the rename destination, the database update is retried once inside the same transaction and its count is returned, instead of the exception propagating."
     ],
     "preconditions": [
      "A caller invokes MediaProvider.update on an item URI matching AUDIO_MEDIA_ID, AUDIO_PLAYLISTS_ID, VIDEO_MEDIA_ID, IMAGES_MEDIA_ID, DOWNLOADS_ID or FILES_ID. The values touch sPlacementColumns (e.g. clearing IS_PENDING), do not include MediaColumns.DATA, the item is not a thumbnail, and movement is allowed (QUERY_ARG_ALLOW_MOVEMENT, default true for non-self callers).",
      "A second row in the Files table already has _data equal to the destination path computed for the move. The tests create it with a plain insert using the same DISPLAY_NAME and RELATIVE_PATH and IS_PENDING=0.",
      "External callers reach update through ContentResolver.update. Per index search, DownloadProvider.updateMediaProvider calls mediaProvider.update on MediaStore URIs, but the material does not show whether that call reaches the movement path."
     ],
     "per_major": {
      "14": "same fix: commit e27ea39d7184 in android-security-14.0.0_r29. Hunks are identical at different line offsets (update around line 7605, updateAllowingReplace around 7932). 8/8 lines of the patched region present.",
      "15": "same fix: commit 9e42f05c7c32 in android-security-15.0.0_r17. Hunks are identical (update around line 8283). 8/8 lines present.",
      "16": "same fix: commit 94374dc41151 in android-security-16.0.0_r8 (the analysed commit). 8/8 lines present.",
      "17": "same fix, with context differences: commit 87beb3978b6a in android-security-17.0.0_r1. The logic and helpers are identical. checkIfPathAlreadyExists is inserted before a Logging.logIfLoggable call instead of Log.d, and the surrounding code uses markPathAsDeletedAndInvalidateFuseDentry. 8/8 lines present."
     },
     "backport_notes": [
      "Single self-contained commit. It adds three private helpers and one parameter and depends only on existing helpers (queryForSingleItem, enforceCallingPermission, clearLocalCallingIdentity/restoreLocalCallingIdentity, helper.runWithTransaction, Files.TABLE). The diff adds no imports, so Objects, BaseColumns, FileNotFoundException and Nullable must already be imported in a fork's MediaProvider.java."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not describe the information-disclosure impact behind the High rating, beyond the commit's 'namespace squatting' description.",
      "The material does not show the state after an unfixed conflict (whether Os.rename is reverted, and which row ends up referencing the moved file).",
      "Whether the insert or FUSE rename paths have similar conflict handling is not shown.",
      "Whether deleting the stale row with a raw db.delete sends change notifications or affects other state is not shown.",
      "Whether this fix also reaches devices through a MediaProvider module update, in addition to the platform security tags, is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45517": {
     "cve": "CVE-2026-45517",
     "summary": "Adds structural (scheme/authority/path pattern) and user-id validation of photopicker pre-selection URIs in PickerUriResolver.processUrisForSelection and MediaQueryForPreSelection before they are permission-checked and used to build picker database queries.",
     "component": "platform/packages/providers/MediaProvider, photopicker pre-selection URI handling (PickerUriResolver, photopicker/v2/model/MediaQueryForPreSelection)",
     "weakness": "Improper input validation of caller-supplied URIs (missing user-id and URI structure/authority check before use in database queries)",
     "root_cause": "Apps can pass URIs of media items via EXTRA_PICKER_PRE_SELECTION_URIS. In PickerUriResolver.processUrisForSelection (v1 path) and in MediaQueryForPreSelection (v2 path, exercised via PickerDataLayerV2.queryMediaForPreSelection in the tests) each input string was parsed with Uri.parse and the only gate was checkUriPermission(uri, pid -1, callingUid); the URI then went into accessibleUris and was used to derive local/cloud id selections for the picker DB query. Nothing verified that the URI was a content URI with MediaStore.AUTHORITY and a valid picker path shape, nor that the user id encoded in path segment 1 (content://media/picker/<user-id>/...) matched the user MediaProvider is running as. Per the commit message this allowed preselected media to be inconsistent with the active profile and cause a data mismatch in database queries; the bulletin rates it High / ID.",
     "fix_analysis": [
      "src/com/android/providers/media/PickerUriResolver.java: adds a static LocalUriMatcher sStaticUriMatcher = new LocalUriMatcher(MediaStore.AUTHORITY) and a new public static isValidPickerUri(@Nullable Uri): returns false for null, for a scheme other than ContentResolver.SCHEME_CONTENT, and unless sStaticUriMatcher.matchUri(uri, allowHidden=false) returns PICKER_ID, PICKER_GET_CONTENT_ID or PICKER_TRANSCODED_ID (this implicitly enforces MediaStore.AUTHORITY and the picker path segment layout).",
      "src/com/android/providers/media/PickerUriResolver.java:processUrisForSelection: inside the per-URI loop, before checkUriPermission, skips (continue, with Log.d) any URI failing isValidPickerUri, then skips any URI whose getUserId(uri) (Integer.parseInt of path segment 1) differs from UserHandle.myUserId(). Only URIs passing both checks reach the existing permission check and accessibleUris.",
      "src/com/android/providers/media/PickerUriResolver.java:getUserId: visibility widened from package-private to public static so MediaQueryForPreSelection can reuse it.",
      "src/com/android/providers/media/photopicker/v2/model/MediaQueryForPreSelection.java: same two pre-checks (PickerUriResolver.isValidPickerUri, then PickerUriResolver.getUserId(uri) != UserHandle.myUserId() -> continue) inserted ahead of PickerUriResolver.checkUriPermission(appContext, uri, -1, callingPackageUid) in the pre-selection URI filtering loop; adds android.os.UserHandle import.",
      "Tests: PickerUriResolverTest switches TEST_USER to UserHandle.myUserId() in testProcessUrisForSelection_permissionChecks and the cloud-authority test; PickerDataLayerV2Test mocks mMockContext.getUser() to Process.myUserHandle(), uses UserHandle.myUserId() in URI placeholders, and adds testQueryMediaForPreSelection_ignoresCrossUserUris, _ignoresMalformedUris, _ignoresFakeAuthorityUris and _ignoresInvalidPickerSegments."
     ],
     "completeness": [
      "Both pre-selection consumers shown in the material are patched: the v1 PickerUriResolver.processUrisForSelection loop and the v2 MediaQueryForPreSelection loop, and they share one validator (isValidPickerUri) so the accepted URI shapes are consistent.",
      "Accepted path shapes are limited to the LocalUriMatcher patterns PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID with allowHidden=false; picker_internal and paths with a non-media fourth segment (e.g. album) are rejected (covered by tests).",
      "The user-id check compares against UserHandle.myUserId() (the MediaProvider process user), not against a caller-supplied user; it is an equality check only, so cross-profile URIs are dropped rather than redirected.",
      "The fix does not validate the provider-authority segment of the picker URI (segment 2) against the configured local/cloud provider in these loops; whether downstream code does so is not shown in the material.",
      "Invalid URIs are silently skipped (Log.d), not rejected with an error, so the overall query proceeds with the remaining URIs."
     ],
     "behavior_changes": [
      "Pre-selection URIs that are not content:// URIs, do not have MediaStore.AUTHORITY, or whose path does not match a picker/picker_get_content/picker_transcoded pattern are now ignored with a debug log.",
      "Pre-selection URIs whose encoded user id differs from UserHandle.myUserId() are now ignored with a debug log, so cross-user pre-selection no longer produces results.",
      "New public API surface inside MediaProvider: PickerUriResolver.isValidPickerUri(Uri) and PickerUriResolver.getUserId(Uri) is now public.",
      "A static LocalUriMatcher instance is created at PickerUriResolver class load."
     ],
     "preconditions": [
      "An app launches the photopicker supplying EXTRA_PICKER_PRE_SELECTION_URIS; the picker forwards them to MediaProvider as QUERY_ID_SELECTION (v1, with QUERY_SHOULD_SCREEN_SELECTION_URIS and EXTRA_CALLING_PACKAGE_UID) or as pre_selection_uris with Intent.EXTRA_UID (v2, PickerDataLayerV2.queryMediaForPreSelection), per the tests in the diff.",
      "The supplied URI must pass checkUriPermission for the calling uid; the new checks run before that.",
      "For the cross-user aspect, a device with more than one user/profile so that a URI can encode a user id different from the one MediaProvider runs as."
     ],
     "per_major": {
      "14": "not affected per the bulletin; at android-security-14.0.0_r29 only 2/8 lines of the patched region are present, so the pre-selection filtering code is largely absent there.",
      "15": "adapted fix (1b032be59978 in android-security-15.0.0_r17): only PickerUriResolver.java and PickerUriResolverTest.java are changed; isValidPickerUri accepts PICKER_ID and PICKER_GET_CONTENT_ID only (no PICKER_TRANSCODED_ID), getUserId is not made public by this diff, and an androidx.annotation.Nullable import is added. MediaQueryForPreSelection is not touched by the 15 commit. 8/8 lines of the patched region present.",
      "16": "same fix (32217e55e510 in android-security-16.0.0_r8): PickerUriResolver, MediaQueryForPreSelection and both test files; 7/8 lines of the patched region present.",
      "17": "same fix (50d220c204df in android-security-17.0.0_r1): identical code changes to 16 apart from indentation of the sStaticUriMatcher declaration; PickerDataLayerV2Test hunks land at different offsets (setUp also has mTestConfigStore). 7/8 lines present."
     },
     "backport_notes": [
      "Single-commit fix. When porting, the set of match constants in isValidPickerUri must follow what LocalUriMatcher defines on the target branch (the 15 fix omits PICKER_TRANSCODED_ID), and the MediaQueryForPreSelection hunk was applied only on 16 and 17, where that v2 class is patched."
     ],
     "confidence": "high",
     "unknowns": [
      "What exception types the surrounding try/catch in both loops handles, and therefore whether a NumberFormatException from getUserId could propagate for a URI that passes the matcher but has a non-numeric user segment (the test expects such a URI to be ignored, suggesting the matcher pattern requires a numeric segment, but LocalUriMatcher is not in the material).",
      "What the downstream consumer does with the embedded provider-authority segment and whether a mismatched authority is handled elsewhere.",
      "The exact information disclosed (bulletin says ID only); the commit message describes it as a data mismatch during database queries.",
      "Why only 7/8 lines of the patched region are found at the 16 and 17 tags.",
      "Whether the fix is also delivered through MediaProvider module updates in addition to platform tags."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45518": {
     "cve": "CVE-2026-45518",
     "summary": "Adds an OPTION_CHECK_BRACKETS mode to SQLiteTokenizer that rejects SQL fragments with unbalanced or prematurely closed parentheses, and makes ContactsProvider's SelectionBuilder.addClause() run every clause (including the caller-supplied base selection) through that check before concatenating it.",
     "component": "platform/packages/providers/ContactsProvider (util/SelectionBuilder) with supporting change in platform/frameworks/base (android.database.sqlite.SQLiteTokenizer)",
     "weakness": "SQL injection: user-supplied selection concatenated into a WHERE clause with only parenthesis wrapping, no validation that the fragment cannot close the enclosing parentheses",
     "root_cause": "SelectionBuilder (ContactsProvider) accumulates clauses and build() wraps each in parentheses joined by AND, e.g. \"(A) AND (B)\". The constructor passes baseSelection, documented as \"typically the user supplied selection arg\", straight to addClause(), which only checked TextUtils.isEmpty before storing it. A base selection containing more ')' than '(' (the test uses \"1=1)) OR ((1=1\") closes the wrapping parentheses and lets the caller append its own OR term outside the provider-added restriction clauses, so the provider's constraints could be bypassed. Separately, SQLiteTokenizer.tokenize() had no way to detect this: it tracked quotes, comments and semicolons but ignored '(' and ')', and it used `options &= OPTION_TOKEN_ONLY` (a compound assignment) in two places, which would have cleared any other option bit once a non-token was seen.",
     "fix_analysis": [
      "Commit 1, frameworks/base core/java/android/database/sqlite/SQLiteTokenizer.java: adds OPTION_CHECK_BRACKETS = 1 << 1. In tokenize(String, int, Consumer) a new `level` counter is kept; after the existing handling of whitespace, tokens, quoted strings, comments and the semicolon check, '(' increments level and ')' decrements it. If level drops below 0 on a ')' and the option is set, or if level != 0 at end of input and the option is set, genException(\"Unbalanced brackets\", sql) is thrown (IllegalArgumentException). Because quoted literals, bracketed identifiers and comments are consumed earlier in the loop, parentheses inside them are not counted (covered by testCheckBracketsInComments).",
      "Commit 1 also fixes `(options &= OPTION_TOKEN_ONLY)` to `(options & OPTION_TOKEN_ONLY)` in both places; without this the new option bit would be cleared as soon as a non-token character was encountered, defeating the check. The `checker` parameter becomes @Nullable and every checker.accept(token) call is guarded by a null check so callers can use tokenize purely for validation.",
      "Commit 2, ContactsProvider src/com/android/providers/contacts/util/SelectionBuilder.java addClause(): after the isEmpty check and before mWhereClauses.add(clause), calls SQLiteTokenizer.tokenize(clause, SQLiteTokenizer.OPTION_CHECK_BRACKETS, null). Since the constructor routes baseSelection through addClause(), both the base selection and later clauses are validated; an unbalanced clause throws IllegalArgumentException instead of being stored. SelectionBuilderTest.testInjection covers both the constructor and addClause paths."
     ],
     "completeness": [
      "Covers every clause that passes through SelectionBuilder.addClause() (constructor and explicit calls), so build() output can no longer have its per-clause parentheses closed by a clause's content.",
      "The check is purely structural: it validates parenthesis balance only. Other content in the clause is still accepted as long as brackets balance; the tokenizer's pre-existing rules (semicolon rejection, quote/comment termination) still apply, but OPTION_TOKEN_ONLY is not used here, so arbitrary expressions remain allowed inside the clause.",
      "Parentheses inside quoted strings, bracket-quoted identifiers and comments are skipped by the tokenizer before the bracket counting runs, so they do not cause false rejections; the test file confirms this for -- and /* */ comments.",
      "Only SelectionBuilder is changed in ContactsProvider. Any other place in the provider that concatenates a caller selection without going through SelectionBuilder is not covered by this commit; the material does not show such places.",
      "The level counter is updated regardless of options but exceptions are only thrown when OPTION_CHECK_BRACKETS is set, so existing callers using OPTION_NONE or OPTION_TOKEN_ONLY see no new rejections (apart from the &= correction, which only matters when more than one option bit is set)."
     ],
     "behavior_changes": [
      "SelectionBuilder constructor and addClause() now throw IllegalArgumentException with message \"Unbalanced brackets in '<clause>'\" for clauses whose parentheses are unbalanced or that close a parenthesis before opening one; previously such clauses were stored and later produced an invalid or injected SQL string.",
      "SQLiteTokenizer gains a new public option OPTION_CHECK_BRACKETS and accepts a null Consumer in tokenize(String, int, Consumer).",
      "Bug fix in SQLiteTokenizer: options are no longer mutated by `&=` during tokenization, so multiple option bits can be combined."
     ],
     "preconditions": [
      "A caller-controlled selection string must reach SelectionBuilder (constructor baseSelection or addClause) in ContactsProvider; the class javadoc states baseSelection is typically the user supplied selection arg.",
      "The selection must contain parentheses that close the provider's wrapping parenthesis for the problematic concatenation to occur.",
      "Which ContactsProvider query entry points feed SelectionBuilder is not shown in the material; the index search only returned the class itself and its unit test."
     ],
     "per_major": {
      "14": "same fix: frameworks/base 9183bfe6574b in android-security-14.0.0_r29 is the identical SQLiteTokenizer change (test file base differs slightly). The ContactsProvider SelectionBuilder commit for 14 is not included in the material; the resolver reports 7/8 lines of the patched region present at that tag.",
      "15": "same fix: ContactsProvider b8e00892122c in android-security-15.0.0_r17 is identical to the 16 SelectionBuilder change (8/8 lines present). The companion frameworks/base SQLiteTokenizer commit for 15 is not shown in the material but is required for OPTION_CHECK_BRACKETS to exist.",
      "16": "same fix: frameworks/base 91cf2b3195ed and ContactsProvider 3c11261b2a6b in android-security-16.0.0_r8, both shown in full; 8/8 lines present.",
      "17": "same fix: frameworks/base 5a73367d5b3a in android-security-17.0.0_r1 is the identical SQLiteTokenizer change. The ContactsProvider commit for 17 is not shown; the resolver reports 7/8 lines of the patched region present."
     },
     "backport_notes": [
      "Apply the frameworks/base SQLiteTokenizer change first: the ContactsProvider change references SQLiteTokenizer.OPTION_CHECK_BRACKETS and passes a null Consumer, both of which only exist after that commit, and the `&=` to `&` correction is needed for the new bit to survive tokenization. SQLiteTokenizer lives in the platform framework rather than a mainline module, so the framework and ContactsProvider must be updated together (general platform knowledge).",
      "The SQLiteTokenizer diff applied with identical hunks on 14, 16 and 17 (same blob ids 7e7c3fb976c7 -> 3ebeb179a5ba), so it should cherry-pick cleanly onto any tree with that file version."
     ],
     "confidence": "high",
     "unknowns": [
      "Which ContactsProvider ContentProvider methods or URIs pass caller selections into SelectionBuilder, and what provider-added restriction clauses the injection could bypass.",
      "The ContactsProvider commits for Android 14 and 17 and the frameworks/base commit for Android 15 are not in the material; only the resolver line counts indicate their presence.",
      "Whether other SQL-concatenation sites in ContactsProvider outside SelectionBuilder exist; the material does not show any."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 0,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45519": {
     "cve": "CVE-2026-45519",
     "summary": "Adds structural checks (content scheme, MediaStore authority, LocalUriMatcher picker path pattern) and a user-ID check to photopicker preselection URIs (EXTRA_PICKER_PRE_SELECTION_URIS), run before the existing permission check in both PickerUriResolver.processUrisForSelection and MediaQueryForPreSelection.",
     "component": "platform/packages/providers/MediaProvider, photopicker preselection URI handling (PickerUriResolver, photopicker v2 MediaQueryForPreSelection)",
     "weakness": "Improper input validation of caller-supplied URIs (no scheme/authority/path check and no user-ID consistency check), leading to information disclosure per the bulletin",
     "root_cause": "Both preselection loops (PickerUriResolver.processUrisForSelection and the equivalent loop in MediaQueryForPreSelection) took each string from the preselection list, called Uri.parse on it, and went straight to the permission check (checkUriPermission(uri, -1, callingUid) in PickerUriResolver; PickerUriResolver.checkUriPermission(appContext, uri, -1, callingPackageUid) in MediaQueryForPreSelection) before adding it to accessibleUris. Nothing verified that the URI was a content:// URI with MediaStore.AUTHORITY and a valid picker path shape. Nothing compared the user ID encoded in path segment 1 (content://media/picker/<user-id>/...) against the current user (UserHandle.myUserId()). A URI naming another user's profile, or with a non-picker shape, could therefore reach the picker database query as a media ID selection. The commit message describes the result as a data mismatch with the active profile; the bulletin rates it ID/High.",
     "fix_analysis": [
      "src/com/android/providers/media/PickerUriResolver.java: adds a static LocalUriMatcher sStaticUriMatcher built on MediaStore.AUTHORITY and a new public static isValidPickerUri(Uri). It returns false if the URI is null or its scheme is not ContentResolver.SCHEME_CONTENT, then runs sStaticUriMatcher.matchUri(uri, /* allowHidden */ false) and accepts only PICKER_ID, PICKER_GET_CONTENT_ID or PICKER_TRANSCODED_ID. Because the matcher is bound to MediaStore.AUTHORITY, this also enforces the authority (per the javadoc and the fake-authority test).",
      "src/com/android/providers/media/PickerUriResolver.java:processUrisForSelection: inside the per-URI try block and before checkUriPermission, skips (continue, Log.d) any URI that fails isValidPickerUri, then skips any URI whose getUserId(uri) differs from UserHandle.myUserId(). Only URIs that pass both checks reach checkUriPermission and accessibleUris.",
      "src/com/android/providers/media/PickerUriResolver.java:getUserId: visibility widened from package-private to public static so the v2 query class can reuse it. It still returns Integer.parseInt(pathSegments.get(1)).",
      "src/com/android/providers/media/photopicker/v2/model/MediaQueryForPreSelection.java: the same two checks are inserted in the preselection loop ahead of PickerUriResolver.checkUriPermission(appContext, uri, -1, callingPackageUid), calling PickerUriResolver.isValidPickerUri and PickerUriResolver.getUserId. Also imports android.os.UserHandle.",
      "Tests: PickerUriResolverTest switches test picker URIs from TEST_USER to UserHandle.myUserId(). PickerDataLayerV2Test mocks mMockContext.getUser() and adds tests checking that cross-user, malformed, fake-authority and invalid-segment preselection URIs are dropped while a valid one is still returned."
     ],
     "completeness": [
      "Covers both preselection entry points visible in the diff: the legacy PickerUriResolver.processUrisForSelection loop and the photopicker v2 MediaQueryForPreSelection loop.",
      "The user-ID check is a strict equality with UserHandle.myUserId() of the MediaProvider process. It runs inside the existing try block, so a non-numeric user segment is stopped either by the LocalUriMatcher pattern or by the surrounding catch clause handling a NumberFormatException. The catch clause is not shown in the material.",
      "Accepted path shapes are limited to PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID. picker_internal and other MediaStore paths are rejected, as the new tests assert.",
      "The fix only filters preselection input. It does not change checkUriPermission or any other consumer of picker URIs. The material shows no other similar unfixed pattern."
     ],
     "behavior_changes": [
      "Preselection URIs that are not content:// URIs on MediaStore.AUTHORITY with a picker, picker_get_content or picker_transcoded path shape are now silently dropped (Log.d) instead of being permission-checked and used.",
      "Preselection URIs whose embedded user ID differs from the current user are now silently dropped, so cross-profile preselected items no longer appear in the picker's preselection result.",
      "New public API surface inside MediaProvider: PickerUriResolver.isValidPickerUri(Uri) is added, and PickerUriResolver.getUserId(Uri) is now public."
     ],
     "preconditions": [
      "A caller supplies preselection URIs (EXTRA_PICKER_PRE_SELECTION_URIS, carried as pre_selection_uris or PickerDataLayer.QUERY_ID_SELECTION in the query args in the tests). These are processed by PickerUriResolver.processUrisForSelection or by the MediaQueryForPreSelection loop. The tests exercise the v2 path through PickerDataLayerV2.queryMediaForPreSelection.",
      "For the cross-user case, the URI must encode a user ID other than the one MediaProvider runs as. The test uses UserHandle.myUserId() + 1 with the same media ID."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29 only 2/8 lines of the patched region are present.",
      "15": "Adapted fix (1b032be59978, android-security-15.0.0_r17). It patches only PickerUriResolver.java and PickerUriResolverTest.java. isValidPickerUri accepts only PICKER_ID and PICKER_GET_CONTENT_ID (no PICKER_TRANSCODED_ID), and the commit adds an androidx Nullable import. It does not touch MediaQueryForPreSelection or change getUserId visibility. 8/8 patched lines present.",
      "16": "Reference fix (32217e55e510, android-security-16.0.0_r8). It changes PickerUriResolver, MediaQueryForPreSelection and both test files. 7/8 patched lines present at the tag; the material does not show which line differs.",
      "17": "Same fix (50d220c204df, android-security-17.0.0_r1). The code changes match 16 apart from indentation differences (the sStaticUriMatcher declaration and one test annotation), and the PickerDataLayerV2Test changes sit at different line offsets. 7/8 patched lines present."
     },
     "backport_notes": [
      "Single commit, no dependency ordering. The 16/17 version needs the LocalUriMatcher constants PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID and the v2 MediaQueryForPreSelection class. Google's Android 15 variant uses two match IDs and patches PickerUriResolver only.",
      "The check compares against UserHandle.myUserId() of the MediaProvider process. The v2 test setup adds a mock for mMockContext.getUser()."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact data disclosed (which columns or items from another profile) is not shown. The material only shows that other-user and malformed URIs could reach the database selection.",
      "The catch clause of the try block around the new checks is not in the material, so how a NumberFormatException from getUserId is handled (non-numeric segment that passes the matcher) is not confirmed.",
      "Whether the LocalUriMatcher patterns for PICKER_ID and the other match IDs restrict the user segment to numeric values is not shown.",
      "Which of the 8 patched-region lines differs at the Android 16 and 17 tags is not shown.",
      "Whether PickerDataLayerV2.queryMediaForPreSelection reaches the MediaQueryForPreSelection loop is not shown directly."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45520": {
     "cve": "CVE-2026-45520",
     "summary": "BiometricsSettingsBase now only honors the EXTRA_USER_ID and EXTRA_KEY_GK_PW_HANDLE intent extras when the hosting SettingsActivity was launched by the Settings package itself; external launches default to the current user and go through the choose-or-confirm lock flow.",
     "component": "platform/packages/apps/Settings, biometrics/combination (CombinedBiometricSettingsActivity / BiometricsSettingsBase)",
     "weakness": "Trusting caller-controlled intent extras for authorization state (missing caller validation, authentication bypass)",
     "root_cause": "In src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java, onAttach() always read Intent.EXTRA_USER_ID from the hosting activity's intent, and onCreate() always read the gatekeeper password handle (EXTRA_KEY_GK_PW_HANDLE, via BiometricUtils.containsGatekeeperPasswordHandle/getGatekeeperPasswordHandle). Per the commit message, external callers could launch CombinedBiometricSettingsActivity with these extras. An unprivileged local app could therefore skip device credential confirmation and change biometric settings (such as Watch Unlock) for any user.",
     "fix_analysis": [
      "BiometricsSettingsBase.java:onAttach: mUserId is now set to UserHandle.myUserId() and a new field mAllowInternalExtras is set to false. Only when getActivity() is a SettingsActivity and activity.getInitialCallingPackage() equals activity.getPackageName() is mAllowInternalExtras set to true and EXTRA_USER_ID read from the intent.",
      "BiometricsSettingsBase.java:onCreate: mGkPwHandle is read from the intent only when mAllowInternalExtras is true and BiometricUtils.containsGatekeeperPasswordHandle(getIntent()) is true. For external callers mGkPwHandle stays 0 and the existing flow goes on to launchChooseOrConfirmLock(), as the new test testExternalCaller_ignoresSensitiveExtras verifies.",
      "CombinedBiometricProfileSettingsTest.java: the test activity is now a SettingsActivity, and ShadowPasswordUtils.setCallingAppPackageName() makes the Settings package the caller. Two tests are added. testInternalCaller_allowsSensitiveExtras checks that user 10 and handle 12345 are accepted. testExternalCaller_ignoresSensitiveExtras sets EXTRA_INITIAL_CALLING_PACKAGE to a foreign package and checks that the user defaults to myUserId, the handle is 0, and launchChooseOrConfirmLock is called."
     ],
     "completeness": [
      "Both sensitive extras that BiometricsSettingsBase reads (EXTRA_USER_ID in onAttach, EXTRA_KEY_GK_PW_HANDLE in onCreate) are gated by the same mAllowInternalExtras decision, so every subclass of the abstract base gets the check.",
      "If the host activity is not a SettingsActivity, both extras are ignored (fail closed).",
      "The check relies on SettingsActivity.getInitialCallingPackage(). Its implementation is not in the material, so how strong the gate is depends on that method identifying the real launcher rather than trusting a value supplied in the intent.",
      "The change only touches BiometricsSettingsBase. Any other Settings fragments that read EXTRA_KEY_GK_PW_HANDLE or EXTRA_USER_ID from their launching intent are not modified by this commit, and none are shown in the material."
     ],
     "behavior_changes": [
      "When CombinedBiometricSettingsActivity is launched by a package other than Settings, EXTRA_USER_ID is ignored and the fragment uses UserHandle.myUserId().",
      "For external launches a supplied gatekeeper password handle is ignored (mGkPwHandle stays 0), so launchChooseOrConfirmLock() runs instead of the credential step being skipped.",
      "Internal Settings navigation (host is a SettingsActivity and the initial calling package is the Settings package) behaves as before."
     ],
     "preconditions": [
      "A local app that can start CombinedBiometricSettingsActivity (the host of a BiometricsSettingsBase subclass) with crafted EXTRA_USER_ID and/or EXTRA_KEY_GK_PW_HANDLE extras (per the commit message)."
     ],
     "per_major": {
      "14": "same fix (a88ca4587208, android-security-14.0.0_r29). The only source adaptation is an added import of android.app.Activity. The Robolectric test class is marked @Ignore on this branch, so the new tests do not run there.",
      "15": "same fix (783b7806995f, android-security-15.0.0_r17). The source change is identical; the test file also adds an import of SetFlagsRule.",
      "16": "same fix (23e5e4077bc9, android-security-16.0.0_r8); this is the analyzed commit.",
      "17": "same fix (759e75cec1c1, android-security-17.0.0_r1); the source change is identical."
     },
     "backport_notes": [
      "The fix depends on SettingsActivity.getInitialCallingPackage(), and the test depends on ShadowPasswordUtils.setCallingAppPackageName()/reset(). Both must exist on the target branch; the patch does not add them on 14 through 17. On a fork where this fragment can be hosted by an activity that is not a SettingsActivity, the extras will be dropped for that host as well."
     ],
     "confidence": "high",
     "unknowns": [
      "How SettingsActivity.getInitialCallingPackage() works out the calling package, and so whether the launching intent can influence it. The test suggests it involves PasswordUtils' calling-app package and the EXTRA_INITIAL_CALLING_PACKAGE extra, but the implementation is not shown.",
      "Which settings operations, beyond Watch Unlock named in the commit message, could be reached with an injected user ID.",
      "Which device configurations (for example, which biometric hardware) make the combined biometrics settings page reachable."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45521": {
     "cve": "CVE-2026-45521",
     "summary": "StorageManagerService.openProxyFileDescriptor now records the Binder calling UID, and AppFuseBridge.openFile rejects the request (the same way it handles an invalid mount) when that UID differs from the UID that created the AppFuse mount scope.",
     "component": "platform/frameworks/base, system_server StorageManagerService / AppFuse proxy file descriptor bridge",
     "weakness": "missing ownership/authorization check (caller UID not compared against mount owner UID)",
     "root_cause": "AppFuseBridge.openFile(int mountId, int fileId, int mode) looked up the MountScope in mScopes by mountId alone. If a scope was found, it waited for the mount and called scope.openFile(mountId, fileId, flags) without checking who the caller was. StorageManagerService.openProxyFileDescriptor, a binder entry point on IStorageManager, passed mountId/fileId/mode straight through without recording Binder.getCallingUid(). A caller that supplied a mountId belonging to another UID could therefore get a ParcelFileDescriptor from that UID's AppFuse mount. This happened even though StorageManagerService creates each scope with an owner UID (new AppFuseMountScope(uid, name)) and the scope has a uid field.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java:openProxyFileDescriptor: adds `final int uid = Binder.getCallingUid();` at entry and passes it as the new first argument to mAppFuseBridge.openFile(uid, mountId, fileId, mode).",
      "services/core/java/com/android/server/storage/AppFuseBridge.java:openFile: the signature becomes openFile(int uid, int mountId, int fileId, int mode). Inside the synchronized(this) block the check becomes `if (scope == null || scope.uid != uid) throw new FuseUnavailableMountException(mountId);`, so a caller with a different UID is rejected before scope.waitForMount() and scope.openFile() run.",
      "openProxyFileDescriptor catches the exception (FuseUnavailableMountException | InterruptedException), logs it with Slog.v and returns null to the binder caller."
     ],
     "completeness": [
      "Covers the only file-open path shown in the material: openProxyFileDescriptor -> AppFuseBridge.openFile -> MountScope.openFile. The ownership check runs under the AppFuseBridge lock, on the same scope object that openFile then uses.",
      "The comparison is an exact UID match against scope.uid, the uid recorded when the AppFuseMountScope was created. Other processes running under the same UID are still allowed. The fix adds no per-fileId authorization.",
      "Mount creation (AppFuseMountScope creation / addBridge) and the native bridge code are unchanged. The material shows no other caller of AppFuseBridge.openFile, so it shows no sibling path left unfixed."
     ],
     "behavior_changes": [
      "When the calling UID is not the UID that created the mount, openProxyFileDescriptor now returns null and logs 'The mount point has already been invalid' at verbose level. Before the fix it could return a descriptor.",
      "AppFuseBridge.openFile has a new leading int uid parameter. Any in-tree or vendor caller that uses the old signature (mountId, fileId, mode) will fail to compile."
     ],
     "preconditions": [
      "The caller can invoke IStorageManager.openProxyFileDescriptor on system_server. The material does not show any permission checks on this binder method.",
      "An AppFuseBridge exists (mAppFuseBridge != null), and a different UID created the MountScope for the supplied mountId (via AppFuseMountScope(uid, name) / addBridge in StorageManagerService)."
     ],
     "per_major": {
      "14": "same fix: commit 0d2ee8e16afc in android-security-14.0.0_r29. The hunks are identical; only the StorageManagerService.java line offsets differ (3571/3581). AppFuseBridge.java blob hashes are identical (292314840efb -> d839b30ddf44).",
      "15": "same fix: commit bca099fcb293 in android-security-15.0.0_r17. The hunks are identical, at offsets 3729/3739. The AppFuseBridge.java hunk is byte-identical.",
      "16": "same fix: commit 63f908ebd9ee in android-security-16.0.0_r8 (the analysed commit), at offsets 3829/3839.",
      "17": "same fix: commit 03988cf95e56 in android-security-17.0.0_r1. The hunks are identical, at offsets 3793/3803."
     },
     "backport_notes": [
      "This is a single self-contained commit. Both files must change together because the openFile signature changes. The fixed check relies on MountScope having a populated uid field; the material confirms this only through the Android 16 code context. The AppFuseBridge.java pre-fix and post-fix blob hashes are the same on 14 through 17, so that hunk should apply cleanly. Only the StorageManagerService.java hunk offsets move."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show whether ordinary third-party apps can reach openProxyFileDescriptor or whether a permission or SELinux check gates it.",
      "The declaration and visibility of MountScope.uid are not shown, only its use in the fixed check and in the AppFuseMountScope(uid, name) construction.",
      "The name of the StorageManagerService method that creates AppFuseMountScope is not visible in the excerpt.",
      "The material does not explain the per-major note '7/8 lines of the patched region present'. The diffs themselves are identical on all four branches."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45525": {
     "cve": "CVE-2026-45525",
     "summary": "Makes MediaProvider's ISO/MP4 box parser keep parsing when an XMP box exceeds the allocation limit, keeps the oversized box's file offsets, and has XmpDataParser mark the whole XMP range for redaction when the XMP bytes could not be loaded.",
     "component": "platform/packages/providers/MediaProvider, metadata redaction (util/IsoInterface, util/XmpDataParser)",
     "weakness": "Fail-open error handling in redaction: an oversized input element stopped metadata parsing and was treated as absent, so location metadata was left unredacted",
     "root_cause": "In src/com/android/providers/media/util/IsoInterface.java, an XMP box (either a uuid box with XMP_UUID or a BOX_XMP box) could be larger than the limit enforced by allocateBuffer (1 MiB per the commit message). In that case allocateBuffer returned null and the box parser did `return false`. Per the commit message, this stopped parsing and skipped all later boxes. In src/com/android/providers/media/util/XmpDataParser.java, XmpData.extractXmpData(IsoInterface) treated a null buffer as \"no XMP found\". It fell back to BOX_XMP and, if that was also null, replaced both the buffer and the offsets with empty arrays. getRedactionRanges(XmpData) then parsed an empty document and returned no ranges. As a result, an oversized XMP box got no XMP redaction ranges, and the boxes after it were not parsed. The commit describes this as a location redaction bypass.",
     "fix_analysis": [
      "IsoInterface.java, box parsing code (the branch that handles BOX_UUID/BOX_XMP/BOX_META, around lines 183-217): both `if (box.data == null) return false;` statements become `if (box.data != null) { Os.read(...) }`. The box is still created with range `new long[]{pos, len}` and parsing continues to the following boxes. Only the data read is skipped when allocateBuffer returns null.",
      "XmpDataParser.java, XmpData.extractXmpData(IsoInterface): the fallback to `iso.getBoxBytes(IsoInterface.BOX_XMP)` / `getBoxRanges(BOX_XMP)` now runs only when `buf == null && xmpOffsets.length == 0`. When buf is null but offsets exist (an oversized box), buf becomes `new byte[0]`. The line `xmpOffsets = new long[0];` is removed, so the offsets are kept in the XmpData.",
      "XmpDataParser.java, getRedactionRanges(XmpData): new early return. If `xmpData.mRawXmp.length == 0 && xmpData.mXmpOffsets.length > 0`, it returns `LongArray.fromArray(xmpData.mXmpOffsets, ...)`, so the whole XMP box range is redacted instead of an empty document being parsed.",
      "Test: adds tests/res/raw/gps_test_xmp_big.mp4 (4301936 bytes) and XmpDataParserTest#testContainer_IsoRedactionRanges_OversizedXmp. The test checks that the box range is present, that getRedactionRanges returns exactly two values with end > start > 0, and that createXmpInterface returns an empty redacted XMP."
     ],
     "completeness": [
      "Covers both ISO XMP carriers (uuid box with XMP_UUID, and BOX_XMP) in the oversized case, and restores parsing of the boxes after the oversized box.",
      "On Android 15/16/17 only the IsoInterface / XmpData.extractXmpData(IsoInterface) path changes. XmpData.extractXmpData(ExifInterface) and getRedactionRanges(ExifInterface) are not touched. The Android 14 variant also converts Exif TAG_XMP ranges from [start,length] to [start,end] in XmpInterface. The material does not show whether the Exif path on 15+ already handles this correctly.",
      "extractXmpData(IsoInterface) now deliberately skips the BOX_XMP fallback when the uuid XMP box exists but is oversized. Judging from the code, if a file has both an oversized uuid XMP box and a normal BOX_XMP, only the uuid box range is redacted and the BOX_XMP content is not parsed for ranges.",
      "getRedactionRanges(XmpData) returns the raw mXmpOffsets array. This assumes the IsoInterface range already has the [start,end] shape that callers expect. The test only checks that actualRanges[1] > actualRanges[0].",
      "Box.data can now be null for a box that is in the flattened list. The test shows that createXmpInterface handles this. Other consumers of getBoxBytes are not shown."
     ],
     "behavior_changes": [
      "Files whose XMP box is above the allocation limit are now parsed past that box, so the boxes after it are visible to MediaProvider.",
      "For such files the whole XMP box range is reported as a redaction range.",
      "For an oversized XMP box, XmpData now holds empty bytes with non-empty offsets, and createXmpInterface returns an empty redacted XMP.",
      "There is no fallback to BOX_XMP when the uuid XMP box is present but oversized."
     ],
     "preconditions": [
      "An ISO-BMFF/MP4 media file whose XMP box (uuid XMP_UUID or BOX_XMP) is larger than the allocateBuffer limit (1 MiB per the commit message).",
      "The file is read through a path where MediaProvider computes redaction ranges. Per index search, MediaProvider.getRedactionRangesForFuse calls RedactionUtils.getRedactionRanges(file), and the index lists it among the users of XmpDataParser.getRedactionRanges. The full call chain is not shown."
     ],
     "per_major": {
      "14": "adapted fix (93cf0194cff2, android-security-14.0.0_r29). The 14 code base allocates XMP buffers inline instead of through allocateBuffer, and the change is made in XmpInterface instead of XmpDataParser. Google added MAX_XMP_SIZE_BYTES (1 MiB) and a public XMP_UUID constant to IsoInterface, and replaced the `return null` aborts with logging so parsing continues. XmpInterface.process now accepts a null rawXmp and adds [xmpOffsets[0], xmpOffsets[1]] to mRedactedRanges. Exif TAG_XMP ranges are converted from [start,length] to [start,end] in XmpInterface. Same test resource, with an adapted test in XmpInterfaceTest.",
      "15": "same fix (ec0834680bfe, android-security-15.0.0_r17): the IsoInterface and XmpDataParser diff is identical to the 16 commit.",
      "16": "same fix (cedfc5086b8d, android-security-16.0.0_r8): RESTRICT AUTOMERGE cherry-pick, Change-Id I70fabd8ded71b60cc7ed7c197770a725ee4d8630.",
      "17": "same fix (21270fd56118, android-security-17.0.0_r1): the diff is identical to 16."
     },
     "backport_notes": [
      "Trees that have allocateBuffer in IsoInterface and XmpData.extractXmpData in XmpDataParser (15+) can take the 16 diff as-is. Trees still on XmpInterface with inline allocation (14-era) should use the Android 14 variant, which also adds the 1 MiB limit and the null-rawXmp handling in XmpInterface.process.",
      "The test needs the binary resource tests/res/raw/gps_test_xmp_big.mp4 (about 4.3 MB) and a stageMp4File/stageFile helper in the test class."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the name of the IsoInterface method that contains the patched box parsing code.",
      "Whether the Exif (JPEG/DNG) XMP path in XmpDataParser on 15+ has a similar oversized-data or offset issue. Only Android 14's variant touches the Exif path.",
      "The allocateBuffer threshold of 1 MiB comes from the commit message. The function body is not shown.",
      "Which redaction ranges outside XMP (for example GPS boxes) depend on the boxes that used to be skipped, and which metadata was leaked in the reported case, beyond 'location redaction bypass'.",
      "The full call chain from FUSE reads to XmpDataParser.getRedactionRanges(IsoInterface)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45527": {
     "cve": "CVE-2026-45527",
     "summary": "Reorders convertCleanApertureToRect() in HeifCleanAperture.cpp so the crop-rect bounds check is done in int64_t before the int32_t additions that compute right and bottom. This prevents a signed-integer overflow (IntSan abort) in media.extractor on crafted CleanAperture values.",
     "component": "platform/frameworks/av, MP4/HEIF extractor (media/module/extractors/mp4, HeifCleanAperture)",
     "weakness": "Signed integer overflow (the bounds check ran after an overflowing addition, and in the wrong arithmetic domain for it)",
     "root_cause": "In media/module/extractors/mp4/HeifCleanAperture.cpp:convertCleanApertureToRect(), *left and *top are int32_t values derived from centerX.getInt32()/centerY.getInt32() after the file-supplied horizOff/vertOff are added. They were added to clapW/clapH (int32_t, the file-supplied clean aperture width/height) to produce *right and *bottom before any range check. The later check (`*left > (UINT32_MAX - clapW) || *right > imageW || ...`) ran only after the addition and in unsigned arithmetic, so it could not stop the int32_t addition itself from overflowing. With crafted clap values (for example horizOff near INT32_MAX with a small image, as in the new test cases) the addition overflows. IntSan treats this as fatal and aborts the media.extractor process (DoS per the bulletin).",
     "fix_analysis": [
      "media/module/extractors/mp4/HeifCleanAperture.cpp:convertCleanApertureToRect(): the two additions `*right = *left + clapW;` and `*bottom = *top + clapH;` are moved after the bounds check.",
      "Same function: the bounds check is rewritten as `(int64_t)*left + clapW > (int64_t)imageW || (int64_t)*top + clapH > (int64_t)imageH`, which does the sum in int64_t so it cannot overflow. Earlier in the function, imageW/imageH > INT32_MAX are rejected (line 122) and negative centerX.n/centerY.n are rejected (lines 137-139). So passing the check guarantees that left+clapW and top+clapH fit in int32_t, and the int32_t additions that follow are safe.",
      "The old `*left > (UINT32_MAX - clapW)` and `*top > (UINT32_MAX - clapH)` terms are dropped because the int64_t comparison against imageW/imageH subsumes them.",
      "media/module/extractors/mp4/tests/HeifCleanApertureUnitTest.cpp: two entries are added to kInvalidClapPropertyTestParams (image 2x2, clap 2x2, horizOff or vertOff = 0x7FFFFFFE). They target the overflow and must now return false."
     ],
     "completeness": [
      "Both overflowing additions in the function (right from left+clapW, bottom from top+clapH) are covered by the reordered int64_t check.",
      "The earlier Fraction operations (centerX/centerY add/subtract) return bool, and the function checks their results. imageW/imageH > INT32_MAX is already rejected. The material does not show the bodies of Fraction::add/subtract or calculateCenter, though an overflowsInt32 helper exists in the file (per index search).",
      "The fix changes only convertCleanApertureToRect(); no other HEIF/MP4 extractor code is touched. The material shows no other similar unguarded additions."
     ],
     "behavior_changes": [
      "Crafted clap properties whose crop rect would exceed the image now make convertCleanApertureToRect() return false instead of causing an IntSan abort of media.extractor.",
      "*right and *bottom are no longer written when the function returns false (before the fix they were assigned ahead of the check). *left and *top are still written before the check, as before.",
      "Otherwise, which rects are accepted or rejected does not change: rects fully inside the image still succeed."
     ],
     "preconditions": [
      "A HEIF/MP4 file with a CleanAperture (clap) property is parsed by the MP4 extractor in the media.extractor process (per commit message). This reaches convertCleanApertureToRect() with crafted width/height/horizOff/vertOff fractions.",
      "The clap values must pass the earlier checks (positive denominators, non-negative integer width/height, non-negative centerX/centerY) while making left+clapW or top+clapH exceed INT32_MAX.",
      "The commit describes the crash as an IntSan abort, so the integer sanitizer is enabled for the extractor."
     ],
     "per_major": {
      "14": "not affected: the bulletin does not list Android 14, and the patched files (media/module/extractors/mp4/HeifCleanAperture.cpp and its unit test) are absent at android-security-14.0.0_r29.",
      "15": "same fix: platform/frameworks/av c92a92203957 in android-security-15.0.0_r17, with a diff identical to the Android 16 commit.",
      "16": "same fix: platform/frameworks/av 9456667afa01 in android-security-16.0.0_r8 (cherry-pick, Change-Id I2b282fc9e69a6510e9414fd2bbfe3beddf81001e).",
      "17": "same fix: platform/frameworks/av 1e00537f8f43 in android-security-17.0.0_r1, identical diff."
     },
     "backport_notes": [
      "This is a single self-contained commit, and the diff applies identically on 15, 16 and 17 (same pre-image index f0a086703e). A fork must already have the imageW/imageH > INT32_MAX check and the centerX.n/centerY.n < 0 check above the patched block, because the new int64_t check relies on them to keep the later int32_t additions safe. The mp4 extractor lives in the media extractor module (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which caller in the MP4 extractor invokes convertCleanApertureToRect() or how the parsed rect is used downstream.",
      "The material does not show any impact of the overflow on builds without IntSan beyond an incorrect (rejected) rect. The bulletin classifies it as DoS only.",
      "The bodies of Fraction::add/subtract and calculateCenter are not shown, so their internal overflow handling cannot be confirmed from the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45528": {
     "cve": "CVE-2026-45528",
     "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp on behalf of the target package and its uid. Before, it called PendingIntent.getActivity on a package Context derived from the system server's own Context, with the calling identity cleared.",
     "component": "platform/frameworks/base, services/core StorageManagerService (system_server storage service)",
     "weakness": "PendingIntent created under the wrong (system) creator identity: privilege confusion / confused deputy when minting a PendingIntent for a third-party package",
     "root_cause": "In services/core/java/com/android/server/StorageManagerService.java, getManageSpaceActivityIntent is a Binder API meant for apps holding MANAGE_EXTERNAL_STORAGE (see the hasExternalStorageAccess check at the top of the method). It cleared the caller's Binder identity and then created the PendingIntent with PendingIntent.getActivity(targetAppContext, ...). targetAppContext came from mContext.createPackageContextAsUser(packageName, 0, user), or from createPackageContext(packageName, 0) on Android 14/15. The commit message says this let callers obtain a PendingIntent registered under the target package identity with system privileges. The PendingIntent was then returned to the calling file-manager app.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java:getManageSpaceActivityIntent: removes the Binder.clearCallingIdentity()/restoreCallingIdentity() block and the Context creation through mContext.createPackageContextAsUser(...) (createPackageContext on 14/15).",
      "Same function: the Intent (ACTION_DEFAULT, setClassName(packageName, appInfo.manageSpaceActivityName), FLAG_ACTIVITY_NEW_TASK) and the ActivityOptions with MODE_BACKGROUND_ACTIVITY_START_DENIED are kept unchanged.",
      "Same function: gets ActivityManagerInternal via LocalServices.getService(ActivityManagerInternal.class) and calls amInternal.getPendingIntentActivityAsApp(requestCode, intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, options.toBundle(), packageName, appInfo.uid). This passes the target app's package name and uid as the creator. appInfo.uid was resolved earlier via mIPackageManager.getApplicationInfo for the caller's user.",
      "Same function: the catch (PackageManager.NameNotFoundException) -> IllegalArgumentException(\"packageName not found\") path is dropped. A missing package is already handled earlier by the appInfo == null check, which throws IllegalArgumentException(\"Invalid packageName\")."
     ],
     "completeness": [
      "The fix covers the single PendingIntent creation site in getManageSpaceActivityIntent. It is the only place in the diff and the surrounding context where a PendingIntent is created on behalf of another package.",
      "The caller-side authorization (hasExternalStorageAccess on packagesFromUid[0], getApplicationInfo for the caller's user) is unchanged. The fix only changes how and under whose identity the PendingIntent is created.",
      "The background activity start restriction (MODE_BACKGROUND_ACTIVITY_START_DENIED) is kept unchanged.",
      "The material does not show whether other system services use the same createPackageContext + PendingIntent.getActivity pattern."
     ],
     "behavior_changes": [
      "The PendingIntent is now created via getPendingIntentActivityAsApp with the target packageName and appInfo.uid as creator, instead of PendingIntent.getActivity from a system-server-derived package Context.",
      "The Binder calling identity is no longer cleared around PendingIntent creation.",
      "The IllegalArgumentException(\"packageName not found\") from NameNotFoundException can no longer occur. Invalid packages still fail earlier with IllegalArgumentException(\"Invalid packageName\")."
     ],
     "preconditions": [
      "Caller holds MANAGE_EXTERNAL_STORAGE (mStorageManagerInternal.hasExternalStorageAccess passes for the calling uid) and, per the code comment, has package visibility of the target package.",
      "Target package resolves via getApplicationInfo for the caller's user and declares a manageSpaceActivity (appInfo.manageSpaceActivityName != null). Otherwise null is returned before the PendingIntent code runs.",
      "Reached via the IStorageManager Binder method getManageSpaceActivityIntent(packageName, requestCode)."
     ],
     "per_major": {
      "14": "same fix (adapted context): platform/frameworks/base 51b647175642 in android-security-14.0.0_r29. The removed code used mContext.createPackageContext(packageName, 0) rather than createPackageContextAsUser; the replacement code is identical.",
      "15": "same fix (adapted context): platform/frameworks/base 8c8709a5ad8c in android-security-15.0.0_r17. The removed code used createPackageContext(packageName, 0); the replacement code is identical.",
      "16": "same fix: platform/frameworks/base 84d85dbe121f in android-security-16.0.0_r8. The removed code used createPackageContextAsUser with the caller's user.",
      "17": "same fix: platform/frameworks/base 43ef4a09e448 in android-security-17.0.0_r1. Same change as the Android 16 diff (different hunk offset)."
     },
     "backport_notes": [
      "Single-file, single-hunk change. Its only new dependency is ActivityManagerInternal.getPendingIntentActivityAsApp(requestCode, intent, flags, bundle, packageName, uid), which Google's fix calls on all four majors. Forks that still call createPackageContext (not AsUser) match the 14/15 variant of the hunk; the replacement block is the same everywhere."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show what a holder of the old PendingIntent could achieve in practice beyond the commit message's statement. The bulletin rates it High EoP.",
      "The material does not show whether getPendingIntentActivityAsApp performs additional checks (e.g. on appInfo.uid vs user), or how it attributes later sends.",
      "On 14/15 the old code used createPackageContext(packageName, 0), not a per-user context. The material does not show how the new appInfo.uid-based creation affects which user's instance is targeted."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45529": {
     "cve": "CVE-2026-45529",
     "summary": "MmsSmsProvider.query() now applies the per-user subscription filter (selectionBySubIds) to five URI paths: simple conversations, conversation recipients, conversation subject, search and searchSuggest. It returns an empty cursor when the calling user has no associated subscriptions.",
     "component": "platform/packages/providers/TelephonyProvider, MmsSmsProvider (content://mms-sms)",
     "weakness": "Missing authorization filter: several query paths did not limit results to the subscriptions associated with the calling user (improper access control across users/subscriptions)",
     "root_cause": "MmsSmsProvider.query() already had a per-user subscription selection (selectionBySubIds) and an emptyCursor. The test mocks suggest selectionBySubIds is based on SubscriptionManager.getSubscriptionInfoListAssociatedWithUser. Several case branches ignored this selection. URI_CONVERSATIONS in simple mode (getSimpleConversations), URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT (getConversationById), URI_SEARCH_SUGGEST (raw SEARCH_QUERY on the words table) and URI_SEARCH (getTextSearchQuery over the sms and pdu tables) all ran their SQL with no subscription constraint. As a result, they could return conversation metadata, search snippets and matching message rows for subscriptions not associated with the caller's user.",
     "fix_analysis": [
      "src/com/android/providers/telephony/MmsSmsProvider.java:query(), case URI_CONVERSATIONS (simple branch): if selectionBySubIds == null, log and return emptyCursor. Otherwise set selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds) before calling getSimpleConversations().",
      "MmsSmsProvider.java:query(), case URI_CONVERSATIONS_RECIPIENTS and case URI_CONVERSATIONS_SUBJECT: the same null check returning emptyCursor, and selectionBySubIds is concatenated into selection before getConversationById().",
      "MmsSmsProvider.java:query(), case URI_SEARCH_SUGGEST: a null check returning emptyCursor. The constant SEARCH_QUERY is shortened to '... WHERE index_text MATCH ?', and the query is built at runtime as SEARCH_QUERY + \" AND \" + selectionBySubIds.replace(\"'\", \"\") + \" ORDER BY snippet LIMIT 50;\" before db.rawQuery().",
      "MmsSmsProvider.java:query(), case URI_SEARCH: a null check returning emptyCursor, placed after an existing check that throws ('... with this query'). getTextSearchQuery() now receives selectionBySubIds.",
      "MmsSmsProvider.java:getTextSearchQuery(String smsTable, String pduTable, String selectionBySubIds): builds smsSubIdClause = \" AND \" + smsTable + \".\" + selectionBySubIds.replace(\"'\", \"\"), and mmsSubIdClause the same way with pduTable. They are inserted into the WHERE clauses of the sms sub-query and the mms/part sub-query. Each clause is an empty string when selectionBySubIds is empty.",
      "tests/src/com/android/providers/telephony/MmsSmsProviderTest.java: adds testQuery_subIdEmpty_returnsEmptyCursor, which mocks an empty getSubscriptionInfoListAssociatedWithUser and asserts a count of 0 for the five URIs. Also adds testQuery_subIdValid_doesNotReturnEmptyCursor, which only asserts that a non-null cursor is returned."
     ],
     "completeness": [
      "Covers exactly five URI paths: conversations?simple=true, conversations/<id>/recipients, conversations/<id>/subject, search?pattern= and searchSuggest?pattern=.",
      "This diff does not modify the non-simple URI_CONVERSATIONS branch or the other case labels in query() (for example URI_MESSAGE_ID_TO_THREAD, visible as context). The material does not show whether they already applied selectionBySubIds.",
      "The subscription clause is added by string concatenation, with quotes stripped by replace(\"'\", \"\"), rather than through bound arguments. In getTextSearchQuery the clause is qualified with the table name. In URI_SEARCH_SUGGEST it is appended unqualified to a query whose only FROM table is words. The material does not show whether the words table has a matching column.",
      "getTextSearchQuery skips the clause when selectionBySubIds is empty (TextUtils.isEmpty), while the URI_SEARCH caller only returns emptyCursor on null. What happens with an empty string therefore depends on how selectionBySubIds is computed earlier in query(), which is not in the material.",
      "The fix is not behind a feature flag (Flag: EXEMPT BUGFIX). On Android 17, only the tests carry @DisableFlags, for an unrelated RCS flag."
     ],
     "behavior_changes": [
      "Queries on the five affected URIs return an empty cursor (not null) when no subscriptions are associated with the calling user, and each path logs a Log.d line.",
      "Results for those URIs are now restricted by the selectionBySubIds clause. Messages and conversations outside the user's subscriptions should no longer appear in simple conversation lists, recipients/subject lookups, search results or search suggestions.",
      "SEARCH_QUERY no longer contains ORDER BY/LIMIT. Both are re-appended at runtime after the subscription clause, so ordering and the 50-row limit for searchSuggest are unchanged."
     ],
     "preconditions": [
      "The caller must be able to query content://mms-sms/ URIs handled by MmsSmsProvider.query(). The provider's own permission/AppOps checks are not in the material; normally READ_SMS-class access is required (general platform knowledge).",
      "The calling user must be associated with none or only some of the device's subscriptions, so that unfiltered rows differ from filtered rows (for example in multi-user or work-profile setups (general platform knowledge)).",
      "The code is reached through the conversations?simple=true, conversations/<id>/recipients, conversations/<id>/subject, search?pattern= or searchSuggest?pattern= URIs."
     ],
     "per_major": {
      "14": "Same fix: commit 475853ddf6b3 in android-security-14.0.0_r29. The hunks are identical to 16, only the line offsets differ slightly; the original getTextSearchQuery(smsTable, pduTable) signature is the same as in 16.",
      "15": "Same fix: commit 4fab244beaa3 in android-security-15.0.0_r17. The diff is identical to the 16 commit (same blob ids dd9f07cf..0fa97e1f).",
      "16": "Fix commit 190c0a3b8e44 in android-security-16.0.0_r8 (cherry-pick of internal e1b034dfe8f4, Change-Id I64fa9a2e334f97f7d6a9c5e62edff73f8e7294c1).",
      "17": "Adapted fix: commit 1fab99e951d5 in android-security-17.0.0_r1. getTextSearchQuery already took canReadRestrictedMessages and otpFilter, so selectionBySubIds is added as a fifth parameter and the subscription clauses go after the read-restriction/otp clauses. The URI_SEARCH_SUGGEST check is inserted after an existing otpFilter check. The getSimpleConversations/getConversationById calls carry extra parameters. The tests gain @DisableFlags(Flags.FLAG_SECURE_ACCESS_TO_RESTRICTED_RCS_MESSAGES)."
     },
     "backport_notes": [
      "The patch relies on selectionBySubIds and emptyCursor already being defined earlier in MmsSmsProvider.query(). The diffs use them without adding them on all four branches. A target tree without that existing per-user subscription code needs it first.",
      "On trees with the restricted-RCS/OTP changes (Android 17 style), use the 17 variant: extend the existing getTextSearchQuery signature instead of replacing it, and keep the new URI_SEARCH_SUGGEST check after the otpFilter check."
     ],
     "confidence": "high",
     "unknowns": [
      "How selectionBySubIds is computed earlier in query() (its exact SQL form, and when it is null versus empty). This decides how the replace(\"'\", \"\") and TextUtils.isEmpty handling behave.",
      "Whether the words table has a column that the unqualified clause appended in URI_SEARCH_SUGGEST can use.",
      "Whether the query() branches left untouched (non-simple URI_CONVERSATIONS, URI_MESSAGE_ID_TO_THREAD and others) already filtered by subscription before this change.",
      "The provider-level permission checks guarding these URIs, and the concrete cross-user scenario behind bug 418776187."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49895": {
     "cve": "CVE-2026-49895",
     "summary": "Raises the minimum accepted EHT Operation element length constant in src/common/ieee802_11_defs.h from 1 to 5 so the length check in get_eht_operation_channel_width() accounts for the fixed oper_params and basic_eht_mcs_nss_set[4] fields that precede the operation information.",
     "component": "platform/external/wpa_supplicant_8, src/common IEEE 802.11 element parsing (EHT Operation element)",
     "weakness": "Incorrect bounds check constant leading to heap out-of-bounds read (information disclosure)",
     "root_cause": "The macro EHT_OPERATION_IE_MIN_LEN (named EHT_OPERATION_ELEM_MIN_LEN on Android 17) was defined as 1. Per the commit message, this value predated the addition of basic_eht_mcs_nss_set[4] to struct ieee80211_eht_operation, so the fixed part of the element is actually 5 octets (1 octet of operation parameters plus 4 octets of basic EHT MCS/NSS set) before the optional operation information. get_eht_operation_channel_width() validated the element against this too-small minimum. An element with length 1 to 4 and the EHT_OPER_INFO_PRESENT bit set passed the check, and the code then read oper_info at offset 5, past the end of the received element data (heap OOB read; the commit message says a manual ASan reproduction confirmed the fix).",
     "fix_analysis": [
      "src/common/ieee802_11_defs.h: EHT_OPERATION_IE_MIN_LEN changed from 1 to 5 (Android 17: EHT_OPERATION_ELEM_MIN_LEN 1 -> 5). This is the only code change. The comment is also updated to reference IEEE Std 802.11be-2024, 9.4.2.321.",
      "Effect: any length check in get_eht_operation_channel_width() (and any other user of this macro) now requires at least 5 octets, which covers the fixed fields preceding oper_info, so oper_info at offset 5 is only accessed when the element is at least that long. The checking function itself is not modified and is not shown in the material."
     ],
     "completeness": [
      "The fix only corrects the constant. It relies on the existing check in get_eht_operation_channel_width() comparing the element length against this macro. The body of that function is not in the material, so the material cannot confirm whether it also verifies that the operation information (EHT_OPER_INFO_MIN_LEN 3, plus 2 octets when EHT_OPER_DISABLED_SUBCHAN_BITMAP_PRESENT is set) fits inside the element.",
      "Other parsers of the EHT Operation element that do not use this macro would not be affected by this change. None are shown in the material.",
      "EHT_OPER_INFO_MIN_LEN (3) and EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE (2) are unchanged."
     ],
     "behavior_changes": [
      "Stricter validation: any length check using EHT_OPERATION_IE_MIN_LEN / EHT_OPERATION_ELEM_MIN_LEN now treats EHT Operation elements shorter than 5 octets as too short. The material does not show how the calling code handles an element that fails the check."
     ],
     "preconditions": [
      "wpa_supplicant parses an EHT Operation element from received over-the-air data and reaches get_eht_operation_channel_width().",
      "Element length between 1 and 4 inclusive with the EHT_OPER_INFO_PRESENT bit set in the operation parameters octet (per the commit message).",
      "The specific frame types and code paths that call get_eht_operation_channel_width() are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 0ce102474997 in android-security-14.0.0_r29 changes EHT_OPERATION_IE_MIN_LEN 1 -> 5 and updates the comment (same hunk, different line offset 2493).",
      "15": "same fix: commit 234d284145fc in android-security-15.0.0_r17 changes EHT_OPERATION_IE_MIN_LEN 1 -> 5 and updates the comment (line offset 2513, minor whitespace difference in the define).",
      "16": "same fix: commit 6579923889d6 in android-security-16.0.0_r8 changes EHT_OPERATION_IE_MIN_LEN 1 -> 5 and updates the comment (line offset 2677).",
      "17": "adapted fix: commit 14398e6a6663 in android-security-17.0.0_r1 changes the renamed macro EHT_OPERATION_ELEM_MIN_LEN 1 -> 5. The comment already referenced 802.11be-2024 on this branch, so only the single define line changes."
     },
     "backport_notes": [
      "Single one-line constant change. On branches older than Android 17 the macro is EHT_OPERATION_IE_MIN_LEN; on 17 it is EHT_OPERATION_ELEM_MIN_LEN. Check that the target tree's struct ieee80211_eht_operation contains basic_eht_mcs_nss_set[4] before oper_info and that get_eht_operation_channel_width() checks the length against this macro. Otherwise the constant change alone does not close the read."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of get_eht_operation_channel_width() and struct ieee80211_eht_operation are not in the material. The offset-5 access and the check against the macro are known only from the commit message.",
      "Which frames or callers lead to get_eht_operation_channel_width() and whether other code uses the macro.",
      "Whether the function also bounds-checks the variable-length operation information after the fixed 5 octets.",
      "How a too-short element is handled once it fails the minimum-length check."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49913": {
     "cve": "CVE-2026-49913",
     "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now tests the mmap() result for MAP_FAILED instead of NULL and unmaps the source heap via a ScopeGuard on every return path after a successful map.",
     "component": "platform/hardware/interfaces, cas/aidl/default (android.hardware.cas AIDL default HAL, DescramblerImpl)",
     "weakness": "Incorrect check of function return value (mmap failure compared against NULL rather than MAP_FAILED), leading to use of an invalid pointer; plus a missing munmap (resource leak).",
     "root_cause": "In cas/aidl/default/DescramblerImpl.cpp, DescramblerImpl::descramble mmaps srcBuffer.heapBase and stores the result in srcPtr. The failure check was `if (srcPtr == NULL)`, but mmap() reports failure with MAP_FAILED, not NULL (per the commit message). When the mapping failed, the check did not fire and execution continued with MAP_FAILED as the base pointer, which the commit message says led to an arbitrary-address memory access. The commit message also says the fix adds munmap() cleanup to prevent memory leaks, which means the mapping was previously leaked.",
     "fix_analysis": [
      "cas/aidl/default/DescramblerImpl.cpp: DescramblerImpl::descramble: the failure check changes from `srcPtr == NULL` to `srcPtr == MAP_FAILED`. On failure the function still logs 'Failed to map src buffer.' and returns toStatus(BAD_VALUE), so a failed mmap is now caught before validateRangeForSize and before srcPtr is used.",
      "cas/aidl/default/DescramblerImpl.cpp: DescramblerImpl::descramble: right after the successful-map check, a `::android::base::make_scope_guard` lambda is installed. It captures srcPtr and srcBuffer.heapBase.size, so munmap(srcPtr, size) runs on every later exit from the function, which closes the leak described in the commit message.",
      "cas/aidl/default/DescramblerImpl.cpp: adds `#include <android-base/scopeguard.h>` for make_scope_guard."
     ],
     "completeness": [
      "The fix covers the single mmap result check for the source buffer (srcPtr) in DescramblerImpl::descramble and adds unmapping for that mapping only.",
      "The diff context does not show the mmap() call itself or whether a destination buffer is mapped separately in this function. Only the src check is changed, so the material does not show any other mapping in this function.",
      "The scope guard is installed after the MAP_FAILED check, so the failure path does not call munmap (correct, since nothing was mapped).",
      "The munmap length is srcBuffer.heapBase.size. The material does not show the length passed to mmap, so a correct unmap depends on mmap also using heapBase.size.",
      "Only the AIDL default implementation (cas/aidl/default) is changed. The material does not show whether the HIDL default CAS implementations have the same pattern."
     ],
     "behavior_changes": [
      "A failed mmap of the client heap now returns BAD_VALUE to the caller. Before the fix, execution continued with an invalid pointer.",
      "The source heap mapping is now unmapped whenever descramble returns after a successful map, including on success.",
      "The file now includes android-base/scopeguard.h."
     ],
     "preconditions": [
      "A client able to invoke descramble on the default AIDL CAS HAL with a SharedBuffer whose heapBase mapping fails. The code comment says the SharedBuffer offset and size are client controlled.",
      "Per index search, related descramble callers include frameworks/base MediaDescrambler.descramble (through its descrambler wrapper; the native_descramble JNI path shown belongs to the HIDL wrapper) and frameworks/av ATSParser::Stream::flushScrambled. The material does not show which of these reach the AIDL DescramblerImpl.",
      "The device must use the default AIDL CAS HAL implementation (cas/aidl/default) or a vendor implementation derived from it."
     ],
     "per_major": {
      "14": "same fix: platform/hardware/interfaces 8858a5521ba4 in android-security-14.0.0_r29, identical diff to DescramblerImpl::descramble (file mode 100755 there).",
      "15": "same fix: platform/hardware/interfaces 9bdc5c54c702 in android-security-15.0.0_r17, identical diff.",
      "16": "same fix: platform/hardware/interfaces 0b6f21543e54 in android-security-16.0.0_r8 (cherry-pick of fde26328df1f), the analysed commit.",
      "17": "same fix: platform/hardware/interfaces d9f6b94ba0f0 in android-security-17.0.0_r1, identical hunk shifted by one line (starts at line 101 instead of 100)."
     },
     "backport_notes": [
      "The patch is a self-contained single-hunk change applied identically on 14 through 17. Its only new dependency is android-base/scopeguard.h, so check that the cas default HAL target can use it in your tree.",
      "At each security tag, the per-major facts report 7 of 8 patched-region lines present, yet the resolver marks all four majors as resolved. The material does not explain the missing line, so when confirming a tree, check that both the MAP_FAILED check and the scope guard are present."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the mmap() call or its arguments (notably the length), or whether a dst buffer is mapped in the same function.",
      "Beyond the commit message, the material does not show whether the pre-fix code ever unmapped srcPtr.",
      "The material does not show whether the HIDL default CAS implementations contain the same NULL-vs-MAP_FAILED check.",
      "The material does not explain why only 7 of 8 patched-region lines are present at each security tag.",
      "The material does not show which framework callers reach the AIDL DescramblerImpl rather than a HIDL descrambler.",
      "The material does not show whether the target's build configuration links libbase."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55290": {
     "cve": "CVE-2026-55290",
     "summary": "ResStringPool::setTo in libandroidfw now checks stringsStart and stylesStart against the chunk size unconditionally. It also checks that the styleCount*4 style-offset array fits within the buffer and ends before stringsStart, with overflow and underflow guards. Malformed string pools that could lead to an out-of-bounds read in ResStringPool::styleAt are now rejected with BAD_TYPE.",
     "component": "platform/frameworks/base, libs/androidfw (ResourceTypes.cpp, ResStringPool)",
     "weakness": "Missing or insufficient bounds check on attacker-controlled header fields (styleCount, stringsStart), leading to an out-of-bounds heap read. Unsigned subtraction without an underflow guard.",
     "root_cause": "In ResStringPool::setTo (libs/androidfw/ResourceTypes.cpp), per the commit message, stringsStart was not validated when stringCount == 0. In the styleCount > 0 block, the old check only verified that the start of the style-offset array (mEntryStyles - mHeader) was within `size`. It did not check that styleCount entries fit. The later check `styleCount * sizeof(uint32_t) > (stringsStart - styleOffsetsStart)` relied on the unvalidated stringsStart: with a huge stringsStart the right-hand side is huge and the check passes. As a result, a styleCount far larger than the backing data was accepted, and per the commit message ResStringPool::styleAt then reads out of bounds on the heap. The old overflow guard also used numeric_limits of the uint32 styleCount rather than SIZE_MAX, and the subtraction stringsStart - styleOffsetsStart had no underflow guard.",
     "fix_analysis": [
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, immediately after `mSize = mHeader->header.size`: new unconditional checks `mHeader->stringsStart > mSize` and `mHeader->stylesStart > mSize`. Each one logs and returns mError=BAD_TYPE. This closes the stringCount == 0 gap by validating stringsStart before any style-dependent arithmetic.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, inside the styleCount > 0 block: the old single check `(mEntryStyles - mHeader) > (int)size` is replaced by four steps. (a) Compute styleOffsetsStart = mEntryStyles - mHeader. (b) Reject if `mHeader->styleCount > SIZE_MAX / sizeof(uint32_t)`. (c) Set styleOffsetsSize = styleCount * sizeof(uint32_t), and reject if `styleOffsetsSize > size || styleOffsetsStart > (size - styleOffsetsSize)`. (d) Reject if `styleOffsetsStart > mHeader->stringsStart || styleOffsetsSize > (mHeader->stringsStart - styleOffsetsStart)`.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo: the older checks that followed the `mStyles = ...` assignment are removed. These were the numeric_limits<styleCount>::max()/sizeof(uint32_t) overflow check and the unguarded `styleCount * 4 > stringsStart - styleOffsetsStart` check. The existing `mHeader->stylesStart >= mHeader->header.size` check is kept.",
      "libs/androidfw/tests/ResourceTypes_test.cpp: adds two tests. ResStringPool_StyleCountUnboundedWhenStringCountIsZero uses stringCount=0, styleCount=100, stringsStart=0xFFFFFFFF and expects BAD_TYPE. ResStringPool_StyleCountCalculationOverflow uses stringCount=0, styleCount=0x40000000 and expects BAD_TYPE."
     ],
     "completeness": [
      "Covered: stringsStart and stylesStart are now bounded by header.size regardless of stringCount. The style-offset array extent is bounded both by the caller-supplied `size` and by stringsStart. The styleCount*4 overflow is guarded against SIZE_MAX, and the underflow of stringsStart - styleOffsetsStart is guarded.",
      "The fix is entirely in setTo. ResStringPool::styleAt itself is not changed.",
      "The new extent check uses `size` (buffer length), while the early start checks use mSize (header.size). The function mixes these two bounds.",
      "The validation inside the stringCount > 0 block is not changed by this commit.",
      "The check `mHeader->stylesStart >= mHeader->header.size` remains after the new early `stylesStart > mSize` check. It is largely redundant."
     ],
     "behavior_changes": [
      "setTo returns BAD_TYPE (and logs ALOGW) for pools whose stringsStart or stylesStart exceeds header.size, even when stringCount == 0.",
      "setTo returns BAD_TYPE when the style-offset array (styleCount*4 bytes starting at mEntryStyles) does not fit within the buffer or extends past stringsStart.",
      "Pools with styleCount > 0 whose stringsStart is smaller than the start of the style-offset array are now rejected. Previously the unsigned subtraction underflowed and the check passed.",
      "The text of the 'entry of %d styles extends past data size' warning changed: it now prints styleCount and uses %zu for the size."
     ],
     "preconditions": [
      "A crafted RES_STRING_POOL_TYPE chunk parsed by ResStringPool::setTo.",
      "Per the commit message and the new test: stringCount == 0 (so stringsStart was not validated), a large styleCount, a huge stringsStart, and stylesStart within the chunk.",
      "A later call to ResStringPool::styleAt that uses the inconsistent style data (per the commit message)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 1eb9fc572c86 in android-security-14.0.0_r29. Diff content is identical (hunks at lines 546/640/653) and the same two tests are added.",
      "15": "same fix: platform/frameworks/base ed4eedc69050 in android-security-15.0.0_r17. Diff content is identical (hunks at lines 564/658/671) and the same tests are added.",
      "16": "same fix: platform/frameworks/base 01bff648b538 in android-security-16.0.0_r8 (cherry-pick of 968cb0736ca6). Hunks at lines 567/661/674, same tests.",
      "17": "same fix: platform/frameworks/base 7e42d365a0c2 in android-security-17.0.0_r1. Diff content is identical (hunks at lines 563/657/670) and the same tests are added."
     },
     "backport_notes": [
      "Single self-contained commit. The diff content is identical across 14 through 17 apart from line offsets. The delete hunk expects the numeric_limits overflow check and the unguarded stringsStart - styleOffsetsStart check to be present. Forks that have modified ResStringPool::setTo need to adapt the patch by hand.",
      "The new tests are self-contained mock-struct tests (stated test command: atest libandroidfw_tests --host)."
     ],
     "confidence": "high",
     "unknowns": [
      "The process or privilege level in which the out-of-bounds read occurs is not shown. The bulletin only gives High / ID.",
      "Which concrete parse entry point (resources.arsc, binary XML, overlays) reaches the vulnerable path is not shown.",
      "Whether ResStringPool::styleAt performs its own bounds checks is not visible in the material.",
      "Whether validate_chunk guarantees header.size <= size is not shown, because its body is not in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55294": {
     "cve": "CVE-2026-55294",
     "summary": "Changes ihevcd_get_tu_data_size() in libhevc's decoder/ihevcd_utils.c so the tu_map region of the TU data buffer is sized by num_tu (luma plus chroma TUs) instead of num_luma_tu, to match how the buffer is later consumed.",
     "component": "platform/external/libhevc, HEVC software decoder (libhevcdec), TU data buffer sizing",
     "weakness": "Incorrect buffer size calculation (undersized allocation) leading to a heap buffer overflow",
     "root_cause": "ihevcd_get_tu_data_size() (decoder/ihevcd_utils.c) adds up several sub-regions to get the total TU data buffer size. It derives num_luma_tu from num_luma_samples, sets num_chroma_tu = num_luma_tu >> 1 and num_tu = num_luma_tu + num_chroma_tu. The tu_t array, the coded subblock/scan_idx bytes and the coeff data terms all use num_tu, and the CTB start index term uses num_ctb + 1. The tu_map term alone used num_luma_tu * sizeof(UWORD8). The commit message says the initialization logic uses tu_map space for both luma and chroma TUs, so the size returned was too small for what the decoder consumes. That mismatch caused the heap buffer overflow named in the commit title.",
     "fix_analysis": [
      "decoder/ihevcd_utils.c:ihevcd_get_tu_data_size(): the tu_map term changes from `num_luma_tu * sizeof(UWORD8)` to `num_tu * sizeof(UWORD8)`. The returned tu_data_size now includes the chroma TU map bytes, which makes it consistent with the other num_tu-based terms in the same function.",
      "This is a one-line fix in a single commit. The diff does not change the consumer (initialization) code. The commit also changes the file mode from 100755 to 100644, which is incidental."
     ],
     "completeness": [
      "The fix corrects the one sub-region (tu_map) in ihevcd_get_tu_data_size() that did not match the num_tu-based layout. The other four terms already used num_tu or num_ctb and are unchanged.",
      "The diff only changes the size calculation. The code that initializes or consumes the TU data buffer is not in the material, so it cannot be checked whether that layout matches exactly these five terms and nothing more.",
      "No bounds check is added where the buffer is used. Correctness depends on the size calculation now matching the consumer."
     ],
     "behavior_changes": [
      "The TU data allocation grows by num_chroma_tu * sizeof(UWORD8) bytes, so the decoder's TU data buffer uses slightly more memory.",
      "The diff adds no new error paths or return codes."
     ],
     "preconditions": [
      "An HEVC bitstream must be decoded by the libhevc software decoder (libhevcdec). Per index search, it is used by frameworks/av/media/codec2/components/hevc/C2SoftHevcDec.cpp and device/generic/goldfish/codecs/c2/decoders/hevcdec/GoldfishHevcHelper.cpp.",
      "The decoder sizes its TU data buffer with ihevcd_get_tu_data_size() and, per the commit message, uses tu_map space for both luma and chroma TUs. The stream properties needed to reach the overflow are not shown in the material (the commit refers to a PoC in the bug)."
     ],
     "per_major": {
      "14": "same fix: platform/external/libhevc d9977ef8294a in android-security-14.0.0_r29, the same one-line change in decoder/ihevcd_utils.c:ihevcd_get_tu_data_size() (same hunk, different file blob hashes).",
      "15": "same fix: platform/external/libhevc c43bbf882f97 in android-security-15.0.0_r17, identical hunk.",
      "16": "same fix: platform/external/libhevc 5492b022e4d6 in android-security-16.0.0_r8, identical hunk.",
      "17": "same fix: platform/external/libhevc 9aa38f2733f4 in android-security-17.0.0_r1, identical hunk."
     },
     "backport_notes": [
      "The hunk is a single line in ihevcd_get_tu_data_size() and is identical on 14 through 17. It should apply cleanly to any libhevc tree where the tu_map term still reads `num_luma_tu * sizeof(UWORD8)`. If the tree carries vendor modifications, check that its consumer layout matches."
     ],
     "confidence": "high",
     "unknowns": [
      "The code that initializes or consumes the TU data buffer is not in the material, so the exact write pattern that overflowed is known only from the commit message.",
      "The bitstream characteristics needed to trigger the overflow (the PoC referenced in bug 505000651) are not shown.",
      "The value of MIN_TU_SIZE, and so the exact number of bytes added, is not shown.",
      "The material does not show whether the overflow can be reached from all codec2/OMX integration paths or only from particular configurations."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58822": {
     "cve": "CVE-2026-58822",
     "summary": "Adds size guards to FreeType's ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c that reject bitmaps whose width/3 (LCD) or rows/3 (LCD_V) exceeds 0x7FFF with Raster_Overflow, preventing a 16-bit truncation of span x-coordinates that led to a heap underflow write in ft_smooth_lcd_spans.",
     "component": "platform/external/freetype, smooth (anti-aliased) rasterizer, LCD subpixel rendering (src/smooth/ftsmooth.c)",
     "weakness": "Missing bounds check leading to integer truncation (16-bit FT_Span.x) and out-of-bounds (underflow) heap write",
     "root_cause": "In ft_smooth_raster_lcd the outline is rendered in direct mode (FT_RASTER_FLAG_DIRECT, gray_spans = ft_smooth_lcd_spans) with params.clip_box.xMax = bitmap->width. The width/3 guard implies the LCD bitmap width is tripled. The rasterizer stores span x-coordinates in FT_Span.x, which the commit describes as 16-bit. For wide enough bitmaps (width/3 > 0x7FFF) the x value is truncated. The callback ft_smooth_lcd_spans then computes dst = dst_line + spans->x * 3 and writes spans->coverage, which the commit describes as a heap underflow write. Per the commit, ft_smooth_raster_lcdv had the analogous unchecked condition on bitmap->rows. A similar guard already existed in ft_smooth_raster_overlap but was missing from the two LCD paths.",
     "fix_analysis": [
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcd: before FT_Raster_Params setup, adds `if ( bitmap->width / 3 > 0x7FFF ) return FT_THROW( Raster_Overflow );`. Bitmaps whose per-channel width would not fit in the 16-bit FT_Span.x are rejected before any span callback runs.",
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcdv: adds `if ( bitmap->rows / 3 > 0x7FFF ) return FT_THROW( Raster_Overflow );` before params setup. This is the matching check for the vertical LCD layout.",
      "Both checks are early returns using an existing FreeType error. ft_smooth_lcd_spans and the rasterizer are not changed. The commit was autogenerated by MendIt and modeled on the existing guard in ft_smooth_raster_overlap."
     ],
     "completeness": [
      "Covers both LCD entry points in ftsmooth.c: ft_smooth_raster_lcd (width) and ft_smooth_raster_lcdv (rows). Only the LCD path is shown using direct rendering through ft_smooth_lcd_spans. The lcdv context shows params.target = bitmap with FT_RASTER_FLAG_AA only.",
      "ft_smooth_raster_lcd checks only bitmap->width, and ft_smooth_raster_lcdv checks only bitmap->rows. In ft_smooth_lcd_spans the y coordinate is an int (no truncation), so in the LCD path only x matters. The material does not show whether the other dimension in each path is bounded elsewhere.",
      "The 16-bit FT_Span.x type and the unchecked pointer arithmetic in ft_smooth_lcd_spans stay as they are. The fix is a precondition guard, not a hardening of the span writer.",
      "Per the commit message, ft_smooth_raster_overlap already had a similar guard. This commit does not touch the plain (non-LCD, non-overlap) rendering path, and the material does not show how that path behaves."
     ],
     "behavior_changes": [
      "Rendering in LCD mode to a bitmap with width/3 > 0x7FFF, or in LCD_V mode with rows/3 > 0x7FFF, now fails with a Raster_Overflow error instead of being rasterized.",
      "No change for bitmaps within the limit. The diff makes no API or ABI change."
     ],
     "preconditions": [
      "A caller renders an outline through the smooth renderer in an LCD or LCD_V mode, which reaches ft_smooth_raster_lcd or ft_smooth_raster_lcdv.",
      "The target bitmap's tripled dimension exceeds 3 * 0x7FFF (per-channel extent > 32767).",
      "Per index search, frameworks/rs/rsFont.cpp uses FreeType (FT_New_Memory_Face / FT_Set_Char_Size). The material does not show whether that caller uses LCD render modes. The minikin matches are HarfBuzz font-funcs code with no evident relevance."
     ],
     "per_major": {
      "14": "Same fix, applied as platform/external/freetype 51c2994abfc5 in android-security-14.0.0_r29. The 8 added lines are identical and only the hunk offsets differ (137/204 vs 141/208). 8/8 lines present at the latest tag.",
      "15": "Same fix, platform/external/freetype fc6e207660f9 in android-security-15.0.0_r17. Identical diff to the 16 commit (same blob hashes 9b0e8886c..6d4b9150a). 8/8 lines present.",
      "16": "Reference fix, platform/external/freetype 6ca3925a3c97 in android-security-16.0.0_r8, cherry-picked from internal commit 2e80dfa7aa27. 8/8 lines present.",
      "17": "Same fix, platform/external/freetype 4321c90fc79e in android-security-17.0.0_r1. Same hunks on a different base file (f0acc1ea4..06a1e1c6f). 8/8 lines present."
     },
     "backport_notes": [
      "Single commit adding 8 lines, with no dependencies shown. It needs ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c. Google applied the same lines unchanged on 14, 15, 16 and 17. Apply by context, not by line number, because offsets differ across branches."
     ],
     "confidence": "high",
     "unknowns": [
      "The bodies of ft_smooth_raster_lcdv and ft_smooth_raster_overlap (including the existing guard the commit refers to) are not shown. The exact write behavior in the LCD_V path comes from the commit message.",
      "The material does not show whether the other dimension in each LCD path (rows for LCD, width for LCD_V) is bounded elsewhere in the rasterizer.",
      "The material does not show which Android platform consumers render in LCD modes, or where oversized outlines would come from (for example, untrusted fonts). The bulletin rates the issue High / RCE without further detail."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58839": {
     "cve": "CVE-2026-58839",
     "summary": "Shrinks the stack-allocated read buffer in the incfs MountRegistry line parser (forEachLine in incfs/MountRegistry.cpp) from 128 KiB to 32 KiB, adds a compile-time check that the caller's declared maximum line length fits, and marks the buffer uninitialized, so the parser does not overrun the calling thread's stack when that stack is already close to full.",
     "component": "platform/system/incremental_delivery, incfs library (MountRegistry mountinfo parser)",
     "weakness": "Excessive stack allocation (128 KiB automatic buffer), which can cause stack overflow or stack exhaustion when called on a thread with little stack left",
     "root_cause": "incfs/MountRegistry.cpp: the static template forEachLine(fd, cb) declared `static constexpr auto kBufSize = 128 * 1024; char buffer[kBufSize];` as a local array, so every call used 128 KiB of the calling thread's stack. forEachLine is called from MountRegistry::Mounts::loadFrom. loadFrom is called from MountRegistry::ensureUpToDate whenever poll() on the mountinfo fd does not time out (it reports an event or fails). ensureUpToDate is called from MountRegistry::copyMounts and MountRegistry::reload. The commit message says this size \"may cause stack overflow if we're already nearing the limit\", meaning the buffer was not sized against the stack budget of the threads that run the parser.",
     "fix_analysis": [
      "incfs/MountRegistry.cpp:forEachLine: the template gains a `size_t maxLineLength` non-type parameter, and kBufSize becomes a non-static constexpr 32 * 1024. This cuts the stack footprint by a factor of 4.",
      "incfs/MountRegistry.cpp:forEachLine: a static_assert(maxLineLength < kBufSize, ...) checks the caller's declared maximum line length against the buffer size at compile time. If a future caller needs longer lines, the build fails instead of quietly relying on the smaller buffer (the assert message says to rewrite with a vector in that case).",
      "incfs/MountRegistry.cpp:forEachLine: the buffer is declared `char buffer[kBufSize] __attribute__((uninitialized))`, which opts out of automatic variable initialization. In the visible code, chunks are built only up to readEnd (nextRead + read). The rest of the loop is not shown.",
      "incfs/MountRegistry.cpp:MountRegistry::Mounts::loadFrom: the call becomes forEachLine<maxLineSize>(fd, ...) with `constexpr auto maxLineSize = 3 * PATH_MAX + 1024`. The comment says each line holds 3 paths plus some shorter fields."
     ],
     "completeness": [
      "The fix only shrinks the stack buffer. forEachLine still allocates 32 KiB on the stack of whatever thread calls copyMounts()/reload(), and the buffer is not moved to the heap. The mitigation reduces the stack use but does not remove the dependency on stack space.",
      "The static_assert only runs at compile time: it checks the declared maxLineLength constant against kBufSize. This change adds no runtime handling for mountinfo lines that exceed the buffer. The provided context does not show how the existing loop handles an overlong line (the code after find('\\n') is cut off).",
      "The maxLineSize bound (3 * PATH_MAX + 1024) is an assumption about the mountinfo line format written into loadFrom. It is not checked against actual input.",
      "Only forEachLine and loadFrom in incfs/MountRegistry.cpp are changed. The material does not show whether the project has other large stack buffers."
     ],
     "behavior_changes": [
      "Mountinfo parsing now reads at most 32 KiB per pread() instead of 128 KiB, so larger files take more pread() iterations. The fix assumes lines within the declared maxLineSize still fit in the buffer. Behavior for longer lines is not shown.",
      "The read buffer is no longer auto-initialized (clang `uninitialized` attribute). On builds with automatic variable initialization, this removes the zero-fill cost (general platform knowledge).",
      "The build fails with a compile-time error if a caller instantiates forEachLine with maxLineLength >= 32 KiB."
     ],
     "preconditions": [
      "MountRegistry::copyMounts() or MountRegistry::reload() must be called while poll() on the mountinfo fd (events POLLERR|POLLPRI) reports an event or fails. Only then does ensureUpToDate run Mounts::loadFrom, and with it forEachLine.",
      "Per the commit message, the 128 KiB buffer only matters if the calling thread has little stack left at that point. The material does not show which threads or call depths are affected.",
      "Per index search, frameworks/base/services/incremental/IncrementalService.cpp uses an incfs wrapper (mIncFs). The material does not show any path from that code to MountRegistry::copyMounts/reload."
     ],
     "per_major": {
      "14": "same fix: platform/system/incremental_delivery b6d06e7ccfc2 in android-security-14.0.0_r29. The diff is identical to the Android 16 commit (same blob index 376eaf2..eaf97dc).",
      "15": "same fix: platform/system/incremental_delivery 877702e09486 in android-security-15.0.0_r17. The diff is identical to the Android 16 commit.",
      "16": "same fix: platform/system/incremental_delivery 67b14f0e00af in android-security-16.0.0_r8. It is a cherry-pick of internal commit 71c3c0252b97, Change-Id I8ac5de734544efff523b2014a7d11c9f9beb9e4a.",
      "17": "not affected per the bulletin. At android-security-17.0.0_r1 the patched region matches at the same 6/8 ratio as the fixed 14/15/16 tags, which is consistent with the change already being present there."
     },
     "backport_notes": [
      "The pre-fix file blob (376eaf2) is the same on 14, 15 and 16, so the single-file diff should apply cleanly. It needs PATH_MAX (limits.h) and clang's `__attribute__((uninitialized))`, both available in the platform toolchain (general platform knowledge). The material has no fork comparison, so no per-file verdict for forks can be given."
     ],
     "confidence": "high",
     "unknowns": [
      "Which caller or thread (and with what stack budget) hits the overflow in practice. The details of bug 466091304 are not in the material.",
      "How forEachLine handles a line longer than the buffer (the loop body after line 291 is not shown).",
      "Why the bulletin rates this High EoP. The material shows only a stack-size reduction.",
      "Whether and how IncrementalService or other system components reach MountRegistry::copyMounts/reload."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58874": {
     "cve": "CVE-2026-58874",
     "summary": "Adds a subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony), loading the stored message's destination address so the emergency-number exemption applies, and makes TelephonyPermissions.checkSubscriptionAssociatedWithUser tolerate a null destAddr (frameworks/base).",
     "component": "platform/frameworks/opt/telephony (SmsController / IccSmsInterfaceManager, SMS sending) and platform/frameworks/base (telephony/common TelephonyPermissions)",
     "weakness": "Missing authorization check (caller user not verified against subscription before sending a stored SMS)",
     "root_cause": "In SmsController, the Binder entry points sendStoredText and sendStoredMultipartText only checked that callingPkg belongs to the calling UID. They then forwarded the request to IccSmsInterfaceManager for the given subId without calling TelephonyPermissions.checkSubscriptionAssociatedWithUser. A caller in a user or profile not associated with the target subscription was therefore not rejected on these paths. Separately, the 4-arg TelephonyPermissions.checkSubscriptionAssociatedWithUser declared destAddr @NonNull and passed it straight to TelephonyManager.isEmergencyNumber. That did not fit the new caller, where the address may be unavailable (null).",
     "fix_analysis": [
      "platform/frameworks/base, telephony/common/com/android/internal/telephony/TelephonyPermissions.java:checkSubscriptionAssociatedWithUser (4-arg overload): destAddr changes from @NonNull to @Nullable, and the emergency exemption condition becomes `tm != null && destAddr != null && tm.isEmergencyNumber(destAddr)`. When destAddr is null the emergency skip is not taken and the method falls through to the 3-arg checkSubscriptionAssociatedWithUser(context, subId, callerUserHandle).",
      "platform/frameworks/opt/telephony, src/java/com/android/internal/telephony/IccSmsInterfaceManager.java:loadTextAndAddress: visibility changes from private to public so SmsController can read the stored message's BODY and ADDRESS. The method queries messageUri with the calling identity cleared and returns {text, address}, or null.",
      "platform/frameworks/opt/telephony, src/java/com/android/internal/telephony/SmsController.java: new private helper getDestAddress(IccSmsInterfaceManager, Uri). It returns null when iccSmsIntMgr is null or loadTextAndAddress returns null, and otherwise returns textAndAddress[1].",
      "SmsController.java:sendStoredText: after the callingPkg/UID check, it computes destAddr = getDestAddress(iccSmsIntMgr, messageUri) and calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, callingUser, destAddr). On false it calls TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, uid, callingPkg), logs, sends SmsManager.RESULT_USER_NOT_ALLOWED via sendErrorInPendingIntent(sentIntent, ...) and returns before iccSmsIntMgr.sendStoredText is reached. Binder.getCallingUid() is now stored in a local `uid`, which the SecurityException message reuses.",
      "SmsController.java:sendStoredMultipartText: same check. On failure it uses sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED) and returns before iccSmsIntMgr.sendStoredMultipartText.",
      "tests/telephonytests/.../SmsControllerTest.java: four new tests (sendStoredText and sendStoredMultipartText, each with an associated and a non-associated user). They mock loadTextAndAddress and SubscriptionManager.isSubscriptionAssociatedWithUser and verify whether the IccSmsInterfaceManager send method is invoked.",
      "The Android 16 commit also adds src/java/com/android/internal/telephony/SmsController_fixed_methods.txt, a plain-text copy of the two fixed methods. It is not Java source and has no functional effect."
     ],
     "completeness": [
      "Both stored-message Binder entry points in SmsController (sendStoredText and sendStoredMultipartText) now run the association check before delegating. The check sits after the callingPkg/UID validation and before any IccSmsInterfaceManager call, including the iccSmsIntMgr == null branch.",
      "The emergency-number exemption is kept: if the stored message's address is an emergency number, the check returns true. If the address cannot be loaded (iccSmsIntMgr is null, the message is not found, or loadTextAndAddress hits a SQLiteException), destAddr is null, the exemption is skipped and the plain user-association check decides.",
      "The check is applied only in SmsController. IccSmsInterfaceManager.sendStoredText/sendStoredMultipartText themselves stay unchecked, so another in-process caller of those methods would not be covered. The material shows no such caller.",
      "The frameworks/base change is only shown for Android 16. The material does not show whether the @Nullable change was applied to 14 and 15."
     ],
     "behavior_changes": [
      "sendStoredText / sendStoredMultipartText called from a user not associated with the subscription now fail with SmsManager.RESULT_USER_NOT_ALLOWED, delivered through sentIntent / sentIntents, and the message is not sent.",
      "TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate may now show a dialog to the user on rejection.",
      "Each call now runs an extra content provider query (BODY, ADDRESS) on messageUri, with the calling identity cleared, before the send.",
      "Messages whose stored address is an emergency number skip the association check (the existing exemption is now reachable from these paths).",
      "IccSmsInterfaceManager.loadTextAndAddress becomes public (module-internal API surface).",
      "New Rlog.d lines on rejection and when destAddr cannot be loaded."
     ],
     "preconditions": [
      "The caller invokes the ISms Binder methods sendStoredText or sendStoredMultipartText (SmsController) with the subId of a subscription that is not associated with the caller's user or profile.",
      "callingPkg must match the calling UID's package (pre-existing check), and a messageUri referencing a stored SMS is supplied.",
      "Per index search, ProviderUtil.allowInteractingWithEntryOfSubscription (TelephonyProvider) is a separate caller of the 3-arg check and is not part of this path."
     ],
     "per_major": {
      "14": "adapted fix: platform/frameworks/opt/telephony 2c10fa519945 in android-security-14.0.0_r29. Same logic, but callingUser and uid are declared locally after the package check, the android.os.UserHandle import is added, and IccSmsInterfaceManager.sendStoredText/sendStoredMultipartText are called without the userId argument. No SmsController_fixed_methods.txt. The material does not show a frameworks/base change for 14.",
      "15": "adapted fix: platform/frameworks/opt/telephony 3ed5e1fdd5da in android-security-15.0.0_r17. As in 14, callingUser and uid are declared locally after the package check and there is no userId argument to IccSmsInterfaceManager. Only the ContentResolver import is added (no UserHandle import). No SmsController_fixed_methods.txt. The material does not show a frameworks/base change for 15.",
      "16": "same fix: platform/frameworks/opt/telephony b77a21c16113 plus platform/frameworks/base 98be98fe443d in android-security-16.0.0_r8. It reuses the existing callingUser local, captures uid, and also adds the non-source SmsController_fixed_methods.txt.",
      "17": "not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 6 of the 8 lines of the patched region are present."
     },
     "backport_notes": [
      "Carry both the opt/telephony change and the frameworks/base @Nullable/null-guard change. On branches where IccSmsInterfaceManager.sendStoredText takes no userId and SmsController does not already compute callingUser, follow the 14/15 variant: declare callingUser/uid locally and add the UserHandle import if it is missing.",
      "Do not carry over SmsController_fixed_methods.txt. It is a non-source file in the 16 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the frameworks/base TelephonyPermissions change (98be98fe443d) was also applied to the Android 14 and 15 branches. Only the 16 commit is in the material.",
      "Whether TelephonyManager.isEmergencyNumber(null) throws or returns false, and therefore whether the opt/telephony change behaves the same without the base change.",
      "Whether IccSmsInterfaceManager queries the message again when sending. Its send internals are not shown.",
      "Why the bulletin's affected list contains '17-next' while Android 17 is not listed.",
      "Why SmsController_fixed_methods.txt was committed."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "17": {
   "counts": {
    "announced": 77,
    "certain": 77,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2026-28604",
     "osv_id": "ASB-A-499053500",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport* across the blocking handshake. The thread now captures a weak-pointer callback id plus the connection object returned by t->connection(), posts the result back to the fdevent looper, and touches the transport only after transport_from_callback_arg confirms it is still alive. FdConnection::DoTlsHandshake (daemon build) also gets 3-second socket send/receive timeouts.",
      "confidence": "high",
      "backport_notes": [
       "On 17-style trees, apply fdbd8a5f26c5 (helper hardening) before 12700491ebb3. The UAF fix relies on the existing transport_to_callback_arg/transport_from_callback_arg helpers, atransport::weak() and fdevent_run_on_looper. If the CHECK_LOOPER_THREAD/CHECK_NOT_LOOPER_THREAD macros are absent, drop them as the 14/15/16 backports did. The Android 14/15 versions also keep LOG(INFO) instead of VLOG(AUTH).",
       "The bulletin lists 16-qpr2 and 17-next as affected, but the material contains no commits for those branches."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/adb",
       "sha": "12700491ebb38ec563f06dbcc1ce6e45d75bfe7e",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/modules/adb",
       "sha": "fdbd8a5f26c58722ddf414afc591fcdbca5389d4",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/packages/modules/adb"
     ]
    },
    {
     "cve": "CVE-2026-28618",
     "osv_id": "ASB-A-492492028",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a check in libopenapv's dec_frm_prepare that the supplied output image buffer (imgb) is at least as wide and tall as the frame dimensions in the Frame PBU header. Otherwise it returns OAPV_ERR_INVALID_ARGUMENT. This stops a heap buffer overflow when the Frame PBU dimensions are larger than the AU_INFO dimensions used for buffer allocation.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained hunk at the top of dec_frm_prepare in src/oapv.c. It relies on imgb->w[]/h[] and ctx->fh.fi.frame_width/frame_height. The Android 16 and 17 diffs are identical apart from line offsets. For other snapshots, check that these fields exist before applying."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libopenapv",
       "sha": "7c797fcb63c8d647ebd3841b0efbde51f812b9ff",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libopenapv"
     ]
    },
    {
     "cve": "CVE-2026-28639",
     "osv_id": "ASB-A-508389703",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds `failed = true;` to the else branch in rw_mfc_handle_read_op (rw_mfc.cc) that logs android_errorWriteLog(0x534e4554, \"178725766\"), so that a MIFARE Classic NDEF read is marked as failed on a length inflation attempt instead of only being logged.",
      "confidence": "medium",
      "backport_notes": [
       "The file is in platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15 and in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) on 16/17. The one-line change is identical: find the `android_errorWriteLog(0x534e4554, \"178725766\")` else branch in rw_mfc_handle_read_op and add `failed = true;` after the log call. If the tree has the saved_len roll back variant (as in the 17 context snippet), first check that this else branch exists."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "9f4efdd67acb88de5f3450a31a85c054156fa4a6",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28653",
     "osv_id": "ASB-A-508389055",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a range check before the loop in the T3T NDEF UPDATE and CHECK command builders (rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd in libnfc-nci rw_t3t.cc). When first_block + block_count would exceed UINT16_MAX, the function now returns NFC_STATUS_FAILED, instead of letting the uint16 block_id loop counter wrap and write past the command buffer.",
      "confidence": "medium",
      "backport_notes": [
       "The file is src/nfc/tags/rw_t3t.cc in platform/system/nfc (Android 14/15) and libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc (Android 16+). The hunk content is identical across branches; only the path and line offsets differ.",
       "When backporting, consider freeing p_cmd_buf before returning NFC_STATUS_FAILED, as the form at the 17 tag does. Keep the bound at UINT16_MAX rather than 0x10000 unless block_id is confirmed to be wider than uint16_t."
      ],
      "meta": {
       "generated": "2026-10-01",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "e55c93242c569d3faf225dae614a41fa5d03eae8",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-28662",
     "osv_id": "ASB-A-495652882",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a bounds check in wpa_supplicant's P2P2 Provision Discovery bootstrap request handler so that a PBMA cookie length taken from the frame is rejected when it is larger than the per-device cookie buffer, and enables the UBSan \"bounds\" sanitizer on the pasn library in wpa_supplicant/Android.bp.",
      "confidence": "high",
      "backport_notes": [
       "The two hunks are independent. The p2p_pd.c check is the security fix and applies wherever p2p_process_prov_disc_bootstrap_req has the 'Truncated PBMA' check and a fixed-size dev->bootstrap_params->cookie field. The Android.bp 'bounds' line is hardening for the pasn cc_library in the cuttlefish section. Branches without the P2P2 bootstrap code (Android 14/15 per the per-major facts) have nothing to patch."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "b8d36c897dde9a2273fc5c850d5ea49ba42c55d9",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref b8d36c897dde",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/wpa_supplicant_8"
     ]
    },
    {
     "cve": "CVE-2026-33636",
     "osv_id": "ASB-A-496616027",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Restricts the NEON palette-expansion loops in libpng's arm/palette_neon_intrinsics.c to full chunks only, removes the post-loop rollback of the pixel counter, and undoes the output-pointer pre-adjustment before returning to the scalar fallback, eliminating an out-of-bounds read/write on the final partial chunk.",
      "confidence": "high",
      "backport_notes": [
       "Single-file, single-commit change confined to arm/palette_neon_intrinsics.c; the 14/15/16 diff differs from 17 only by a leading blank line in the file header, so the same patch applies to all branches with trivial offset. No 'Fix in fork' section was provided, so no per-file fork verdict can be reported.",
       "Verify after applying that pngrtran.c's scalar fallback still consumes the returned i, *ssp and *ddp as this commit assumes, since that file is not in the diff."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libpng",
       "sha": "7d98050ef8d9d17d495eb178b8217548437cedf1",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libpng"
     ]
    },
    {
     "cve": "CVE-2026-45515",
     "osv_id": "ASB-A-503554165",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the Bluetooth A2DP Opus decoder, the fix changes the frame_size argument passed to opus_decode(). It used to be the raw A2DP_OPUS_DECODE_BUFFER_LENGTH value and is now the number of samples per channel the output buffer can hold (A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t))). The fix also adds a channel-count check, decodes each packet once instead of once per frame, and sizes the callback payload by the fixed stereo output channel count.",
      "confidence": "high",
      "backport_notes": [
       "This is a single-commit fix. For a tree without the fmt-style bluetooth/log.h logger, the Android 14 variant (LOG_ERROR/LOG_INFO with %d) can serve as the template. In the target tree, verify that A2DP_OPUS_DECODE_BUFFER_LENGTH is the byte size used to allocate decode_buf, and that A2DP_OPUS_CODEC_OUTPUT_CHS exists and matches the channel count used to create the decoder.",
       "The new test calls the libopus encoder (opus_encoder_create/opus_encode). Check that the test target can link these functions in the target tree."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "3b78dbef3ec6d1ab1703962a191f4ff25050c451",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-45531",
     "osv_id": "ASB-A-508645975",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens fsck.exfat boot-region validation in read_boot_region against integer overflow/underflow from on-disk vol_length, clu_offset and clu_count fields, and makes the shared DIV_ROUND_UP macro compute in 64 bits.",
      "confidence": "high",
      "backport_notes": [
       "Only 5ec5ede9ba50 carries the runtime fix. a6faf5dfd40a (host_supported in the Android.bp files) is independent and only needed to build the tests. In trees where the error calls have no `if (verbose)` wrappers (as in Android 14), drop the wrappers. Insert the two new guards right after the vol_length check, whether the next check is the clu_max_count form (17) or the `clu_count * EXFAT_CLUSTER_SIZE(bs)` form (14/16)."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/exfatprogs",
       "sha": "5ec5ede9ba508dde0c2cc95363742f2c2f669fff",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/external/exfatprogs",
       "sha": "a6faf5dfd40a639edc0c4d87f1b14babfc129bc5",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/external/exfatprogs"
     ]
    },
    {
     "cve": "CVE-2026-49879",
     "osv_id": "ASB-A-503544522",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Widens the Type 3 Tag block counter to uint32_t and recomputes the block-range bounds check and loop condition in 32-bit arithmetic in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, and frees the GKI command buffer when the range is rejected.",
      "confidence": "medium",
      "backport_notes": [
       "Every hunk's pre-image assumes the earlier guard tagged android_errorWriteLog(0x534e4554, \"508389055\") is already present in both functions. On a branch without that guard, the whole check block has to be added before this diff will apply.",
       "On Android 14/15 the fix lands in platform/system/nfc (src/nfc/tags/rw_t3t.cc). On 16 and 17 it lands in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_t3t.cc). Only the path prefix differs."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "442e948e4be2fd09dc553b99bd5ed1735e710b2e",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-49882",
     "osv_id": "ASB-A-503553866",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (libnfc-nci rw_mfc.cc), the fix snapshots ndef_length and ndef_start_pos before rw_nfc_decodeTlv on the first block read, treats a TLV decode that fails or inflates ndef_length as a failure, and rolls both fields back so an inflated length does not persist in the Mifare Classic control block and drive later copies into p_ndef_buffer.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained hunk in rw_mfc_handle_read_op. The file lives in platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15 and in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) on 16/17. The only difference between branches is the verbose-log macro on 14 (DLOG_IF instead of LOG(VERBOSE))."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "19fc329133fe95e5da27ebd54b6d4fb79a01dfd0",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-49884",
     "osv_id": "ASB-A-503557185",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In rw_mfc_handle_read_op (libnfc-nci rw_mfc.cc), the fix snapshots p_mfc->ndef_length and p_mfc->ndef_start_pos before rw_nfc_decodeTlv on the first block read. It treats a decode failure or an inflated ndef_length as a failure and rolls both fields back to the snapshot, so the tag-supplied, inflated length is no longer left in the control block to bound copies into p_ndef_buffer.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The file is src/nfc/tags/rw_mfc.cc in platform/system/nfc for 14/15 and libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc for 16/17 (libnfc-nci moved into the Nfc mainline module in newer releases, general platform knowledge). For older trees, adjust only the log macro (DLOG_IF vs LOG(VERBOSE))."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "19fc329133fe95e5da27ebd54b6d4fb79a01dfd0",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-49918",
     "osv_id": "ASB-A-511821731",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds 64-bit overflow checks to the cupsBytesPerLine and line/page buffer size computations in libcupsfilters (imagetoraster.c, pwgtoraster.c, raster.c) so that large raster dimensions no longer wrap to undersized heap allocations.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The hunks rely on <stdint.h> plus the log/ld, ret/out, cfImageClose and cupsFreeOptions constructs already present in the surrounding code. The commit also adds a patches/*.patch copy of the change.",
       "cfRasterSetColorSpace gains a new -1 return path. When backporting, check that its callers in the target branch treat a non-zero return as failure."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libcupsfilters",
       "sha": "5c0e5bf182a50c11af14463b34e48a305d85b195",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libcupsfilters"
     ]
    },
    {
     "cve": "CVE-2026-49919",
     "osv_id": "ASB-A-503556301",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Replaces the unchecked `size = rows * pitch; FT_ALLOC(buf, size)` in tt_face_colr_blend_layer (src/sfnt/ttcolr.c) with FT_ALLOC_MULT(buf, rows, pitch), so the allocator detects a multiplication overflow instead of allocating a wrapped size.",
      "confidence": "high",
      "backport_notes": [
       "A single small hunk in one file with no dependencies. It applies cleanly wherever tt_face_colr_blend_layer contains the `size = rows * pitch; FT_ALLOC( buf, size )` pair. If `size` becomes unused after the change, check for a compiler warning treated as an error in the local build."
      ],
      "meta": {
       "generated": "2026-10-01",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "65f3c6ba20e777f45f409ae2a7500c4583a6545f",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    },
    {
     "cve": "CVE-2026-49921",
     "osv_id": "ASB-A-503554490",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds SDP_MAX_ATTR_LEN bounds checks and a dynamically sized serialization buffer to the Bluetooth SDP server so an oversized attribute in the local SDP database can no longer be serialized past a fixed 400-byte heap allocation (or read past it via a continuation offset) when building partial attribute responses.",
      "confidence": "high",
      "backport_notes": [
       "Google shipped one commit per branch. For trees that look like Android 14 (SDP_TRACE_* logging, SDP_AddAttributeToRecord, pre-fix 'attr_len >= SDP_MAX_ATTR_LEN' check only in the 'rem_len < attr_len' branch), follow the Android 14 variant: it moves the check before the continuation/partial branches and removes the old one. It also adds osi_malloc/osi_free mock bodies to the StackSdpUtilsTest fixture alongside the new tests."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "4a9e8875e210f2c26f1a755922bd43af758a92cf",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-49926",
     "osv_id": "ASB-A-513832249",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes `integer_overflow: true` from the sanitize block of the `libjxl` cc_library in external/libjxl Android.bp, so that unsigned integer wrap-around that upstream libjxl uses on purpose no longer triggers UBSan aborts.",
      "confidence": "medium",
      "backport_notes": [
       "Single one-line Android.bp change with no code dependency. On any branch that carries external/libjxl, remove `integer_overflow: true` from the sanitize block of the `libjxl` cc_library, then check with the CTS test named in the commit."
      ],
      "meta": {
       "generated": "2026-10-01",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libjxl",
       "sha": "069d75c482c7080f5b7f55d1d0776252ffbbe497",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libjxl"
     ]
    },
    {
     "cve": "CVE-2026-49927",
     "osv_id": "ASB-A-511640905",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds 64-bit overflow checks when ppdRasterInterpretPPD derives cupsBytesPerLine. In write_flate it rejects cupsBytesPerLine*6 above INT32_MAX and replaces the header-sized stack VLAs and per-line mallocs with single checked heap allocations, freed through a common cleanup path.",
      "confidence": "high",
      "backport_notes": [
       "Apply both halves together: the direct edits to ppd/raster-interpret.c and ppd/rastertops.c, and the new patches/libppd-2.1.1-Fix-integer-overflow-and-heap-OOB-write-errors.patch. The code needs <stdint.h> for uint64_t/UINT32_MAX/INT32_MAX."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libppd",
       "sha": "6ce8492f2566a35e79afd0f159be7acebe124863",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libppd"
     ]
    },
    {
     "cve": "CVE-2026-49932",
     "osv_id": "ASB-A-513581684",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens PduParser.parseParts in frameworks/base telephony/common so that malformed MMS multipart PDUs no longer cause StackOverflowError (unbounded recursion into nested multipart/alternative parts), OutOfMemoryError (allocation of a part buffer sized by an unchecked declared dataLength), or NullPointerException/ArrayIndexOutOfBoundsException (taking getPart(0) of a null or empty nested body). The parser now returns null in these cases.",
      "confidence": "high",
      "backport_notes": [
       "The patch is self-contained in one file plus a new test. The parseParts hunks have the same offsets on all four branches (pre-image blob 62eac7ac942a on 14/15/16, ffd2a8b419bb on 17), so it is expected to cherry-pick cleanly onto a tree with the stock parseParts.",
       "If a tree contains forked copies of PduParser (for example the packages/apps/Messaging copies found per index search), they need separate review and porting. This commit does not change them."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1c37e9ef69ea9e3768e0c70423fc72383abc2226",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55256",
     "osv_id": "ASB-A-513581389",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds null checks in PduParser.parsePartHeaders so that a null return from parseWapString for the Content-Disposition value, the filename parameter, or a text header name/value is no longer passed to PduPart setters or new String(), preventing potential NullPointerExceptions while parsing an MMS PDU.",
      "confidence": "high",
      "backport_notes": [
       "Two independent single-file commits (frameworks/base and development) with no ordering dependency. The hunks target the original setContentDisposition/setFilename/'true ==' pattern in parsePartHeaders. The pre-image blobs differ between 17 (15448a6a77f4) and 15/16 (ecab9562f72e), although the hunk content and offsets are the same.",
       "The platform/development copy belongs to the ApiDemos sample app, which is not normally part of device builds (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/development",
       "sha": "21dbe0e7ba1f29f36c37b60a6e249732f2fff4fa",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "724976bd27bdfe7d64db47fa2dd1827e3e90ef68",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/development",
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55273",
     "osv_id": "ASB-A-524767048",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "aapt2 now sanitizes XML-derived comment text before writing it into the Javadoc blocks of the generated R.java. It replaces the \"*/\" terminator with \"*&#47;\" and the backslash of any backslash-u sequence with \"&#92;\", so the comment cannot close the Javadoc block and inject Java code.",
      "confidence": "high",
      "backport_notes": [
       "The change is self-contained: one new static function, one call in AppendCommentLine, and a test, with no header or build changes. It uses std::string_view, which the existing AppendCommentLine code already uses (sDeprecated). The only difference seen between branches is the sAnnotationRules entry count (4 on 17, 3 on 16), which affects only hunk context."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "28830f292a06db0cc0d0eb554fd2373dd5af01e5",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55277",
     "osv_id": "ASB-A-524820424",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a bounds check in RoutingManager::checkUiccListenConfigNeeded so a new UICC listen configuration is only appended to mNfceeListenConfig.config when nb_config is below NFA_EE_MAX_EE_SUPPORTED, otherwise it logs an error and returns false.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, 5-line self-contained change that uses NFA_EE_MAX_EE_SUPPORTED. For a forked RoutingManager.cpp, insert the check in checkUiccListenConfigNeeded's `if (!found)` branch before the first config[nb_config] write. The RoutingManagerTest case mentioned in the commit message is not in this cherry-pick."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "8181cc81e3000afaadf2ab707ac4cf672af66d41",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Nfc"
     ]
    },
    {
     "cve": "CVE-2026-55285",
     "osv_id": "ASB-A-522374659",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a range check on the channel number byte that the Secure Element returns in the MANAGE CHANNEL OPEN response. The check runs before the byte is used to index mOpenedChannels in openLogicalChannel, across the NXP pn8x and snxxx SecureElement/VirtualISO HAL variants. The fix also makes pn8x closeChannel return early on an invalid channel number.",
      "confidence": "high",
      "backport_notes": [
       "Google shipped one commit per branch, and each hunk wraps the same 4-line success block locally. Use MAX_LOGICAL_CHANNELS for pn8x and mMaxChannelCount for snxxx, plus the matching invalid value (0xff for HIDL, -1 for AIDL). snxxx/aidl/SecureElement.cpp has a different hunk offset between 16 and 17, so expect small context adjustments there."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/nxp/secure_element",
       "sha": "addc064ca4ec34b2100d253b320c651475d58beb",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/nxp/secure_element"
     ]
    },
    {
     "cve": "CVE-2026-58823",
     "osv_id": "ASB-A-522376080",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "Critical",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds length and array-capacity checks in stpropnci_process_std() of the ST NFC HAL, so that RF_NFCEE_DISCOVERY_REQ_NTF and NFCEE_MODE_SET_NTF messages from the NFC controller can no longer write past the fixed-size ee_info and active_nfcee_ids arrays in the global stpropnci_state.",
      "confidence": "high",
      "backport_notes": [
       "A single self-contained commit that touches only libstpropnci/src/stpropnci_std.cc. Its sizeof-based checks depend on ee_info and active_nfcee_ids being fixed-size arrays in stpropnci_state. The patched file is absent in the 14, 15 and 16 security tags."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/st/nfc",
       "sha": "8a18f3300dd54ba02492f3351a539a91ca6f7fa7",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/st/nfc"
     ]
    },
    {
     "cve": "CVE-2025-48564",
     "osv_id": "ASB-A-403565650",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On Android 17 the commit linked to this CVE is a test-only change for IntentForwarderActivityTest, plus two Injector hooks in IntentForwarderActivity for the executor and the device-provisioned state. The substantive fix, seen on Android 14/15/16, makes IntentForwarderActivity resolve the intent MIME type once and pass that same resolvedType to both the canForwardTo check and the target resolveActivityAsUser call. It also sanitizes the selector intent before launching the resolver.",
      "confidence": "medium",
      "backport_notes": [
       "Order: the frameworks/base PackageManager/ApplicationPackageManager resolvedType overloads must land before the IntentForwarderActivity change, and before the IntentResolver change (a separate project) on 16. The Android 17 test commit depends on the substantive fix's test additions (the times(2) canForwardTo capture and the capturedIntents assertions) and changes the Injector interface. Any out-of-tree Injector implementations must therefore add getExecutorService() and isDeviceProvisioned().",
       "Where IntentForwarderActivity exists both in frameworks/base (com.android.internal.app) and in packages/modules/IntentResolver, every copy that is actually in use needs the resolvedType and selector-sanitization change. The 17 commit touches only the frameworks/base copy."
      ],
      "meta": {
       "generated": "2026-10-01",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "323c8b968a8821bf34149803912334be42e682fa",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 323c8b968a88",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48565",
     "osv_id": "ASB-A-407763772",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Android 17 commit linked to this CVE is a test follow-up that moves executor creation and the DEVICE_PROVISIONED read in IntentForwarderActivity behind the Injector and re-enables IntentForwarderActivityTest; the substantive fix (seen in the 14/15/16 commits) resolves the intent MIME type once in IntentForwarderActivity.onCreate and passes that same resolvedType to both the canForwardTo check and the target resolveActivityAsUser call, sanitizes the selector in the mini-resolver path, and uses getLaunchedFromPackage() for the privileged skip-consent check.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the substantive commit ('Add resolveActivityAsUserForExplicitType api to pm') first. The 17 test commit depends on it (it corrects assertions that commit introduced) and is otherwise optional for production behaviour. When backporting the substantive commit, PackageManager.java, ApplicationPackageManager.java, IntentForwarderActivity.java and AbstractMultiProfilePagerAdapter.java must move together because canForward's signature changes; any vendor Injector or PackageManager subclass must implement the new methods."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "323c8b968a8821bf34149803912334be42e682fa",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 323c8b968a88",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2025-48566",
     "osv_id": "ASB-A-397216638",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that same resolvedType to both the cross-profile canForwardTo check and the target-user activity resolution, via a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload. It also sanitizes the selector intent before startActivityAsCaller in the selected-profile launch path, and identifies the privileged skip-confirmation caller with getLaunchedFromPackage(). The commit linked for Android 17 is a test-only follow-up that makes the executor and device-provisioned state injectable.",
      "confidence": "medium",
      "backport_notes": [
       "Backport the substantive commit 'Add resolveActivityAsUserForExplicitType api to pm' first; it touches PackageManager.java, ApplicationPackageManager.java, IntentForwarderActivity.java and AbstractMultiProfilePagerAdapter.java. Commit 323c8b968a88 is test-only and optional. If you take it, it builds on the substantive fix's test code and on the Injector interface, and it includes the AndroidManifest.xml HIDE_OVERLAY_WINDOWS permission and the Android.bp exclude-filter removal.",
       "Any OEM or vendor PackageManager subclass must override the new resolveActivityAsUser(Intent, String, ResolveInfoFlags, int), because the base version throws UnsupportedOperationException. Any custom Injector implementation must add the new resolveActivityAsUser overload (and getExecutorService/isDeviceProvisioned if the test commit is taken)."
      ],
      "meta": {
       "generated": "2026-10-01",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "323c8b968a8821bf34149803912334be42e682fa",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 323c8b968a88",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28581",
     "osv_id": "ASB-A-479515555",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Hardens CallIntentProcessor.doesUserHaveDialerRoleHolder in Telecomm so that a dialer role holder reported by RoleManager for a work profile counts only if PackageManager, queried in that profile's user context, finds the package and reports it enabled.",
      "confidence": "high",
      "backport_notes": [
       "Two-commit sequence. First apply 'Fix issue initiating calls from the work profile.', which adds doesUserHaveDialerRoleHolder and replaces the DefaultDialerManager.getInstalledDialerApplications check. Then apply 'Update fix for initiating calls from work profile', which adds the PackageManager enabled cross-check. The second commit's import hunk may need adapting: for example, the Android 14 imports shown in the material do not include android.content.pm.PackageManager, which the new code needs for NameNotFoundException.",
       "fixInitiatingUserIfNecessary differs by branch. On 14 it has no FeatureFlags parameter and uses UserManager.get(context); on 15 and 16 it takes FeatureFlags and uses telecomResolveHiddenDependencies(). The helper body is identical across 14, 15 and 16."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "c1104b6c1bbebdfa9ba9530569f9eb72c7787ac2",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/services/Telecomm"
     ]
    },
    {
     "cve": "CVE-2026-28582",
     "osv_id": "ASB-A-487522353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ConfirmDeviceCredentialActivity.onCreate now rejects any ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL launch whose intent component class name is not the alias com.android.settings.ConfirmRemoteDeviceCredentialActivity: it logs a warning and calls finish(). Callers can therefore no longer reach the remote-credential flow by targeting the underlying activity directly.",
      "confidence": "high",
      "backport_notes": [
       "One self-contained hunk in ConfirmDeviceCredentialActivity.onCreate plus one constant. It relies on the existing `remoteValidation` boolean and needs the android.content.ComponentName and android.util.Log imports; check that both are present on the target branch. The Android 14 fix ships without the test changes.",
       "Check that the target branch's AndroidManifest declares the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity pointing at this activity. Otherwise the check will reject all remote-validation launches."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "642944e4c4e6de89b416721e2dc8a3110bbf084b",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28584",
     "osv_id": "ASB-A-472269954",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PackageInstallerService now deduplicates SessionParams.whitelistedRestrictedPermissions (rebuilding the list from a HashSet) before intersecting it with the platform restricted permission set, so the list can no longer be made arbitrarily long with repeated valid entries.",
      "confidence": "medium",
      "backport_notes": [
       "The patch is self-contained: one hunk plus a HashSet import. Per the per-major facts, most of the patched region (6/8 lines) is present in the 14/15/16 security tags, so a fork that wants the hardening should be able to apply it with little adaptation. Add SessionSizeLimitTest if it exists on the branch."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "89e9c4b37577b5c87ba148d7f9ce7bcf45745a70",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28593",
     "osv_id": "ASB-A-388272624",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "The Live TV app's \"Send Feedback\" settings action now builds an explicit intent targeting com.google.android.feedback/.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent, so the intent can no longer resolve to an arbitrary app that registered for that action.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained Java change with the same file blob (762a190c -> 1ac5d81e) on all four branches. It should apply cleanly to any tree with an unmodified SettingsFragment. Forks that ship a different feedback app would need to put in their own explicit ComponentName (or setPackage) rather than go back to an implicit intent."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "724154d5db034439eeb911fcf419030094407c4f",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28594",
     "osv_id": "ASB-A-498720726",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes MemoryIntArray pass one explicit element count from Java to both nativeOpen and nativeClose so mmap and munmap always use the same length instead of re-querying the fd's ashmem size, and hardens libcutils ashmem_valid() so a memfd is only accepted as an ashmem replacement when it carries both F_SEAL_GROW and F_SEAL_SHRINK.",
      "confidence": "medium",
      "backport_notes": [
       "The fix has two independent parts. (a) frameworks/base MemoryIntArray.java and android_util_MemoryIntArray.cpp must land together because the JNI signatures change; the test files are optional. (b) The system/core libcutils ashmem-dev.cpp is_memfd_fd() seal check only matters on branches where ashmem_valid() accepts memfds via is_memfd_fd(). Before porting, check that this helper exists on the target branch and that its fcntl headers provide F_GET_SEALS/F_SEAL_*."
      ],
      "meta": {
       "generated": "2026-10-01",
       "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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a44880c82aac2a343ae56ac65ccb349b08bdbc17",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/system/core",
       "sha": "2de25eeaec5500f0bfbc5d1ae320ca8c702e39f6",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/system/core",
       "sha": "6297cab2c921135a98183a84e2b75ed4d74b910c",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "projects": [
      "platform/frameworks/base",
      "platform/system/core"
     ]
    },
    {
     "cve": "CVE-2026-28599",
     "osv_id": "ASB-A-487147249",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ActivityManagerService.addCreatorToken no longer treats intent.getPackage() as the target package when the outer intent has no explicit component. As a result, nested intents inside a package-constrained intent that carries a selector targeting another app now get creator tokens instead of being skipped as \"same app\".",
      "confidence": "high",
      "backport_notes": [
       "Apply the diff, not the commit message. The merged change is just `: intent.getPackage()` -> `: null` in addCreatorToken, and no selector-handling code needs to be ported.",
       "Relevant on branches that already have addCreatorToken and preventIntentRedirect() (the bulletin lists 16, 16-qpr2, 17, 17-next). On 14 and 15 only part of the region is present, and the bulletin does not list them."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ee7ea482d39e1fa263e83b5452376d7c7c79f81a",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28600",
     "osv_id": "ASB-A-490605007",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PaymentDefaultDialog.onCreate no longer reads a UserHandle from the Intent.EXTRA_USER extra; the userId passed to buildDialog is now always UserHandle.myUserId().",
      "confidence": "high",
      "backport_notes": [
       "Single-file change (1 insertion, 8 deletions) with identical context (file index d333b3da6ac..d556b6df929) on 15, 16 and 17. It should apply cleanly wherever onCreate still contains the UserHandle/EXTRA_USER block. UserHandle is still used via UserHandle.myUserId()."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "d1db7e8d9994ee8dee0b95cfb08861e2c523b14b",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28602",
     "osv_id": "ASB-A-466379087",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ClipboardService now reads and writes the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS secure setting with an explicit userId (getIntForUser/putIntForUser), so clipboard access notifications are gated by the setting of the user given in the call.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, two-hunk change. The only difference between versions is the method around the second hunk ('return' on 14 to 16, 'return false' on 17). In a fork, find the Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, ...) call in the notification path and the putInt call in setClipboardAccessNotificationsEnabledForUser, and make sure a userId variable is in scope in the notification method.",
       "The commit message says a mock test will follow in a separate CL. That test is not part of this fix and is not in the material."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "4b3bf7fb29ec6e50bcfb91d96692b4536da195fb",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28603",
     "osv_id": "ASB-A-491253353",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Settings' AppRestrictionsFragment, the vetted copy of an app-supplied custom restrictions Intent now has all four FLAG_GRANT_*_URI_PERMISSION flags removed and its data and clipData cleared before Settings starts the activity with it.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The six added lines go after vettedIntent.setPackage(activityInfo.packageName) in assertSafeToStartCustomActivity on all branches; only the hunk offsets differ. The test needs SettingsRoboTests and uses reflection into the private method and the mPackageManager field, so adapt it if the fork lacks that infrastructure."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "a3cbe8fbf286ff5ff5426e640ca8d043b6e9a18e",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28606",
     "osv_id": "ASB-A-495543559",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes AdapterService always drop the per-device entry in mBondAttemptCallerInfo whenever a bond state transition leaves BOND_BONDING. On Android 17 it does this by removing the remove_bond_caller_info aconfig flag gate; on Android 15/16 it adds the removal block directly.",
      "confidence": "medium",
      "backport_notes": [
       "On branches without the flagged block (as on 15/16), do not cherry-pick the 17 unflag commit. Instead, insert the unflagged block as in 9b196f44770c / 07542331e7c4, add the BOND_BONDING import, and confirm that mBondAttemptCallerInfo and CallerInfo exist in the target AdapterService (the material does not show when they were introduced).",
       "On branches that carry the flagged block, cherry-pick 1b0d684402b2. It also deletes the flag definition from flags/pairing.aconfig, so remove any other references to Flags.removeBondCallerInfo() (tests included) or the build will fail (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "1b0d684402b2f62c52288840f014820ae4558947",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28607",
     "osv_id": "ASB-A-489225275",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a caller permission check (REQUEST_INSTALL_PACKAGES requested, or INSTALL_PACKAGES granted) at the start of UnarchiveErrorActivity.onCreate and moves the existing check in UnarchiveActivity.onCreate ahead of the PIA v2 forward to UnarchiveLaunch, so callers without either permission can no longer reach the unarchive dialogs.",
      "confidence": "high",
      "backport_notes": [
       "In the 15/16 diffs, the security-relevant change is the new gate at the top of UnarchiveErrorActivity.onCreate plus the SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS flag. The UnarchiveActivity hunks there are a refactor to the new PackageUtil helpers. If UnarchiveErrorActivity lacks them, add the LOG_TAG constant and the permission/Process/Log/Arrays imports.",
       "If a branch carries the use_pia_v2 forward in UnarchiveActivity (or UnarchiveErrorActivity), make sure the `isVersionTwoEnabled` block ends up after the INSTALL_PACKAGES / REQUEST_INSTALL_PACKAGES check. Refactoring the helpers alone is not enough."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "743a34f05af0f66fed81d9c9add5d4ff037396e0",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28612",
     "osv_id": "ASB-A-475542441",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Reorders the activity start paths in ActivityStarter and ActivityStartController so the intent-redirect checks run before supervisor.resolveIntent/resolveActivity. The checks are the EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN abort and the IntentCreatorToken extraction. Resolution can change the intent's component and so invalidate the creator token.",
      "confidence": "high",
      "backport_notes": [
       "Single commit across two files. The ActivityStartController hunk depends on removing `private` from ActivityStarter.logAndAbortForIntentRedirect, so apply both files together. The bulletin lists 16, 16-qpr2, 17 and 17-next as affected. Confirm the IntentCreatorToken / EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN logic exists in the target branch before applying."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a2f24fcecabdd20cb967c4d167f77277c93fb7e5",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28613",
     "osv_id": "ASB-A-329233015",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In Live TV's ChannelImpl.initAppLinkTypeAndIntent, the fix refuses to build a channel app-link Intent when the resolved target activity is exported and declares a permission, in addition to the existing block on activities in the Live TV package itself.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit touching one method. The pre-image blob (5be1179d) is the same on all four branches, so it should apply cleanly wherever the isProtectedActivity block exists.",
       "Forks with a different CommonConstants.BASE_PACKAGE check should keep it and add the isPermissionProtected check after it, before mAppLinkIntent is assigned."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "a10682aac13856f206b13bc61da047f171a37b89",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28614",
     "osv_id": "ASB-A-486385459",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an isCallerValid() check to SystemUI's SlicePermissionActivity.onCreate so the slice permission dialog is only shown when the activity was actually launched by the slice provider's package, SystemUI itself, or the \"android\" system package; otherwise the activity finishes immediately.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; Google applied the same Java change on 14 through 17 with only line-offset differences. Activity.getLaunchedFromPackage() became public API in Android 14 (API 34) and existed earlier as a hidden API (general platform knowledge); forks backporting further should verify its availability.",
       "The new Kotlin test relies on SysuiTestCase and reflection into Activity internals (mActivityInfo, mFragments, mApplication, mWindow, ContextWrapper.mBase); it is optional for the security fix and may need adjustment on forks with modified SystemUI test infrastructure."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7e25b93a1faa85adc23fd90879b57117349c9896",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28617",
     "osv_id": "ASB-A-498792914",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On V+, WifiNetworkSuggestionsManager now clears the vendorData list on each suggestion's WifiConfiguration when suggestions are added. Caller-supplied PersistableBundle vendor data therefore no longer reaches, through the suggestion add path, the XmlUtil OUI-keyed-data serialization/deserialization that the commit message says has no depth bound.",
      "confidence": "medium",
      "backport_notes": [
       "Self-contained one-file change: add the Collections import and insert the SdkLevel-gated loop right after the 'bad wifi suggestion from app' early return. The target branch must provide WifiConfiguration.setVendorData and SdkLevel.isAtLeastV().",
       "The commit message refers to XmlUtil depth handling and cites two cherry-pick sources, but the diff contains only this file. Check separately whether a companion XmlUtil change exists and whether the target branch needs it."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Wifi",
       "sha": "9b1148745f6b5b36dbdccbde65026f6b79fde25b",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Wifi"
     ]
    },
    {
     "cve": "CVE-2026-28620",
     "osv_id": "ASB-A-477024345",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes the intent-redirection URI grant logic in ActivityStarter and ActivityStartController. When an intent's creator UID differs from the calling UID, URI grants are now applied only if both the caller check and the creator check return grants, and the grants used are the caller's. Previously the two parties' grants were merged. The fix also removes NeededUriGrants.merge.",
      "confidence": "high",
      "backport_notes": [
       "The fix assumes the intent redirection hardening is already present (IntentCreatorToken, DEFAULT_INTENT_CREATOR_UID, creatorIntentGrants, logAndThrowExceptionForIntentRedirect). Because NeededUriGrants.merge is deleted, check that no vendor or OEM code calls it before applying.",
       "The 16 and 17 patches differ in context lines inside the hunks (applicationInfo.uid vs getUid()), so applying them on a fork may need fuzz or a manual context fix as well as offset changes."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8df772fb59b4f688f09bc94c7afc9615cfb564dd",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28622",
     "osv_id": "ASB-A-484928416",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now returns an empty result whenever latitude/longitude columns appear in the SQL selection, GROUP BY or HAVING clause (regardless of whether a sort order is also present), and neutralizes those columns in the sort order with a case-insensitive, word-bounded replacement instead of a case-sensitive substring replace.",
      "confidence": "high",
      "backport_notes": [
       "A single commit in this tag, cherry-picked from two internal changes. It relies on the pre-existing indexMediaLatitudeLongitude() flag, isCallingPackageSelf() and getClausesForFilteringGeolocationData(), so a target branch needs those in place. MediaProvider is a mainline module, so its code can also be updated through module updates (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "43a7df8794a86d21cd89e10ad468af13bf292770",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-28623",
     "osv_id": "ASB-A-494698714",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Stops the generic ranging framework in the Uwb module from serializing the device's Bluetooth MAC address inside BleCsRangingCapabilities and BleRssiRangingCapabilities parcels, substituting a fixed 00:00:00:00:00:00 on the receiving (app) side.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The non-test source hunks are identical on 16 and 17. Keep the write and read sides in the same change so the parcel stays aligned."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Uwb",
       "sha": "5dbf65f08b166639036543b5fd28c5ded0023f41",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Uwb"
     ]
    },
    {
     "cve": "CVE-2026-28624",
     "osv_id": "ASB-A-494585668",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the EXTRA_DATA Intent extra that ConfirmDeviceCredentialActivity read from its launch intent and returned unchanged as its RESULT_OK result data. FingerprintSettings now keeps the gatekeeper password handle in its own state instead of reading it from that returned Intent.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. It depends on the long-based GatekeeperPasswordProvider.requestGatekeeperHat(long, long, int) and removeGatekeeperPasswordHandle(long); per index search, GatekeeperPasswordProviderTest exercises the first. Confirm both exist in the target branch. If a fork added other callers of the removed five-argument launchBiometricPromptForMandatoryBiometrics or of EXTRA_DATA, move them to caller-held state the same way FingerprintSettings was."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "d800aed5a84f48c5a5f7f8f3db43fdae296dd7eb",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-28626",
     "osv_id": "ASB-A-332883816",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a launching-package check (getLaunchedFromPackage() must equal CommonConstants.BASE_PACKAGE) in SetupPassthroughActivity before it forwards the embedded TV input setup intent, finishing the activity otherwise.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit, self-contained change; the same diff (identical pre/post blob hashes) is applied on 14 through 17. It uses Activity.getLaunchedFromPackage() (API 34) and CommonConstants.BASE_PACKAGE, which is already imported in the file. At runtime the SDK_INT gate skips the check on SDK below 34, but building still needs a compile SDK of 34 or higher."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/TV",
       "sha": "25c98c9106758d73aac17428b6fe99caad8a3d05",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 25c98c910675",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/TV"
     ]
    },
    {
     "cve": "CVE-2026-28627",
     "osv_id": "ASB-A-455566331",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In btm_sec_encrypt_change, a BR/EDR Encryption Change event reporting HCI_ENCRYPT_MODE_ON (E0 encryption) is now checked with btm_sec_is_enc_algo_downgrade and, if the device previously used Secure Connections, the link is disconnected with HCI_ERR_AUTH_FAILURE instead of being accepted as encrypted.",
      "confidence": "medium",
      "backport_notes": [
       "The fix depends on btm_sec_is_enc_algo_downgrade (called as btm_sec_is_enc_algo_downgrade(handle, 0, 0)), acl_set_disconnect_reason and btm_sec_send_hci_disconnect(p_device, reason, handle, comment); on older branches confirm the helper exists and its semantics (and whether it checks the btsec_check_implicit_encryption_downgrade aconfig flag) before porting the 12-line hunk.",
       "Bluetooth is a mainline module, so the fix may ship via module update rather than the platform security tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "5819c807e810b7e0d732654d1cf6761999d7be61",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Bluetooth"
     ]
    },
    {
     "cve": "CVE-2026-28630",
     "osv_id": "ASB-A-488233632",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ContactsPickerActivity now calls window.setHideOverlayWindows(true) in onCreate, and the HIDE_OVERLAY_WINDOWS permission is added to the manifest, so that other apps' non-system overlay windows cannot be drawn over the Contacts Picker UI.",
      "confidence": "high",
      "backport_notes": [
       "No backport is needed for 14 to 16, because the ContactsPicker project does not exist in those security tags. On 17, apply both the manifest permission and the onCreate call together."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/ContactsPicker",
       "sha": "f44519d86b51b3508f6fbfe93f5ee4baddd11069",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/ContactsPicker"
     ]
    },
    {
     "cve": "CVE-2026-28631",
     "osv_id": "ASB-A-461500706",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "On Android 14/15 the fix makes IntentForwarderActivity call getWindow().setHideOverlayWindows(true) on its mini resolver window. The commit linked to Android 16/17 is a companion: it refactors IntentForwarderActivity for testability (Injector gains getExecutorService() and isDeviceProvisioned()) and re-enables IntentForwarderActivityTest, which now needs the HIDE_OVERLAY_WINDOWS permission because the activity calls setHideOverlayWindows().",
      "confidence": "medium",
      "backport_notes": [
       "For branches without the hardening, the essential change is the single getWindow().setHideOverlayWindows(true) call directly after setContentView(R.layout.miniresolver) in IntentForwarderActivity.java, as in the 14/15 commits. The 16/17 test commit assumes that call exists: its manifest adds HIDE_OVERLAY_WINDOWS specifically because of it. Any custom Injector implementation must also add the two new Injector methods.",
       "No 'Fix in fork' section was provided, so no per-file fork verdict can be given."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "323c8b968a8821bf34149803912334be42e682fa",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref 323c8b968a88",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28633",
     "osv_id": "ASB-A-430284594",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In safe mode, VoiceInteractionManagerService stops parsing the XML recognition-service metadata of third-party (non-system) RecognitionService providers and of the currently configured recognizer, so an app with oversized metadata cannot crash system_server during a safe-mode boot.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with no dependencies. The RecognitionServiceInfo.java change has the same blob hashes on 14 through 17; only the VoiceInteractionManagerService.java line offsets differ. A fork that added its own callers of RecognitionServiceInfo.getAvailableServices must update them for the new isSafeMode parameter."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "4c5500a0d604fe9f98dfd1dc41f49d9493bc9f0d",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28634",
     "osv_id": "ASB-A-471796626",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a TelephonyPermissions.checkSubscriptionAssociatedWithUser check to PhoneInterfaceManager.handleUssdRequest, so a USSD request is rejected with SecurityException when the calling user is not associated with the target subscription.",
      "confidence": "high",
      "backport_notes": [
       "The production change is self-contained (5 lines) and needs TelephonyPermissions.checkSubscriptionAssociatedWithUser to be available. On 14 the preceding line is enforceCallPermission() rather than enforceTelephonyFeatureWithException, so anchor on the Binder.clearCallingIdentity() line of handleUssdRequest.",
       "The new tests need replaceInstance(SubscriptionManagerService.class, \"sInstance\", ...), a mocked SubscriptionManagerService.isSubscriptionAssociatedWithUser, and a mockable SubscriptionManager from mContext. Forks with a different PhoneInterfaceManagerTest setup are most likely to need to adapt the test hunk."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "5ef227b0a2f76cdc6f4c8596897e04adf7edf225",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/services/Telephony"
     ]
    },
    {
     "cve": "CVE-2026-28638",
     "osv_id": "ASB-A-470260357",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider now includes 'XMP_' boxes in IsoInterface's flattened box list, and XmpDataParser now redacts XML elements whose EXIF attributes (not only element names) carry sensitive tags such as GPSLatitude, closing a location redaction bypass for ISO media files.",
      "confidence": "high",
      "backport_notes": [
       "The one-line `case BOX_XMP:` change applies only to trees whose IsoInterface.parseNextBox already has the selective-flattening switch. The 15 commit shows the refactor that introduces the switch bundled with the fix. The 14 commit has no switch change and instead adds list-based box getters.",
       "There are two code shapes. On 14, XmpInterface.process uses depth-aware skipping and processes every XMP box. On 15+, XmpDataParser.isSensitiveTagOrAttribute uses END_TAG name matching and extracts only the first box. Use the shape that matches the parser class in the target tree."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "11009f3800f9007f3e172b9229938fa9a5c0a2ef",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-28650",
     "osv_id": "ASB-A-493714520",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WindowState.setHiddenWhileSuspended now checks the window's base type (getBaseType(), which for sub-windows reflects the top parent window per the commit message) instead of mAttrs.type when deciding whether to hide a window while its owning package is suspended. Sub-windows attached to a system alert window are therefore hidden too.",
      "confidence": "high",
      "backport_notes": [
       "One commit with a two-line functional change in WindowState.setHiddenWhileSuspended. It uses getBaseType() and isSystemAlertWindowType, which the diffs for Android 14 through 17 also use. Only the test helper differs: createWindow on 14/15, newWindowBuilder on 16/17."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "236258a3e304683b229200f174dc3c7ba70717e1",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28652",
     "osv_id": "ASB-A-492683684",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds enforceRangingPermission() checks to five Binder-exposed out-of-band (OOB) ranging methods in RangingServiceImpl (oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed, registerOobSendDataListener) that previously forwarded to RangingServiceManager without calling enforceRangingPermission().",
      "confidence": "high",
      "backport_notes": [
       "Self-contained five-line addition relying only on the existing enforceRangingPermission() helper in RangingServiceImpl; on trees where the ranging service exists, apply by inserting the call at the top of each of the five OOB methods. Uwb is a mainline module, so updates may also ship via module update rather than platform tag (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/Uwb",
       "sha": "3be00374c294c51c56ce29fb0ca1f0251652f02d",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/Uwb"
     ]
    },
    {
     "cve": "CVE-2026-28655",
     "osv_id": "ASB-A-477278752",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "RemoteViews parceling is reworked: one ApplicationInfoCache list is written once at the hierarchy root and nested RemoteViews refer to it by integer id. The Parcel.allowSquashing and forced-immediate-Bundle-unparcel mechanism in RemoteViews.writeToParcel and ReflectionAction is removed.",
      "confidence": "medium",
      "backport_notes": [
       "Apply the ApplicationInfoCache commit first and the allowSquashing-removal commit second: the second commit's base index, baaaee43c352, is the result of the first. The second commit is small and mechanical. The first touches every mApplication assignment and the parcel format. When porting it, check that each constructor path (sized, landscape/portrait, copy, parcel) sets mApplicationId, and that writeToParcel and the parcel constructor write and read the cache at the same position in all three branches.",
       "On 14 the file differs (public writeToParcel(Parcel, int), ReflectionAction.value, no draw instructions or original density), so the hunks must be placed by hand."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "56f83ee5f1f554cafd9629ff20ee4564c981345d",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "b78e053388018e2dc8be512eda8660e190759a55",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28657",
     "osv_id": "ASB-A-498916138",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppWidgetConfigActivityProxy.onActivityResult now forwards only the extras of the configuration activity's result Intent (via a fresh Intent().putExtras(data)) instead of the original Intent. URI permission grant flags and the data URI from the widget provider are therefore no longer relayed through the system-privileged proxy's setResult().",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The functional change is the replacement in AppWidgetConfigActivityProxy.onActivityResult and applies wherever that file exists (16-qpr2 and later per the bulletin). Drop the android.view imports only if nothing else in the target file uses them.",
       "The test additions use Kotlin and androidx ActivityScenario in FrameworksCoreTests and register the @hide proxy activity in the test APK manifest. They can be adapted or left out if the target branch lacks that setup."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7d46aaa9199b9c9d3ddd33ce91d57e89a657d6e9",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28658",
     "osv_id": "ASB-A-507575871",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AccountsDb.findMetaAuthUid now derives the account type from a meta_key by stripping the fixed \"auth_uid_for_type:\" prefix instead of splitting the key on \":\" and taking the second token, so account types containing a colon are no longer truncated when building the type-to-authenticator-UID map.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that only touches AccountsDb.java. The same diff was applied on 14 through 17 with only line-offset differences, so it is expected to port easily to trees that still have META_KEY_DELIMITER and the TextUtils.split call in findMetaAuthUid."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "31e7981c9d23a1b39997d09ac38e22174761ffa2",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28660",
     "osv_id": "ASB-A-498588487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a PackageInstallerService.getAllSessions(userId, filterCallingUid) overload and makes LauncherAppsService.getAllSessions pass the real caller UID. SessionInfo generation and filtering then use the launcher client's UID, not system_server's UID (identity is cleared at that point), so originatingUri/referrerUri are scrubbed as intended.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. It uses the existing PackageInstallerSession.generateInfoForCaller(boolean, int) and shouldFilterSession(Computer, int, SessionInfo); the 15/16/17 diff context shows both. The fix depends on generateInfoForCaller doing the per-UID scrubbing, which is not shown.",
       "The 15 and 16 backports carry only the two service file changes; the test was added only in the 17 commit."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6acdd7f1a72dcfed0ada404be142a7b40f68f93a",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28663",
     "osv_id": "ASB-A-492867206",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "LauncherAppsService.buildIntentSenderForUser now creates its PendingIntent with ActivityOptions whose PendingIntentCreatorBackgroundActivityStartMode is MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE instead of null options, so the app-market IntentSender (getAppMarketActivityIntent) no longer gives an unrestricted background activity launch on behalf of the system server.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained one-file change: it needs ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode and MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE in the target branch. The diff adds no imports, so Bundle and ActivityOptions are already imported in LauncherAppsService.java on the branches where it applies.",
       "Verify with atest CtsPackageManagerTestCases:android.content.pm.cts.LauncherAppsForHiddenProfilesTest as named in the commit."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "b8e217c4e6cd732b12359d880882a3a45923cf51",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-28664",
     "osv_id": "ASB-A-489675413",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In RuntimeImage::WriteImageToDisk, ART now calls fchmod() to set the runtime-generated app image (.art) file to mode 0444 right after the file is created, and fails the write if fchmod fails.",
      "confidence": "high",
      "backport_notes": [
       "This is a self-contained change to one file. Add #include <sys/stat.h>, and put the fchmod block in RuntimeImage::WriteImageToDisk right after the early return on image-file creation failure and before image->FillData(). Google used the same hunk on 14 through 17, with only line offsets differing. ART is a mainline (APEX) module, so the fix may also arrive through an ART module update rather than the platform build (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/art",
       "sha": "70be2043e6dbcd080b82022a3dee030de7caad80",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/art"
     ]
    },
    {
     "cve": "CVE-2026-28668",
     "osv_id": "ASB-A-501541998",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that a realloc rejected by the android allocation limit returns nullptr without freeing the caller's still-valid original pointer.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The code hunk is identical on all four branches and only line offsets differ, so it should apply to any tree whose LimitRealloc still contains the 'Free the old pointer. LimitFree(old_mem);' lines. The resolver reports 6/8 lines of the patched region present on each tag. The material does not explain that figure, but the code at android-security-17.0.0_r1 already contains the fixed lines."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/bionic",
       "sha": "48a7e3362ff9021cb5168058569627ac21e5a8a7",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/bionic"
     ]
    },
    {
     "cve": "CVE-2026-28671",
     "osv_id": "ASB-A-395640294",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider.update's file-movement path now checks for an existing database row at the computed destination path before Os.rename. A row with no file on disk is deleted; a row that has a file requires write permission. If the follow-up DB update then fails on the UNIQUE(_data) constraint and the conflicting path is the active rename target, the conflicting row is deleted and the update is retried, so a squatter row cannot keep the path a pending file is being published to.",
      "confidence": "medium",
      "backport_notes": [
       "Single self-contained commit that touches only MediaProvider.java and its test. It relies on existing helpers (queryForSingleItem, enforceCallingPermission, clearLocalCallingIdentity/restoreLocalCallingIdentity, DatabaseHelper.runWithTransaction) and adds no imports. Place the checkIfPathAlreadyExists call after the final afterPath adjustment and right before the 'Moving' log in the movement block. MediaProvider ships as a Mainline module, so devices may also receive it through a module update (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "87beb3978b6a5182bcf9f6515a55f44dd7a5c40b",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45517",
     "osv_id": "ASB-A-471796999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds structural (scheme, authority, picker path pattern) and user-ID validation of photopicker pre-selection URIs before the existing permission check, in both the PickerUriResolver and the v2 MediaQueryForPreSelection filtering loops.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with no dependencies beyond LocalUriMatcher and its PICKER_ID / PICKER_GET_CONTENT_ID / PICKER_TRANSCODED_ID codes. On trees without PICKER_TRANSCODED_ID or without the v2 MediaQueryForPreSelection class, follow the Android 15 variant (PickerUriResolver only, two match codes).",
       "Existing tests that build picker URIs with a fixed user ID (0 or TEST_USER) can fail the new gate when that ID differs from UserHandle.myUserId(). Update them as the commit does."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "50d220c204df894fa99ce5016588cd7ff6868dae",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45518",
     "osv_id": "ASB-A-393377017",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an OPTION_CHECK_BRACKETS mode to android.database.sqlite.SQLiteTokenizer that rejects SQL with unbalanced or prematurely closed parentheses, and makes ContactsProvider's SelectionBuilder.addClause run every clause through it so a caller-supplied selection cannot break out of the \"(clause) AND (clause)\" wrapping.",
      "confidence": "high",
      "backport_notes": [
       "Apply the frameworks/base SQLiteTokenizer change first, because it defines OPTION_CHECK_BRACKETS and allows a null checker. Then apply the ContactsProvider SelectionBuilder change, which uses both. Keep the '&=' to '&' correction: without it the new option bit is cleared at the first of the corrected tests, and the check is silently ineffective."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5a73367d5b3a65f760cfcf639d425957d9e143fa",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "5ec4850fbe73949e42b016b6063bf025fd92337a",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/providers/ContactsProvider"
     ]
    },
    {
     "cve": "CVE-2026-45519",
     "osv_id": "ASB-A-471797062",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Before photopicker pre-selection URIs (EXTRA_PICKER_PRE_SELECTION_URIS) are permission-checked and used, the fix now checks their structure (scheme, authority, picker path pattern) and their user ID. It does this in both PickerUriResolver.processUrisForSelection and MediaQueryForPreSelection.",
      "confidence": "high",
      "backport_notes": [
       "On 16/17 the PickerUriResolver.java hunk adds no imports, so LocalUriMatcher, ContentResolver, UserHandle, Nullable and the PICKER_ID / PICKER_GET_CONTENT_ID / PICKER_TRANSCODED_ID constants must already be visible there. The 15 variant adds the Nullable import and omits PICKER_TRANSCODED_ID. MediaQueryForPreSelection.java adds an android.os.UserHandle import. On trees without photopicker v2 (MediaQueryForPreSelection), only the PickerUriResolver hunk applies.",
       "When backporting the MediaQueryForPreSelection hunk, the hunk depends on the getUserId visibility change (package-private to public static) in PickerUriResolver. Tests that use TEST_USER for pre-selection URIs must switch to UserHandle.myUserId()."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "50d220c204df894fa99ce5016588cd7ff6868dae",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45520",
     "osv_id": "ASB-A-490286986",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "BiometricsSettingsBase now accepts the EXTRA_USER_ID and gatekeeper password handle intent extras only when the hosting SettingsActivity was launched by the Settings package itself; external callers get the current user and no gatekeeper handle, so credential confirmation is enforced.",
      "confidence": "high",
      "backport_notes": [
       "The source change is self-contained in BiometricsSettingsBase.java but depends on SettingsActivity.getInitialCallingPackage() existing on the target branch; on Android 14 the android.app.Activity import must also be added, as Google did. The test changes additionally use SettingsActivity.EXTRA_INITIAL_CALLING_PACKAGE and the ShadowPasswordUtils test shadow with setCallingAppPackageName/reset."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "759e75cec1c1f39abb7a1ad1bf5fe10ea2a9de22",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2026-45521",
     "osv_id": "ASB-A-491748298",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid() and AppFuseBridge.openFile rejects the request (FuseUnavailableMountException, surfaced as a null return) when the caller UID does not match the uid recorded in the AppFuse MountScope for the requested mountId.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The AppFuseBridge.java hunk is byte-identical on 14 through 17 (same blob ids 292314840efb..d839b30ddf44), so the StorageManagerService.java hunk is the only part that may need offset adjustment. The fix requires MountScope to already expose a `uid` field populated by AppFuseMountScope(uid, name); verify this on any fork before applying.",
       "Update every caller of AppFuseBridge.openFile to the new (uid, mountId, fileId, mode) signature. In AOSP the only caller shown is openProxyFileDescriptor."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "03988cf95e56a9b472933cf4416c288faff2ebe7",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45525",
     "osv_id": "ASB-A-470265726",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MediaProvider's ISO/MP4 XMP handling is changed so that an XMP box larger than the allocation limit no longer aborts box parsing and is instead redacted in its entirety, closing a location-metadata redaction bypass.",
      "confidence": "high",
      "backport_notes": [
       "Branches that have allocateBuffer in IsoInterface, getBoxBytesForXmpUuid/getBoxRangesForXmpUuid and XmpDataParser (15/16/17) take the reference diff unchanged. Branches on the older XmpInterface-based code (14) need the adapted variant, which adds the size limit itself and handles a null rawXmp in XmpInterface.process.",
       "No 'Fix in fork' section was provided, so no per-file fork verdict can be reported."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 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-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "21270fd5611883be4880897f2a929f34121c62c3",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2026-45527",
     "osv_id": "ASB-A-503550257",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In HeifCleanAperture.cpp:convertCleanApertureToRect(), the fix performs the crop-rect bounds check in int64_t arithmetic before computing *right and *bottom, so crafted CleanAperture values can no longer trigger a signed int32 overflow (IntSan abort) in media.extractor.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit; the pre-image blob is the same (f0a086703e) on 15/16/17, so the patch should apply cleanly wherever that version of HeifCleanAperture.cpp exists. The file is absent at android-security-14.0.0_r29."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "1e00537f8f438a9c9167e6d532c081d6401f1620",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/av"
     ]
    },
    {
     "cve": "CVE-2026-45528",
     "osv_id": "ASB-A-504573577",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp(..., packageName, appInfo.uid). Before, it called PendingIntent.getActivity on a package Context created by system_server with the caller identity cleared. Per the commit message, this stops callers from obtaining a PendingIntent registered under the target package identity with system privileges.",
      "confidence": "high",
      "backport_notes": [
       "The replacement block is identical across 14 through 17. Only the removed lines differ (createPackageContext vs createPackageContextAsUser). Per its diffstat, the 17 commit adds no imports. A backport to another tree should confirm that LocalServices and ActivityManagerInternal are imported, and that ActivityManagerInternal exposes getPendingIntentActivityAsApp(int, Intent, int, Bundle, String, int). The 14 fix uses this method, so it exists on the 14 security branch."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 5,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "43ef4a09e4485648014cc7d55491f6f8dabcea81",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-45529",
     "osv_id": "ASB-A-418776187",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MmsSmsProvider.query now limits the simple-conversations, conversation recipients/subject, search and searchSuggest URIs to messages whose sub_id belongs to a subscription associated with the calling user. It returns an empty cursor when the user has no associated subscriptions.",
      "confidence": "high",
      "backport_notes": [
       "The material shows one commit per branch, linked by bug_id. The commit relies on selectionBySubIds already being computed in query(), and the tests rely on the getSubscriptionInfoListAssociatedWithUser mock in setUp. On branches without the otpFilter/canReadRestrictedMessages code, use the 14-16 variant of getTextSearchQuery and drop the @DisableFlags annotations."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "1fab99e951d5c57e7cdfb5a5bd1d72809f4857da",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/TelephonyProvider"
     ]
    },
    {
     "cve": "CVE-2026-49881",
     "osv_id": "ASB-A-501155244",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Removes InCallController.serviceClassExists and the 'android.telecom.CLASS_EXISTENCE_CHECK' metadata check from Telecom. That code loaded a third-party package's code inside Telecom during InCallService enumeration (CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY, then getClassLoader and Class.forName). The fix also drops the now-unused metadata from ConnectivityCallListenerService in the framework manifest.",
      "confidence": "high",
      "backport_notes": [
       "The fix spans two repositories. The security-relevant change is the Telecomm removal of serviceClassExists and the metadata check. The frameworks/base manifest change is cleanup and can be applied after it. The fix is only needed on trees that carry the CLASS_EXISTENCE_CHECK feature: Android 17 per the material, while 14 to 16 are not affected per the bulletin."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cbb9f1f1ab9c8a37a8859c9b9ff07972a13990ba",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "668eb07260ec0f1fbb00910f2b31a4c10b857906",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 2,
     "projects": [
      "platform/frameworks/base",
      "platform/packages/services/Telecomm"
     ]
    },
    {
     "cve": "CVE-2026-49887",
     "osv_id": "ASB-A-480976221",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Fixes an inverted boolean in PackageInstaller v2's InstallRepository.maybeRemoveInvalidInstallerPackageName so that a mismatching EXTRA_INSTALLER_PACKAGE_NAME is stripped when the caller lacks INSTALL_PACKAGES, rather than when it holds it.",
      "confidence": "high",
      "backport_notes": [
       "Single one-line change with no dependencies. On any branch that has the v2 InstallRepository.kt, check the sign of the isPermissionGranted(...) conjunct in maybeRemoveInvalidInstallerPackageName and add the negation if it is missing. Branches without that file (e.g. 14 at 14.0.0_r29) have nothing to patch for this CVE."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "110da4cfe2a84413cb7d1a8786d77fcb1bee34fe",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-49895",
     "osv_id": "ASB-A-503551046",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Raises the EHT Operation element minimum-length constant in src/common/ieee802_11_defs.h from 1 to 5. The constant is EHT_OPERATION_ELEM_MIN_LEN on Android 17 and EHT_OPERATION_IE_MIN_LEN on Android 14/15/16. With the new value, the length check that guards access to the EHT Operation Information field counts the 1-byte parameters field plus the 4-byte basic_eht_mcs_nss_set field.",
      "confidence": "high",
      "backport_notes": [
       "This is a one-line constant change. On Android 14/15/16 the macro is named EHT_OPERATION_IE_MIN_LEN instead of EHT_OPERATION_ELEM_MIN_LEN, so change the value under whichever name the target tree uses.",
       "Before backporting, confirm that the target tree's EHT Operation parser (get_eht_operation_channel_width() or its equivalent) really gates oper_info access on this macro. If a fork uses a hardcoded length check instead, changing the constant will not fix it."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "14398e6a666325fb1c8048ec230aff09c922566e",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/wpa_supplicant_8"
     ]
    },
    {
     "cve": "CVE-2026-49913",
     "osv_id": "ASB-A-505452999",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now compares the mmap() result against MAP_FAILED instead of NULL, and unmaps the source buffer through an android::base ScopeGuard on every exit path.",
      "confidence": "high",
      "backport_notes": [
       "This is a single, self-contained commit. Its only new dependency is libbase's android-base/scopeguard.h, so the HAL default implementation must link libbase (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/hardware/interfaces",
       "sha": "d9f6b94ba0f05f5832f3222f719fc9b74b464105",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/hardware/interfaces"
     ]
    },
    {
     "cve": "CVE-2026-55290",
     "osv_id": "ASB-A-499091487",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds early validation of stringsStart and stylesStart against the chunk size in ResStringPool::setTo and replaces the style-offset-array checks so that styleCount * sizeof(uint32_t) is verified (overflow-safe) to fit both within the data size and before stringsStart, closing an OOB heap read reachable via ResStringPool::styleAt when stringCount is zero.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit. The published hunks are identical apart from line offsets on Android 14 through 17. When merging into customized code, make sure the two old checks (the numeric_limits overflow check and 'styleCount * sizeof(uint32_t) > (stringsStart - styleOffsetsStart)') are removed, not duplicated."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7e42d365a0c2a82c9a5e6f42c09c329b08d56319",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2026-55294",
     "osv_id": "ASB-A-505000651",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Changes ihevcd_get_tu_data_size() in libhevc's decoder/ihevcd_utils.c to size the tu_map region with num_tu (luma plus chroma TUs) instead of num_luma_tu, so the TU data buffer has room for the tu_map entries that initialization writes for both luma and chroma.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained one-line change with no dependencies. It applies wherever ihevcd_get_tu_data_size() still sizes the tu map with num_luma_tu. After applying, check that the line reads `num_tu * sizeof(UWORD8)`."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 1,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/libhevc",
       "sha": "9aa38f2733f4071f7c4dc61bbe8f583978a7606a",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/libhevc"
     ]
    },
    {
     "cve": "CVE-2026-58822",
     "osv_id": "ASB-A-506109174",
     "source": "osv",
     "announced": "2026-09-01",
     "severity": "High",
     "types": [
      "RCE"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds size guards to FreeType's LCD rasterizers (ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c). They return Raster_Overflow when bitmap width/3 or rows/3 exceeds 0x7FFF, to address a 16-bit span x-coordinate truncation that the commit says leads to a heap underflow write.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit with no dependencies shown. The two guards use only bitmap->width, bitmap->rows and FT_THROW( Raster_Overflow ). Google applied the same 8 lines to 14 through 17, with only line offsets differing."
      ],
      "meta": {
       "generated": "2026-10-02",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-17.0.0_r1",
      "date": "2026-09-08",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/external/freetype",
       "sha": "4321c90fc79ec55f50adb618017d0ef0186f0681",
       "tag": "android-security-17.0.0_r1",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/external/freetype"
     ]
    }
   ],
   "tags": [
    "android-security-17.0.0_r1"
   ],
   "unannounced": [
    {
     "bug_ids": [
      "512973352"
     ],
     "commits": [
      {
       "project": "platform/external/wpa_supplicant_8",
       "sha": "3426cb0086f9f7e54182230c797681c719c81747",
       "subject": "Fix NAN frequency list handling in AIDL interfaces.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "503553199"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "f103cdde766d5e5deffeac1fd5a4b81753a037dc",
       "subject": "Fix race conditions in CryptoHal plugin usage",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "503541238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "237289dd22e131dfb3c1f2438c793968f7f7ea11",
       "subject": "Fix MediaBuffer size-inflation off-by-32 bug",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "505827492"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "526699cba3779ac55f580f6a871f6b8f8f30dcf3",
       "subject": "Fix OOB in DynamicsProcessing MBC band processing",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "396471524",
      "482172329"
     ],
     "commits": [
      {
       "project": "platform/frameworks/av",
       "sha": "b8080da68249309202654e7fdfb8cdb1c54df509",
       "subject": "[media] Fix heap-buffer-overflow in AudioAttributes unmarshalling",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "525133623"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "f064617cb9ccfa6f15490cb1d466fd68deebedf6",
       "subject": "[26Q2] Mitigate BAL bypass via Companion Device Manager",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "349318333"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ec56593e44df7847b614c1fbdb44feaf2d75623e",
       "subject": "Fix a regression in ECM mode setting after package install",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "472955222"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "27be977cd8056410cdbb68ea0491d59c0ef1445f",
       "subject": "Sanitize labels in GrantCredentialsPermissionActivity",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "492093013"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "9644b7e34bb0156301cf36b6eb5f6cd542260e8f",
       "subject": "Truncate long device admin descriptions",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "64214ba74e81d3d597228d9296b2342fc24780b4",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/Settings",
       "sha": "36a70818e7976d7b3521a38292c6494e40a3e8a7",
       "subject": "Replace DeviceAdminInfo.loadDescription() with loadDescriptionSafe()",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      },
      {
       "project": "platform/packages/apps/TvSettings",
       "sha": "c63a1f6ec913d76c388e5d226ac54851071916ff",
       "subject": "Use loadDescriptionSafe for DeviceAdmin descriptions.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "429417238"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bbf84d307ba6e1faf68df68407fa5b913dd6d97e",
       "subject": "Limit knownActivityEmbeddingCerts in manifest",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "505067993"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c54a3ee92fa2aadc319fa1549cba50fe24d647eb",
       "subject": "Use byte count rather than string length for NMS component name check",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "472968114"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "1f47cb234369c286ce97cdd2af46072d3fc49ff9",
       "subject": "SystemUi UsbDialog: fix label vulnerability",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "495409704"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "45c98d34046991dd9d08fc6a32a47b2e8c2ceb98",
       "subject": "Fix path traversal and missing ownership check in LocaleManagerService",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "490083213"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "9277b3da7bea38b409e73adc9dc030f4402f8e2a",
       "subject": "Add Task-level input sink to block cross-task tap pass-through",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      },
      {
       "project": "platform/frameworks/base",
       "sha": "64cbb2d0021de2191792317776a151b6d4835b54",
       "subject": "Move input sink on top of the Activity when needed",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "498857374"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "865939513d4696b2bd159d21a6b8ae0430a45650",
       "subject": "Check notification permission in getAuthToken(notifyOnAuthFailure).",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "451602444"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "560b2df86acf515c13d3c96818d9c3d916d07adf",
       "subject": "Add size validation and trimming for ShortcutInfo objects.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "501447778"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "8800ef252b9f59834c1e1707bfa47a5de65aae10",
       "subject": "[LocalImageResolver] Fix security check bypass",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "bulletin_history": [
      {
       "bulletin": "2026-09",
       "cve": "CVE-2026-28666",
       "severity": "Critical",
       "versions": [
        "14",
        "15",
        "16",
        "16-qpr2",
        "17"
       ],
       "status": "removed",
       "removed_seen": "2026-10-13",
       "removed_in_version": "1.3"
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "478725173"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "dc8814dec422e63453116985c850c278165e3c38",
       "subject": "Hide media on smartspace view when setting is disabled",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      },
      {
       "project": "platform/frameworks/base",
       "sha": "3df35f926c78a8027fdf8a153e8ab19e3de73c2e",
       "subject": "Update host visibility whenever lockscreen state changes",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "465446148"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "4d1f600cbcbbeb990e19bec4a386fb64f922ddd1",
       "subject": "Autofill SaveUi URL validation and FLAG_IMMUTABLE",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "382064697"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "79dd06a8c17c5e5b8a5a22b4f8819a5c8c263011",
       "subject": "CVE-2025-22442: set profile user restrictions earlier",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "475232460"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "250af57de977cbb4cbc1d425a64555fd1d89da2f",
       "subject": "RESTRICT AUTOMERGE Filter ignorable Unicode codepoints in ExternalStorageProvider",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "463362836"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "3cc466bb712c817570d9002e4514750c16826b4d",
       "subject": "RESTRICT AUTOMERGE [ExternalStorageProvider] Revoke URI permissions by path",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "e6698744f7aa19fd036f4d52a2198f546f8ee8f1",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions for specific document paths",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      },
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "ebcbbeb31b2c8c2ab82460cc4e101fdb712d1dcf",
       "subject": "RESTRICT AUTOMERGE Revoke URI permissions on file path updates",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "495646742"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "5cf100905634f504cb5f9fa30e9b3f1d828c8f5f",
       "subject": "Restrict VirtualDeviceImpl methods to device owner.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "507488454"
     ],
     "commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6f8d24ddbe7192098094a0efbc39a65036bc1996",
       "subject": "Set min_sdk_version in GooglePackageInstaller",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-05-22",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "517377278"
     ],
     "commits": [
      {
       "project": "platform/frameworks/opt/telephony",
       "sha": "e73ac6487867836a4ee7caff4246219af95f327a",
       "subject": "Fix ArrayIndexOutOfBoundsException in SIMRecords due to invalid EF_CFIS/EF_CFF",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "522375782"
     ],
     "commits": [
      {
       "project": "platform/hardware/nxp/nfc",
       "sha": "e245c4b5a85156a2d6bb720d17e8b1e0309b6f47",
       "subject": "Fix Use-After-Free in NXP NFC HAL timer teardown",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "394083030"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Car/Settings",
       "sha": "a64ee74011f7986f77abaa73c536b58bca874b00",
       "subject": "Check getLaunchedFromPackage for CE Storage",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "499298430",
      "506191531"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/ContactsPicker",
       "sha": "824770d6d42d0e9dea42112364c6ea3b2f7b4b74",
       "subject": "Change to startActivityAsCaller when forwarding ACTION_PICK intent",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "471797085"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "a0870b60ba8eddff4597300b0adddfba83c07201",
       "subject": "Fix confused deputy in Bluetooth settings dashboard",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "488559440"
     ],
     "commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "12fb76cd7025e2798a66cf9a580f74ba7aa73e3f",
       "subject": "Sanitize package labels in SettingsApplication",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "465140520"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "aa513b35ec64c2d82db12c98c5ef3b613a5eb244",
       "subject": "GATT: Fix notifications sent to remote device without security checks",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "522373202"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "01cb44f098bee1fe8c8e1106d5927ea1aa58a00e",
       "subject": "Fix cross-thread UAF in AvrcpService updates",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "497102083",
      "497851840",
      "510038786"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "d2c0b10836db1c367a51dd501e2175f8e04fbe54",
       "subject": "[RESTRICT AUTOMERGE] Enforce incoming CTKD security requirements",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "315241296",
      "415689442",
      "458487080"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Bluetooth",
       "sha": "746c2f474f29da5000336cc0b889a55c29d87e47",
       "subject": "Remove btsec_sdp_database_thread_sync flag and old synchronization.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-09",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "517567152"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Connectivity",
       "sha": "33f9aaedc8fa66c96ffd84df3b9f8f164b15ff70",
       "subject": "Rehome enable_d2d_connectivity_service Flag.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-06-02",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "522366268"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "52c0818d37fcfdd62c7aae26e7011496d5c66b68",
       "subject": "Fix integer underflow and OOB access in libnfc-nci",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "503550880"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "f8325e216e4fd8bc18690d40f618daccfc33b75a",
       "subject": "Fix integer underflow in rw_ci_data_cback",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "503545851"
     ],
     "commits": [
      {
       "project": "platform/packages/modules/Nfc",
       "sha": "f5089c4c9b4e790638d8512750743be3929fc16c",
       "subject": "Fix heap buffer overflow in nfa_t4tnfcee_store_rx_buf",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "486763102"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/ContactsProvider",
       "sha": "8b1ebf91ad20207e7079dcb392ed17a5f2c31bda",
       "subject": "Restrict max size for Note.NOTE field",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "482947423"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "adbfd901579e2623a68a8bcc024ca1e3e0dcaa67",
       "subject": "RESTRICT AUTOMERGE Fix ZWSP path bypass in DownloadProvider",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "498162425"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "30a6a1220e98ce432b1f3e78d835a580303936c7",
       "subject": "RESTRICT AUTOMERGE Fix DownloadProvider completed download security bypass",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "490768196"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/DownloadProvider",
       "sha": "6ed0eac90f83dde6b68945d5ff93ff267f1d5b28",
       "subject": "RESTRICT AUTOMERGE Fix path traversal vulnerability in DownloadStorageProvider",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "463363325"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "8dc44d72ec897265dcec939cd5c75681385a9075",
       "subject": "RESTRICT AUTOMERGE Use parameterised queries for photo picker search request queries",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "restrict_automerge"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "494725584"
     ],
     "commits": [
      {
       "project": "platform/packages/providers/TelephonyProvider",
       "sha": "dde6dddce4c95a84b501fcf922d1edbcd006fc46",
       "subject": "TelephonyProvider: Fix SQL injection in projection and sortOrder",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "503542266"
     ],
     "commits": [
      {
       "project": "platform/packages/services/BuiltInPrintService",
       "sha": "c343ebe454ba2de5309058657304e4c3f5c65303",
       "subject": "Limit media-supported values",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "505203996",
      "524878429"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "45a9ba77aa090e082c55961ab527c9d02fb87998",
       "subject": "Fix privilege escalation in ACTION_CALL intent trampoline",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-13",
       "markers": [
        "cve_fix"
       ]
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "508585261"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telecomm",
       "sha": "84aa17aab267954cf2b3356a09a275c8a6b161a8",
       "subject": "Fix MMI check bypass via leading whitespace in tel URI.",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "517377772"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "ba3c68f022f30332abacb10fd6aaea1208be7342",
       "subject": "Fix StackOverflowError in SatelliteEntitlementController",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    },
    {
     "bug_ids": [
      "490916793"
     ],
     "commits": [
      {
       "project": "platform/packages/services/Telephony",
       "sha": "7e3d27fd759ffd9d5bcab79021d31f5ec549745e",
       "subject": "[Telephony] Secure contact URI access in Call Forwarding",
       "tag": "android-security-17.0.0_r1",
       "merged": "2026-07-10",
       "markers": []
      }
     ],
     "tags": [
      "android-security-17.0.0_r1"
     ],
     "tag": "android-security-17.0.0_r1"
    }
   ],
   "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": {
    "CVE-2026-28604": {
     "cve": "CVE-2026-28604",
     "summary": "Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport* across the blocking handshake. The thread now captures a weak-pointer callback id plus the connection object returned by t->connection(), posts the result back to the fdevent looper, and touches the transport only after transport_from_callback_arg confirms it is still alive. FdConnection::DoTlsHandshake (daemon build) also gets 3-second socket send/receive timeouts.",
     "component": "platform/packages/modules/adb, adbd daemon authentication / TLS transport (daemon/auth.cpp, transport.cpp)",
     "weakness": "Use-after-free (race between a detached handshake thread holding a raw atransport* and transport destruction)",
     "root_cause": "In daemon/auth.cpp, adbd_auth_tls_handshake(atransport* t) started a detached std::thread that captured the raw pointer t. The thread blocked in t->connection()->DoTlsHandshake() and then dereferenced t (t->IsTcpDevice(), t->auth_key = ..., adbd_wifi_secure_connect(t), adbd_auth_verified(t), t->Kick()) without checking that the transport still existed. Nothing tied the transport's lifetime to the thread. If the transport was destroyed while the handshake was in progress, the thread could use freed memory. The daemon handshake had no socket timeout, so a stalled peer could keep it blocked. The existing transport_to_callback_arg/transport_from_callback_arg weak_ptr mechanism, documented as used for the user-authorization popup flow, was not used on this path.",
     "fix_analysis": [
      "Commit fdbd8a5f26c5 (applied first; daemon/auth.cpp a80438e8 -> 372e9402), daemon/auth.cpp:transport_to_callback_arg and transport_from_callback_arg: adds CHECK_LOOPER_THREAD() to both helpers, so the global 'transports' map (std::map<uint32_t, weak_ptr<atransport>>) and weak_ptr::get() are used only on the fdevent thread. It rewrites transport_from_callback_arg to use transports->extract(id), so the entry is always removed on lookup and nullptr is returned when the id is missing or the weak_ptr has expired. It also documents the transport-to-callback flow.",
      "Commit 12700491ebb3 (daemon/auth.cpp 372e9402 -> 8e6b27d8), daemon/auth.cpp:adbd_auth_tls_handshake: before starting the thread, it calls transport_to_callback_arg(t) to register a weak_ptr and gets 'auto connection = t->connection()'. The detached thread now captures only [arg, connection], stores the result of connection->DoTlsHandshake(rsa_pkey, &auth_key) in a local 'connected' flag, and never dereferences the transport. It then calls fdevent_run_on_looper(...) with a lambda capturing arg, the moved auth_key and connected. On the looper, that lambda resolves t = transport_from_callback_arg(arg) and returns early when t is nullptr (logging if the handshake had succeeded). If the handshake failed it calls t->Kick(). Otherwise it runs the original success path: t->auth_key = auth_key and adbd_wifi_secure_connect(t) for TCP devices, else adbd_auth_verified(t) and adbd_notify_framework_connected_key(t), now marked by a TODO as most likely dead code.",
      "Same commit, transport.cpp:FdConnection::DoTlsHandshake: the single function with interleaved #ifdefs is split into two full definitions. The #if ADB_HOST version uses the Client role, EnableClientPostHandshakeCheck, adb_tls_set_certificate and accepts any server certificate. The #else version uses the Server role, the adbd_tls_verify_cert callback and adbd_tls_client_ca_list. In the Android 17 version both start with CHECK_NOT_LOOPER_THREAD(). The daemon (#else) version sets SO_RCVTIMEO and SO_SNDTIMEO on fd_ to 3 seconds with adb_setsockopt before tls_->DoHandshake(); on failure it logs with PLOG and continues. Both timeouts are reset to 0 afterwards. As before, a failed handshake resets tls_ and returns false."
     ],
     "completeness": [
      "Covers the daemon-side path in daemon/auth.cpp:adbd_auth_tls_handshake: every transport dereference after the blocking handshake now runs on the looper thread behind a liveness check. The connection object is captured by value. Whether that keeps it alive depends on the return type of t->connection(), which the material does not show.",
      "The handshake thread is still detached. The new 3-second timeouts (daemon build only) limit each blocking socket read or write, not the total handshake time. They are best-effort: if setsockopt fails, the error is logged and the handshake proceeds without a timeout.",
      "Per index search, the host-side counterpart client/auth.cpp:adb_auth_tls_handshake still captures a raw atransport* in a detached thread and calls t->Kick() after the handshake. These commits do not change it, and the host DoTlsHandshake variant gets no timeout.",
      "For Android 14/15/16 the material shows only the UAF-fix commit. It does not show whether the CHECK_LOOPER_THREAD/extract hardening commit (fdbd8a5f26c5) was also applied there."
     ],
     "behavior_changes": [
      "adbd TLS handshake (non-host build): a socket read or write that stalls for more than 3 seconds now times out, failing the handshake. The socket timeouts are reset to 0 after the handshake.",
      "Transport state updates after the handshake (auth_key, adbd_wifi_secure_connect, Kick) now run asynchronously on the fdevent looper instead of on the handshake thread. A successful handshake that completes after the transport is gone is logged ('TLS handshake succeeded for deleted transport, ignoring') and ignored.",
      "transport_from_callback_arg now always removes the map entry, including when the weak_ptr has expired. On 17, calling the callback-arg helpers off the looper thread, or calling DoTlsHandshake on the looper thread, now trips a CHECK.",
      "DoTlsHandshake is now two separate functions selected by ADB_HOST; the host version leaves its unused auth_key parameter unnamed."
     ],
     "preconditions": [
      "Device-side adbd must reach adbd_auth_tls_handshake, i.e. a transport that negotiates TLS. The success path handles t->IsTcpDevice(), the wireless/TCP debugging case. Per index search, it is called from transport.cpp; the call site itself is not shown.",
      "The transport must be destroyed while the detached handshake thread is blocked in, or returning from, DoTlsHandshake."
     ],
     "per_major": {
      "14": "adapted fix: c7995e05cc7b in android-security-14.0.0_r29 applies the same adbd_auth_tls_handshake rework and the DoTlsHandshake split with 3s timeouts. It keeps the branch's LOG(INFO) auth_key log and omits the CHECK_NOT_LOOPER_THREAD() calls (only the comment remains). The hardening commit is not shown for this branch.",
      "15": "adapted fix: 119250e41944 in android-security-15.0.0_r17, same content as the 14 backport (LOG(INFO), no CHECK_NOT_LOOPER_THREAD()). The hardening commit is not shown for this branch.",
      "16": "adapted fix: f487f473c82d in android-security-16.0.0_r8, same auth.cpp change (VLOG(AUTH), as on 17) and same transport.cpp change without CHECK_NOT_LOOPER_THREAD(). The hardening commit is not shown for this branch.",
      "17": "same fix: 12700491ebb3 plus the preceding hardening commit fdbd8a5f26c5 in android-security-17.0.0_r1. This includes CHECK_NOT_LOOPER_THREAD() in both DoTlsHandshake variants and CHECK_LOOPER_THREAD()/extract in the callback-arg helpers."
     },
     "backport_notes": [
      "On 17-style trees, apply fdbd8a5f26c5 (helper hardening) before 12700491ebb3. The UAF fix relies on the existing transport_to_callback_arg/transport_from_callback_arg helpers, atransport::weak() and fdevent_run_on_looper. If the CHECK_LOOPER_THREAD/CHECK_NOT_LOOPER_THREAD macros are absent, drop them as the 14/15/16 backports did. The Android 14/15 versions also keep LOG(INFO) instead of VLOG(AUTH).",
      "The bulletin lists 16-qpr2 and 17-next as affected, but the material contains no commits for those branches."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the hardening commit (CHECK_LOOPER_THREAD, std::map::extract) was backported to Android 14/15/16.",
      "The return type of t->connection(), and so whether the by-value capture in the thread keeps the connection object alive during the handshake.",
      "The exact call site in transport.cpp that invokes adbd_auth_tls_handshake, and the teardown sequence that frees the transport during the handshake (neither is shown in the material).",
      "Whether the 3-second timeout value was tuned for real-world slow links; the material only states that it is meant to prevent denial of service."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28618": {
     "cve": "CVE-2026-28618",
     "summary": "Adds a check in libopenapv's dec_frm_prepare that the supplied output image buffer (imgb) is at least as wide and tall as the frame dimensions in the Frame PBU header. Otherwise it returns OAPV_ERR_INVALID_ARGUMENT. This stops a heap buffer overflow when the Frame PBU dimensions are larger than the AU_INFO dimensions used for buffer allocation.",
     "component": "platform/external/libopenapv, APV software decoder (src/oapv.c), used by the Codec2 C2SoftApvDec component per index search",
     "weakness": "Missing bounds/dimension validation leading to heap buffer overflow (out-of-bounds write)",
     "root_cause": "dec_frm_prepare in src/oapv.c took the output buffer imgb and set up the decode without checking that imgb was large enough. That setup derives ctx->w/ctx->h by aligning ctx->fh.fi.frame_width/frame_height and selects the block-to-image writers. Per the commit message, buffer allocation uses the dimensions in AU_INFO, while the decode follows the Frame PBU header. If a bitstream's Frame PBU header declares larger dimensions than AU_INFO, decoding overflows the allocated imgb on the heap.",
     "fix_analysis": [
      "src/oapv.c:dec_frm_prepare: at function entry, before ctx->imgb is assigned and before imgb_addref is called, the fix compares imgb->w[0] and imgb->h[0] against ctx->fh.fi.frame_width and ctx->fh.fi.frame_height. If either plane-0 dimension is smaller, it returns OAPV_ERR_INVALID_ARGUMENT, so the frame is not decoded into the undersized buffer.",
      "The check runs before any state change: no reference is taken on imgb and no ctx fields are updated. The early return therefore leaves the decoder context untouched for that frame."
     ],
     "completeness": [
      "Only plane 0 (imgb->w[0], imgb->h[0]) is checked; chroma plane sizes are not validated directly. In the shown context the chroma shifts (comp_sft) come from imgb->cs, so chroma coverage depends on the chroma planes having been allocated consistently with plane 0.",
      "The check compares against the unaligned frame_width/frame_height, while ctx->w/ctx->h are aligned to OAPV_MB_W/OAPV_MB_H. The material does not show whether the block writers (fn_block_to_imgb) clip to the frame dimensions or write full aligned macroblocks.",
      "No validation is added for other imgb/frame-header mismatches (chroma format vs ctx->fh.fi.chroma_format_idc, bit depth, stride); only width/height are covered.",
      "The fix is a single hunk in src/oapv.c; this commit changes no caller code."
     ],
     "behavior_changes": [
      "dec_frm_prepare now fails with OAPV_ERR_INVALID_ARGUMENT when the supplied output buffer is smaller than the Frame PBU frame dimensions; such frames are rejected instead of decoded.",
      "Streams whose Frame PBU header dimensions are larger than the allocated buffer (e.g. one sized from AU_INFO) now produce a decode error instead of output."
     ],
     "preconditions": [
      "libopenapv decodes an APV bitstream through the oapvd decode path that reaches dec_frm_prepare. Per index search, on Android this is reachable from the Codec2 software component C2SoftApvDec (frameworks/av/media/codec2/components/apv/C2SoftApvDec.cpp), which creates the decoder via oapvd_create.",
      "The bitstream's Frame PBU header declares a frame_width/frame_height larger than the dimensions used to allocate the output imgb (AU_INFO, per the commit message)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin; platform/external/libopenapv is not present in android-security-14.0.0_r29.",
      "15": "Not affected per the bulletin; platform/external/libopenapv is not present in android-security-15.0.0_r17.",
      "16": "Same fix: commit b40bceb14205 in android-security-16.0.0_r8 adds the identical 4-line check in src/oapv.c dec_frm_prepare (3/3 patched lines present).",
      "17": "Same fix: commit 7c797fcb63c8 in android-security-17.0.0_r1 (3/3 patched lines present)."
     },
     "backport_notes": [
      "Single self-contained hunk at the top of dec_frm_prepare in src/oapv.c. It relies on imgb->w[]/h[] and ctx->fh.fi.frame_width/frame_height. The Android 16 and 17 diffs are identical apart from line offsets. For other snapshots, check that these fields exist before applying."
     ],
     "confidence": "high",
     "unknowns": [
      "The commit lists two Cherrypick-From internal commits, but the AOSP change contains only this one hunk; the material does not show whether other related internal changes exist.",
      "The material does not show the exact write site that overflows (which fn_block_to_imgb variant), or whether it clips to the frame dimensions or writes aligned macroblocks.",
      "How C2SoftApvDec sizes the output buffer is not shown; the only statement is the commit message's note that AU_INFO dimensions are used for allocation."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28639": {
     "cve": "CVE-2026-28639",
     "summary": "Adds `failed = true;` to the else branch in rw_mfc_handle_read_op (rw_mfc.cc) that logs android_errorWriteLog(0x534e4554, \"178725766\"), so that a MIFARE Classic NDEF read is marked as failed on a length inflation attempt instead of only being logged.",
     "component": "platform/packages/modules/Nfc (libnfc-nci, MIFARE Classic tag reader/writer rw_mfc.cc); platform/system/nfc on Android 14 and 15",
     "weakness": "Missing failure propagation after a length check (heap out-of-bounds write per the commit message)",
     "root_cause": "In rw_mfc_handle_read_op, an else branch that handles what the commit title calls a length inflation attempt (it logs android_errorWriteLog(0x534e4554, \"178725766\"), the same ID used for the ndef_length > saved_len check in the 17 context) only logged and did not set the local `failed` flag. The read operation therefore was not marked as failed. According to the commit message, this allowed a heap out-of-bounds write. The exact guarding condition and the copy logic around this branch are not shown in the diff.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: one line, `failed = true;`, is added inside the existing `} else { android_errorWriteLog(0x534e4554, \"178725766\"); }` branch. Per the commit message, this explicitly marks the operation as failed so that the MFC NDEF read terminates. How `failed` is acted on later in the function is not shown in the hunk.",
      "No other files or functions changed. The detection condition itself is unchanged; only its consequence is."
     ],
     "completeness": [
      "Covers the one location in rw_mfc_handle_read_op where the inflation attempt was logged but the operation was not marked as failed.",
      "The length check logic and p_ndef_buffer handling are not changed by this commit.",
      "The code context supplied for android-security-17.0.0_r1 has a differently structured check (saved_len/saved_pos roll back with `failed = true`, lines 1359-1369) that does not match the diff hunk text. The material therefore cannot confirm how this commit maps onto that tag's code.",
      "Write and format paths in rw_mfc are not touched by this commit. The material does not show whether a similar pattern exists there."
     ],
     "behavior_changes": [
      "A MIFARE Classic NDEF read that hits the length inflation branch is now marked as failed (per the commit message: the read is terminated) instead of only being logged.",
      "The android_errorWriteLog(0x534e4554, \"178725766\") event is still emitted."
     ],
     "preconditions": [
      "NFC enabled and the device reading a MIFARE Classic (NFC_PROTOCOL_MIFARE) tag through libnfc-nci's rw_mfc state machine during NDEF read handling.",
      "Tag content that triggers the length inflation branch (an NDEF TLV length larger than the previously established length, per the commit title and the 17 context).",
      "Reached through rw_mfc_handle_read_op within rw_mfc.cc. The index search found no callers outside the Nfc component (per index search)."
     ],
     "per_major": {
      "14": "same fix, applied to platform/system/nfc src/nfc/tags/rw_mfc.cc (commit e884267aee53, android-security-14.0.0_r29, hunk at line ~1057); 2/2 patched lines present",
      "15": "same fix, applied to platform/system/nfc src/nfc/tags/rw_mfc.cc (commit e22f0f7b91dd, android-security-15.0.0_r17, hunk at line ~1054); 2/2 patched lines present",
      "16": "same fix, applied to platform/packages/modules/Nfc libnfc-nci/src/nfc/tags/rw_mfc.cc (commit ca73224fb2c2, android-security-16.0.0_r8, hunk at line ~1289); 2/2 patched lines present",
      "17": "same fix, platform/packages/modules/Nfc libnfc-nci/src/nfc/tags/rw_mfc.cc (commit 9f4efdd67acb, android-security-17.0.0_r1, hunk at line ~1378); 2/2 patched lines present"
     },
     "backport_notes": [
      "The file is in platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15 and in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) on 16/17. The one-line change is identical: find the `android_errorWriteLog(0x534e4554, \"178725766\")` else branch in rw_mfc_handle_read_op and add `failed = true;` after the log call. If the tree has the saved_len roll back variant (as in the 17 context snippet), first check that this else branch exists."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact pre-fix condition guarding the else branch and the surrounding copy loop are not shown in the diff. The supplied context snippet does not line up with the hunk.",
      "What the `failed` path does after this point (abort, callback or event to the upper layer) is not shown in the material.",
      "How p_ndef_buffer is sized is not shown.",
      "Whether the inflated length could also affect other rw_mfc operations (write/format) is not shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28653": {
     "cve": "CVE-2026-28653",
     "summary": "Adds a range check before the loop in the T3T NDEF UPDATE and CHECK command builders (rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd in libnfc-nci rw_t3t.cc). When first_block + block_count would exceed UINT16_MAX, the function now returns NFC_STATUS_FAILED, instead of letting the uint16 block_id loop counter wrap and write past the command buffer.",
     "component": "platform/packages/modules/Nfc (Android 16/17) and platform/system/nfc (Android 14/15), libnfc-nci Type 3 tag (T3T) reader/writer, rw_t3t.cc",
     "weakness": "Integer wrap of a uint16 loop counter leading to an unbounded heap out-of-bounds write (missing bounds check on first_block + block_count)",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, the block-list loop is `for (block_id = first_block; block_id < (first_block + count); block_id++)`. Each pass appends a 2-byte or 3-byte block descriptor to the command buffer via UINT8_TO_STREAM / UINT16_TO_STREAM. The commit says this loop counter could wrap and cause an unbounded heap out-of-bounds write. The likely mechanism, inferred from the commit title and the UINT16_MAX check, is this: when first_block + count goes past the uint16 range, the uint16 block_id never reaches the bound and wraps, so the loop keeps writing descriptors. Before the fix, nothing checked that the sum stayed within the uint16 range. The block count for UPDATE is capped by p_cb->ndef_attrib.nbw and the number of remaining blocks.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: adds `if (ndef_blocks_to_write > UINT16_MAX - first_block_to_write)`. It sits after the UPDATE header and block count are streamed and the timeout is computed, and before the block-list loop. When it trips, the function logs an error, calls android_errorWriteLog(0x534e4554, \"508389055\") and returns NFC_STATUS_FAILED.",
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: adds the matching check `if (cur_blocks_to_read > UINT16_MAX - first_block_to_read)` before the CHECK block-list loop, with the same logging and NFC_STATUS_FAILED return.",
      "The check uses subtraction (count > UINT16_MAX - first), so it rejects exactly the cases where first + count > 65535.",
      "The code at the android-security-17.0.0_r1 tag does not match the provided diff. There the condition is `(uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000`, the failure path also calls GKI_freebuf(p_cmd_buf), and the loop bound is cast to uint32_t. The material does not identify the commit behind this form."
     ],
     "completeness": [
      "The fix covers both T3T NDEF multi-block builders shown: UPDATE in rw_t3t_send_next_ndef_update_cmd and CHECK in rw_t3t_send_next_ndef_check_cmd. In each, the check sits before the loop that streams block descriptors.",
      "In the provided diff, the failure path returns without freeing p_cmd_buf. The form at the 17 tag adds GKI_freebuf(p_cmd_buf), which suggests the original form may leak the buffer when the check trips.",
      "The original check rejects first + count > 65535. The form at the 17 tag rejects only first + count > 0x10000, so a sum of exactly 0x10000 passes. Whether that is safe depends on the type of block_id, which the material does not show.",
      "The fix validates only at the point of use in these two builders. Where the attribute values are parsed is not changed. Other T3T command builders are not shown in the material and are not touched by this diff."
     ],
     "behavior_changes": [
      "If an NDEF UPDATE or CHECK would have first block plus block count above the uint16 range, the builder now returns NFC_STATUS_FAILED instead of building the command.",
      "When the check trips, it writes a LOG(ERROR) line \"<func>: Heap out-of-bounds\" and an android_errorWriteLog event (tag 0x534e4554, id 508389055).",
      "In the original diff, the failure path does not free p_cmd_buf, so a GKI buffer may leak. The form at the 17 tag frees it."
     ],
     "preconditions": [
      "The device must be acting as an NFC reader/writer, performing an NDEF read (CHECK) or write (UPDATE) on a Type 3 tag through libnfc-nci's rw_t3t code.",
      "For the bug to trigger, first block + blocks in the current command must exceed 65535. The material does not show where these values come from.",
      "Unknown: the entry points that drive these functions, and any permission or interaction requirements, are not shown in the material."
     ],
     "per_major": {
      "14": "Same fix: platform/system/nfc bea12a1b3e25 at android-security-14.0.0_r29, file src/nfc/tags/rw_t3t.cc. Same two checks (UINT16_MAX subtraction form, no GKI_freebuf). 7 of the 8 patched lines are present at the tag.",
      "15": "Same fix: platform/system/nfc 089a1a4123db at android-security-15.0.0_r17, file src/nfc/tags/rw_t3t.cc, same checks. 7 of the 8 patched lines are present at the tag.",
      "16": "Same fix: platform/packages/modules/Nfc 4648c160b825 at android-security-16.0.0_r8, file libnfc-nci/src/nfc/tags/rw_t3t.cc, same checks. 7 of the 8 patched lines are present at the tag.",
      "17": "Same fix: platform/packages/modules/Nfc e55c93242c56 at android-security-17.0.0_r1 (cherry-pick of a3c9d2138b59). The code at the tag shows a different form of the check: a uint32_t-cast `> 0x10000` condition, GKI_freebuf(p_cmd_buf) on failure, and a uint32_t-cast loop bound. 7 of the 8 patched lines are present at the tag."
     },
     "backport_notes": [
      "The file is src/nfc/tags/rw_t3t.cc in platform/system/nfc (Android 14/15) and libnfc-nci/src/nfc/tags/rw_t3t.cc in platform/packages/modules/Nfc (Android 16+). The hunk content is identical across branches; only the path and line offsets differ.",
      "When backporting, consider freeing p_cmd_buf before returning NFC_STATUS_FAILED, as the form at the 17 tag does. Keep the bound at UINT16_MAX rather than 0x10000 unless block_id is confirmed to be wider than uint16_t."
     ],
     "confidence": "medium",
     "unknowns": [
      "The declared types of block_id, first_block_to_write/first_block_to_read and the count variables are not shown. The uint16 wrap is inferred from the commit title and the UINT16_MAX check.",
      "The commit that changed the check to `(uint32_t)... > 0x10000` with GKI_freebuf at the 17 tag is not identified. The material also does not show whether that form landed on 14/15/16; it only says 7 of the 8 patched lines are present at each tag.",
      "Where the first-block and block-count values come from (for example, the tag's attribute data), and which fields are validated earlier, is not shown.",
      "Whether p_cmd_buf is already allocated at the point of the original check is not shown in the original diff.",
      "The bulletin lists the type as DoS, while the commit describes an unbounded heap out-of-bounds write. The material does not reconcile the two."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28662": {
     "cve": "CVE-2026-28662",
     "summary": "Adds a bounds check in wpa_supplicant's P2P2 Provision Discovery bootstrap request handler so that a PBMA cookie length taken from the frame is rejected when it is larger than the per-device cookie buffer, and enables the UBSan \"bounds\" sanitizer on the pasn library in wpa_supplicant/Android.bp.",
     "component": "platform/external/wpa_supplicant_8, wpa_supplicant P2P (Wi-Fi Direct) P2P2 Provision Discovery / PBMA handling (src/p2p/p2p_pd.c), plus the sanitizer config in wpa_supplicant/Android.bp",
     "weakness": "Missing bounds check on an attacker-controlled length (heap buffer overflow per the commit message)",
     "root_cause": "In src/p2p/p2p_pd.c:p2p_process_prov_disc_bootstrap_req, when a Provision Discovery request carries a PBMA attribute (pbma_info_len > 2) and status P2P_SC_COMEBACK, the code reads a one-byte cookie_len from the frame (cookie_len = *pos++). It only checked that the PBMA attribute itself was long enough (msg->pbma_info_len < 1 + cookie_len gives \"Truncated PBMA\"). It never checked cookie_len against the size of the cookie field in dev->bootstrap_params (sizeof(dev->bootstrap_params->cookie)), so a cookie_len chosen by the peer could exceed the cookie buffer in the heap-allocated bootstrap_params. The commit message describes the result as a heap overflow in wpa_supplicant, triggered by a malicious Wi-Fi Direct peer sending an oversized cookie length.",
     "fix_analysis": [
      "src/p2p/p2p_pd.c:p2p_process_prov_disc_bootstrap_req: after the existing truncation check, adds `if (cookie_len > sizeof(dev->bootstrap_params->cookie)) { p2p_dbg(p2p, \"Too long PBMA cookie\"); return; }`. This caps the cookie_len taken from the frame at the size of the cookie field in bootstrap_params. The check runs before `cookie = pos`, before the os_memcmp against dev->bootstrap_params->cookie, and before any later use of cookie/cookie_len in the function. Because sizeof is not evaluated at runtime, the check does not dereference dev->bootstrap_params, which may be NULL at that point; the NULL case is handled by the `!dev->bootstrap_params` test that follows. The frame is dropped with no response (return, not goto out).",
      "wpa_supplicant/Android.bp: in the cc_library in the cuttlefish section that compiles src/pasn/pasn_initiator.c, pasn_responder.c and pasn_common.c (the commit message calls it libpasn), adds \"bounds\" to sanitize.misc_undefined next to unsigned-integer-overflow and signed-integer-overflow (cfi was already true). This is defense in depth and does not change the P2P logic.",
      "The commit message lists two internal Cherrypick-From references and lands as one AOSP commit with Change-Id Ic3c75644dbb7b3ea278335e977ad428ae14d9709."
     ],
     "completeness": [
      "The fix covers only the PBMA comeback branch of p2p_process_prov_disc_bootstrap_req (request side). The else branch (\"PBMA request\", bootstrap = WPA_GET_LE16(pos)) is unchanged. The context shown there only reveals an os_free(dev->bootstrap_params) and no further cookie handling, so the material does not allow judging that branch.",
      "The diff does not touch the handler for bootstrap Provision Discovery responses, even though the commit message speaks of a peer 'responding' to a request. The material does not show whether a response path parses a PBMA cookie length, so it cannot tell whether a similar check is needed there.",
      "The commit title says 'harden wpa_supplicant/hostapd', but the diff only modifies wpa_supplicant/Android.bp (the pasn library in the cuttlefish section). No hostapd build file is changed, and only one cc_library gets the bounds sanitizer.",
      "The check rejects cookie_len only when it is strictly greater than sizeof(cookie). Lengths up to and including the buffer size are still processed, and they are compared with os_memcmp only when they equal the stored cookie_len."
     ],
     "behavior_changes": [
      "P2P2 Provision Discovery requests with status P2P_SC_COMEBACK whose PBMA cookie length exceeds the local cookie buffer are now silently dropped with the debug log \"Too long PBMA cookie\". No Provision Discovery response is sent for such frames (early return before the `out` label).",
      "The pasn cc_library in the cuttlefish section of wpa_supplicant/Android.bp is now built with UBSan bounds checking in addition to the integer-overflow checks and CFI. Out-of-bounds array indexing caught by the sanitizer aborts the process instead of letting it continue (general platform knowledge)."
     ],
     "preconditions": [
      "Device runs wpa_supplicant with P2P (Wi-Fi Direct) and the P2P2 code path. Per the per-major facts the patched region exists only in Android 16 and 17 and is absent in 14 and 15.",
      "A Wi-Fi Direct peer sends a P2P Provision Discovery request containing a PBMA attribute (pbma_info_len > 2) and a status attribute equal to P2P_SC_COMEBACK.",
      "The handler must find a device entry for the sender (on the path shown it returns when p2p_get_device(p2p, sa) finds none), and no response TX may be pending (p2p->send_action_in_progress)."
     ],
     "per_major": {
      "14": "not affected per the bulletin; 0/5 lines of the patched region exist at android-security-14.0.0_r29 (code absent), so no fix applied.",
      "15": "not affected per the bulletin; 0/5 lines of the patched region exist at android-security-15.0.0_r17 (code absent), so no fix applied.",
      "16": "same fix: commit 3f792f7b7afa in android-security-16.0.0_r8, matched by change_id. The p2p_pd.c hunk and the Android.bp hunk are identical; only the Android.bp hunk offset differs (683 vs 696). 5/5 patched lines present.",
      "17": "same fix: commit b8d36c897dde in android-security-17.0.0_r1, matched by change_id. 5/5 patched lines present."
     },
     "backport_notes": [
      "The two hunks are independent. The p2p_pd.c check is the security fix and applies wherever p2p_process_prov_disc_bootstrap_req has the 'Truncated PBMA' check and a fixed-size dev->bootstrap_params->cookie field. The Android.bp 'bounds' line is hardening for the pasn cc_library in the cuttlefish section. Branches without the P2P2 bootstrap code (Android 14/15 per the per-major facts) have nothing to patch."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact overflowing read or write is not visible in the material. In the context shown, the os_memcmp runs only when cookie_len equals the stored cookie_len, so the site where an oversized cookie_len corrupts the heap is not shown.",
      "The declared size of the cookie array in struct bootstrap_params and the type of cookie_len are not shown.",
      "Whether the response handler for bootstrap Provision Discovery or the non-comeback 'PBMA request' branch has similar cookie handling is not shown.",
      "Whether reaching the vulnerable branch requires an earlier bootstrap exchange (dev->bootstrap_params already allocated), and whether the framework's provisionDiscoveryWithParams path (packages/modules/Wifi, per index search) is related, is not shown.",
      "Why the commit title mentions hostapd when only wpa_supplicant/Android.bp changes, and what the two internal Cherrypick-From commits contain, is not shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-33636": {
     "cve": "CVE-2026-33636",
     "summary": "Restricts the NEON palette-expansion loops in libpng's arm/palette_neon_intrinsics.c to full chunks only, removes the post-loop rollback of the pixel counter, and undoes the output-pointer pre-adjustment before returning to the scalar fallback, eliminating an out-of-bounds read/write on the final partial chunk.",
     "component": "platform/external/libpng, ARM NEON palette expansion (arm/palette_neon_intrinsics.c)",
     "weakness": "Out-of-bounds read and write from a missing loop bound check (vector loop processing a partial final chunk as a full chunk)",
     "root_cause": "In arm/palette_neon_intrinsics.c, png_do_expand_palette_rgba8_neon (4-lane uint32x4_t chunks stored via vst1q_u32) and png_do_expand_palette_rgb8_neon (8 pixels x 3 bytes per chunk, stored via vst3_u8) iterated with `for (i = 0; i < row_width; i += pixels_per_chunk)`. When row_width is not a multiple of pixels_per_chunk, the last iteration still loaded a full chunk of palette indices and stored a full chunk of output at dp, so both reads and writes went outside the row buffer. The post-loop `if (i != row_width) i -= pixels_per_chunk;` only corrected the returned pixel count after the out-of-bounds access. Additionally, the pre-adjustment `*ddp = *ddp - ((pixels_per_chunk * sizeof(...)) - 1)` applied before the loop was never undone, so the dp handed back to the scalar fallback in pngrtran.c was offset from the correct position.",
     "fix_analysis": [
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: loop condition changed to `i + pixels_per_chunk <= row_width`, so only complete chunks are vectorised; the trailing `if (i != row_width) i -= pixels_per_chunk;` block is deleted because i no longer overshoots.",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgba8_neon: before the final `*ssp = *ssp - i; *ddp = *ddp - (i << 2);` the code adds `*ddp = *ddp + (pixels_per_chunk * 4 - 1);` to reverse the pre-loop pre-adjustment of *ddp, so the scalar fallback in pngrtran.c receives dp at the correct position (per the commit comment).",
      "arm/palette_neon_intrinsics.c:png_do_expand_palette_rgb8_neon: same loop condition change to `i + pixels_per_chunk <= row_width`, same removal of the post-loop rollback, and `*ddp = *ddp + (pixels_per_chunk * 3 - 1);` added before `*ssp = *ssp - i; *ddp = *ddp - ((i << 1) + i);`.",
      "The remaining (row_width mod pixels_per_chunk) pixels are left to the scalar code in pngrtran.c, which continues from the returned i and the corrected sp/dp (pngrtran.c itself is not changed by this commit)."
     ],
     "completeness": [
      "Both NEON entry points in arm/palette_neon_intrinsics.c (RGBA8 and RGB8) are fixed with the same pattern; the fix covers the vector loop bound and the pointer handoff.",
      "png_riffle_palette_neon in the same file is untouched by this commit; the material does not show whether it needed changes.",
      "The scalar fallback in pngrtran.c is not part of the diff; its correctness with the newly corrected dp is asserted by the commit comment only and is not visible in the material.",
      "No non-ARM SIMD palette paths appear in the material, so coverage of other architectures cannot be judged from the code provided."
     ],
     "behavior_changes": [
      "For rows whose width is not a multiple of the chunk size, the trailing pixels are now expanded by the scalar code in pngrtran.c instead of by the NEON loop.",
      "The dp value returned via *ddp to the scalar fallback is now shifted by (pixels_per_chunk*4 - 1) or (pixels_per_chunk*3 - 1) bytes relative to the old code, correcting the handoff position.",
      "No new error paths, return codes or API changes."
     ],
     "preconditions": [
      "libpng built with PNG_ARM_NEON_IMPLEMENTATION == 1 (ARM/ARM64 NEON build), per the #if guard in arm/palette_neon_intrinsics.c.",
      "Decoding a palette (indexed-colour) PNG with palette-to-RGB expansion enabled, so png_do_expand_palette_rgba8_neon or png_do_expand_palette_rgb8_neon is invoked from pngrtran.c.",
      "Image row width not a multiple of the chunk size used by the respective NEON path.",
      "Example caller of palette expansion in the platform tree: frameworks/base/libs/androidfw/Png.cpp readPng calls png_set_palette_to_rgb for PNG_COLOR_TYPE_PALETTE input (per index search); other libpng consumers are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 843457e59be7 in android-security-14.0.0_r29, identical hunks to Android 17 with a one-line offset (file has a leading blank line, blob 92c7d6f9f..bdd15849d); resolver reports 7/8 patched-region lines present.",
      "15": "same fix: commit fa7e33a0a0cc in android-security-15.0.0_r17, byte-identical diff to Android 14 (same blob hashes); resolver reports 6/8 patched-region lines present.",
      "16": "same fix: commit 0b9147198ed6 in android-security-16.0.0_r8, byte-identical diff to Android 14/15; resolver reports 6/8 patched-region lines present.",
      "17": "same fix: commit 7d98050ef8d9 in android-security-17.0.0_r1 (Cherrypick-From commit d13d190be0e9, Change-Id I56e19b3e1a226de21e91e400124e36021357689a); resolver reports 6/8 patched-region lines present."
     },
     "backport_notes": [
      "Single-file, single-commit change confined to arm/palette_neon_intrinsics.c; the 14/15/16 diff differs from 17 only by a leading blank line in the file header, so the same patch applies to all branches with trivial offset. No 'Fix in fork' section was provided, so no per-file fork verdict can be reported.",
      "Verify after applying that pngrtran.c's scalar fallback still consumes the returned i, *ssp and *ddp as this commit assumes, since that file is not in the diff."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact caller contract in pngrtran.c (png_do_expand_palette) is not shown, so the precise byte offset of the old mis-positioned dp handoff and its effect on output cannot be confirmed from the material.",
      "The exact pixels_per_chunk values are not shown in the material beyond the vector types and the RGB8 '8 pixels x 3 bytes' comment.",
      "Which Android processes decode untrusted PNGs through this libpng NEON path (the bulletin only gives Critical/EoP); the index shows androidfw Png.cpp as one consumer but not the full set.",
      "Why the resolver reports 6/8 or 7/8 patched-region lines present on branches that contain the fix commit; the material does not explain the mismatch.",
      "Whether png_riffle_palette_neon or any non-ARM optimised palette path has related issues; not covered by the material."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45515": {
     "cve": "CVE-2026-45515",
     "summary": "In the Bluetooth A2DP Opus decoder, the fix changes the frame_size argument passed to opus_decode(). It used to be the raw A2DP_OPUS_DECODE_BUFFER_LENGTH value and is now the number of samples per channel the output buffer can hold (A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t))). The fix also adds a channel-count check, decodes each packet once instead of once per frame, and sizes the callback payload by the fixed stereo output channel count.",
     "component": "platform/packages/modules/Bluetooth, system/stack/a2dp (A2DP Opus vendor codec decoder, a2dp_vendor_opus_decoder.cc)",
     "weakness": "Heap buffer overflow caused by a unit mismatch in a bounds parameter (buffer size in bytes passed where samples-per-channel was expected)",
     "root_cause": "a2dp_vendor_opus_decoder_decode_packet() in system/stack/a2dp/a2dp_vendor_opus_decoder.cc called opus_decode() with A2DP_OPUS_DECODE_BUFFER_LENGTH as the frame_size parameter. According to the commit message, this value is the buffer size in bytes, but opus_decode() expects a count of samples per channel. As a result, the capacity reported to libopus was larger than the real capacity of a2dp_opus_decoder_cb.decode_buf. An Opus packet with a large frame duration could then make the decoder write more PCM than the heap buffer holds. The regression test uses a real 60 ms stereo packet (2880 samples per channel). The same function had secondary defects. It looped numFrames times (the value from opus_packet_get_nb_frames) and decoded the whole packet on every iteration, invoking decode_callback once per iteration. It also computed the callback length from the packet's channel count (numChannels) instead of the configured output channel count.",
     "fix_analysis": [
      "system/stack/a2dp/a2dp_vendor_opus_decoder.cc:a2dp_vendor_opus_decoder_decode_packet: both opus_decode() calls now pass A2DP_OPUS_DECODE_BUFFER_LENGTH / (A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(int16_t)) as frame_size. This covers the normal decode and the concealment decode with NULL/0 input. libopus is therefore given a per-channel sample capacity instead of a byte count. When the decoded frame does not fit, the error path (ret_val < OPUS_OK) applies.",
      "Same function: adds an early check, 'if (numChannels != 1 && numChannels != 2) return false', on the value from opus_packet_get_nb_channels(pBuffer). On failure it logs 'Invalid numChannels'.",
      "Same function: removes the 'for (uint32_t frame = 0; frame < numFrames; ++frame)' loop and the re-read of numChannels inside it. The packet is now passed to opus_decode() exactly once, with one concealment retry and a single decode_callback invocation.",
      "Same function: frame_len passed to decode_callback changes from ret_val * numChannels * sizeof(decode_buf[0]) to ret_val * A2DP_OPUS_CODEC_OUTPUT_CHS * sizeof(decode_buf[0]). The added comment says Opus is always configured as stereo, so the output size follows the codec channel selection and not the channel count in the frame.",
      "Same function: removes the 'uint32_t num_frames = pBuffer[0] & 0xf' local and the two log::error lines. They are replaced by a single log::info line that reports numChannels, numFrames, frameSize, frameLen, bufferSize and offset.",
      "system/stack/test/a2dp/a2dp_opus_unittest.cc: adds TEST_F(A2dpOpusTest, decode_packet_60ms_overflow_repro). The test uses libopus to encode a 60 ms (2880 samples per channel) stereo Opus packet, prepends the 1-byte A2DP header, and feeds the result to decoder_iface_->decode_packet(). It adds the <stdio.h> and <opus.h> includes. The test does not assert on the decode result, so it depends on memory-error detection to catch the overflow."
     ],
     "completeness": [
      "Both opus_decode() call sites in this function (normal and concealment) receive the corrected frame_size, so the capacity bound is applied consistently here.",
      "The new bound is only correct if A2DP_OPUS_DECODE_BUFFER_LENGTH is the byte size used to allocate decode_buf and the OpusDecoder is created with A2DP_OPUS_CODEC_OUTPUT_CHS (2) channels. The material shows neither the allocation in a2dp_vendor_opus_decoder_init nor the constant definitions.",
      "The numChannels check is a defensive addition. The output size is bounded by the frame_size change, not by this check.",
      "The decode path still only has the empty-packet check (p_buf->len == 0) before computing bufferSize = p_buf->len - 1. This code is unchanged by the fix.",
      "The fix covers only the Opus decoder. This commit does not touch other A2DP vendor decoders, and the material gives no evidence about them either way."
     ],
     "behavior_changes": [
      "When a decoded frame exceeds the per-channel capacity passed as frame_size, opus_decode() is expected to return an error. The decoder then tries concealment, and if that also fails it drops the packet (returns false).",
      "Each A2DP Opus packet is decoded once and decode_callback is invoked once per packet. Before the fix, a multi-frame packet was decoded numFrames times, with one callback per decode.",
      "The byte length passed to decode_callback is now always ret_val * 2 * sizeof(int16_t). For packets whose TOC indicates mono, the reported length is double what it was before.",
      "Packets with a channel count other than 1 or 2 are rejected with a log and return false.",
      "Per-packet logging changes from two log::error lines to one log::info line, which no longer prints the raw 'pBuffer[0] & 0xf' value."
     ],
     "preconditions": [
      "Incoming A2DP Opus media packets must be routed to a2dp_vendor_opus_decoder_decode_packet().",
      "The input is the BT_HDR media payload after the 1-byte A2DP Opus header (pBuffer = p_buf->data + p_buf->offset + 1, bufferSize = p_buf->len - 1). The test builds this input with AllocateL2capPacket().",
      "The decoder must have been initialised (a2dp_vendor_opus_decoder_init with a decode callback) so that opus_handle and decode_buf exist.",
      "The packet must carry a frame duration large enough that the decoded PCM exceeds decode_buf. The regression test uses a 60 ms stereo packet (2880 samples per channel at 48 kHz)."
     ],
     "per_major": {
      "14": "adapted fix: commit 4f7ed05b1660 in android-security-14.0.0_r29. The logic is identical, with these differences: it uses the printf-style LOG_ERROR/LOG_INFO macros instead of log::error/log::info, it reflows the frameSize/frameLen lines, and in the unit test it replaces the existing '#include <stdio.h>' with '#include <opus.h>'. Resolved via bug_id; 6/8 lines of the patched region are present at the tag.",
      "15": "adapted fix: commit b837ddeb7125 in android-security-15.0.0_r17. Same logic as 17, using log::error/log::info. It reflows the frameSize/frameLen lines, which were wrapped differently before the fix, and the test adds <stdio.h> and <opus.h>. Resolved via bug_id; 6/8 lines of the patched region are present at the tag.",
      "16": "same fix: commit 5eefb7cb0b0c in android-security-16.0.0_r8. The hunk content matches the Android 17 commit and the test-file diff is identical. The decoder source blob hashes differ from 17, so the surrounding file is not byte-identical. Resolved via bug_id; 4/8 lines of the patched region are present at the tag.",
      "17": "same fix: commit 3b78dbef3ec6 in android-security-17.0.0_r1 (cherry-pick of e87caba4b8df, Change-Id I3a1b150c0a761de54dedc5a27e0e9de96b19b16c, RESTRICT AUTOMERGE). Resolved via bug_id; 4/8 lines of the patched region are present at the tag."
     },
     "backport_notes": [
      "This is a single-commit fix. For a tree without the fmt-style bluetooth/log.h logger, the Android 14 variant (LOG_ERROR/LOG_INFO with %d) can serve as the template. In the target tree, verify that A2DP_OPUS_DECODE_BUFFER_LENGTH is the byte size used to allocate decode_buf, and that A2DP_OPUS_CODEC_OUTPUT_CHS exists and matches the channel count used to create the decoder.",
      "The new test calls the libopus encoder (opus_encoder_create/opus_encode). Check that the test target can link these functions in the target tree."
     ],
     "confidence": "high",
     "unknowns": [
      "The definitions of A2DP_OPUS_DECODE_BUFFER_LENGTH and A2DP_OPUS_CODEC_OUTPUT_CHS and the allocation of decode_buf in a2dp_vendor_opus_decoder_init are not shown. The byte-vs-sample mismatch comes from the commit message.",
      "How the OpusDecoder handle is created (the channel count passed to opus_decoder_create) is not shown. Only the fix's comment says it is always stereo.",
      "The material does not show which component dispatches media packets to a2dp_vendor_opus_decoder_decode_packet, which A2DP role (for example sink) uses this decoder, or on which configurations it is enabled.",
      "The second bug id (503554165) and how it relates to the first (494914402) are not explained in the material."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45531": {
     "cve": "CVE-2026-45531",
     "summary": "Hardens fsck.exfat boot-region validation in read_boot_region against integer overflow/underflow from on-disk vol_length, clu_offset and clu_count fields, and makes the shared DIV_ROUND_UP macro compute in 64 bits.",
     "component": "platform/external/exfatprogs, fsck.exfat (fsck/fsck.c) and shared header include/libexfat.h",
     "weakness": "Integer overflow/underflow in size validation (insufficient bounds checks on on-disk boot sector fields)",
     "root_cause": "In fsck/fsck.c read_boot_region, the volume-size check was written as `le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) > bd->size`. A large vol_length from the untrusted boot sector can make the multiplication wrap, so the check passes for a volume length larger than the device. The later computation `clu_max_count = (vol_length - clu_offset) >> sect_per_clus_bits` had no guard that clu_offset < vol_length, so the subtraction could underflow to a huge value and defeat the clu_count comparison. clu_count was not capped at the exFAT maximum EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5). Separately, DIV_ROUND_UP in include/libexfat.h computed `(__i + __d - 1) / __d` in the type of its arguments, so the rounding addition could wrap for narrow argument types. The commit title describes these as \"fsck overflows when handling bad clu_count or vol_length fields\".",
     "fix_analysis": [
      "Commit 5ec5ede9ba50 (fsck/fsck.c:read_boot_region): replaces the overflow-prone multiplication with `le64_to_cpu(bs->bsx.vol_length) > (bd->size >> bs->bsx.sect_size_bits)`, a shift form that cannot wrap. The sector size is already range-checked earlier (512..4KB).",
      "Same commit, same function: adds `if (le32_to_cpu(bs->bsx.clu_offset) >= le64_to_cpu(bs->bsx.vol_length)) goto err;` before the `vol_length - clu_offset` subtraction so clu_max_count cannot underflow.",
      "Same commit, same function: adds `if (le32_to_cpu(bs->bsx.clu_count) > EXFAT_MAX_NUM_CLUSTER) goto err;` so clu_count is bounded by 0xFFFFFFF5 before the clu_max_count comparison.",
      "Same commit (include/libexfat.h): DIV_ROUND_UP now casts its first operand, `(((uint64_t)(__i) + (__d) - 1) / (__d))`, so the rounding addition is done in 64 bits for every user of the shared macro.",
      "Same commit adds a regression image tests/clu_count_overflow/exfat.img.tar.xz.",
      "Commit a6faf5dfd40a (Android.bp, fsck/Android.bp, lib/Android.bp): adds `host_supported: true` to libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat so the tests can be built on the host. It has no runtime code change."
     ],
     "completeness": [
      "All three new or changed checks are only in fsck/fsck.c read_boot_region. The DIV_ROUND_UP change is the only part that affects the other tools sharing include/libexfat.h. Whether those tools validate the boot region themselves is not shown.",
      "On Android 17, the check after the new guards compares clu_count against clu_max_count (derived from vol_length - clu_offset), so the new clu_offset guard directly protects it. On Android 14 and 16, the next check is still the older `le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > bd->size`, which the backport leaves unchanged. The EXFAT_MAX_NUM_CLUSTER cap bounds clu_count but does not by itself make that product non-wrapping (the type of EXFAT_CLUSTER_SIZE is not shown).",
      "The fix validates the fields when they are read. It does not change how fsck later uses clu_count or vol_length.",
      "The second commit is build-only (host test support) and adds no protection on the device."
     ],
     "behavior_changes": [
      "fsck.exfat now fails read_boot_region (goto err) for boot sectors where vol_length exceeds bd->size >> sect_size_bits, where clu_offset >= vol_length, or where clu_count > 0xFFFFFFF5. Error messages print when verbose is set on 16/17 and always in the Android 14 backport.",
      "DIV_ROUND_UP now evaluates in uint64_t for all callers in exfatprogs.",
      "exfatprogs modules (headers, defaults, libexfat, fsck.exfat) become host_supported, which enables host builds of the tests."
     ],
     "preconditions": [
      "fsck.exfat must process an exFAT volume whose boot sector has crafted vol_length, clu_offset or clu_count values. The checks are reached in read_boot_region after the sector-size, cluster-size, fs_version and num_fats checks pass.",
      "Which platform component runs fsck.exfat on untrusted media is not shown in the material."
     ],
     "per_major": {
      "14": "adapted fix: commit 54cf500d4abd in android-security-14.0.0_r29 applies the same three checks and the DIV_ROUND_UP change. It has no `if (verbose)` wrappers and keeps the older `clu_count * EXFAT_CLUSTER_SIZE(bs) > bd->size` check that follows. 7/8 patched lines present. Whether the host-test Android.bp commit was also applied is not shown.",
      "15": "resolved by bug_id in android-security-15.0.0_r17, with 8/8 patched lines present. The material shows only the Android.bp host_supported commit 2957a37c1d7f for this branch. The fsck.c commit itself is not shown.",
      "16": "adapted fix: commit c0e2d5cc6945 in android-security-16.0.0_r8 applies the same checks with `if (verbose)` wrappers, but the check that follows is still the older `clu_count * EXFAT_CLUSTER_SIZE(bs) > bd->size` form. 6/8 patched lines present. The host-test Android.bp commit is not shown for this branch.",
      "17": "same fix: commits 5ec5ede9ba50 (fsck.c, libexfat.h, test image) and a6faf5dfd40a (Android.bp host_supported) in android-security-17.0.0_r1. 8/8 patched lines present."
     },
     "backport_notes": [
      "Only 5ec5ede9ba50 carries the runtime fix. a6faf5dfd40a (host_supported in the Android.bp files) is independent and only needed to build the tests. In trees where the error calls have no `if (verbose)` wrappers (as in Android 14), drop the wrappers. Insert the two new guards right after the vol_length check, whether the next check is the clu_max_count form (17) or the `clu_count * EXFAT_CLUSTER_SIZE(bs)` form (14/16)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which downstream computation (allocation size, bitmap size, loop bound) the overflowed values affected. The material shows only the validation changes and the commit title.",
      "The definition and type of EXFAT_CLUSTER_SIZE, and the DIV_ROUND_UP call sites, so the exact wrap the macro change prevents is not visible.",
      "The fsck.c commit for Android 15 and the Android.bp commits for Android 14 and 16 are not included in the material.",
      "Whether other exfatprogs tools that parse the boot sector have equivalent checks.",
      "Which platform component invokes fsck.exfat on untrusted media."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49879": {
     "cve": "CVE-2026-49879",
     "summary": "Widens the Type 3 Tag block counter to uint32_t and recomputes the block-range bounds check and loop condition in 32-bit arithmetic in rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, and frees the GKI command buffer when the range is rejected.",
     "component": "platform/packages/modules/Nfc (libnfc-nci), Type 3 Tag reader/writer: libnfc-nci/src/nfc/tags/rw_t3t.cc (platform/system/nfc src/nfc/tags/rw_t3t.cc on Android 14/15)",
     "weakness": "Integer overflow hardening of a 16-bit block counter (per the commit message), plus a GKI buffer leak on the bounds-check error path",
     "root_cause": "In rw_t3t_send_next_ndef_update_cmd and rw_t3t_send_next_ndef_check_cmd, block_id was a uint16_t iterated from first_block_to_write/first_block_to_read up to first + count. A uint16_t counter cannot represent a range ending at block 65535 (first + count == 0x10000) without wrapping. The pre-image guard, tagged with bug 508389055 (reject when count > UINT16_MAX - first), avoided this by rejecting any range ending at 0x10000. That made it stricter by one than the valid block ID space up to 65535. When the guard fired, it logged and exited without freeing p_cmd_buf, so a GKI buffer leaked. The commit describes the change as preventing integer overflow. The material does not show how the issue behind the Critical rating arises beyond this.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_update_cmd: the block_id declaration changes from uint16_t to uint32_t (declared alongside timeout), so the counter can hold 65535 and still increment past it to end the loop.",
      "rw_t3t_send_next_ndef_update_cmd: the bounds check changes from 'ndef_blocks_to_write > UINT16_MAX - first_block_to_write' to '(uint32_t)first_block_to_write + ndef_blocks_to_write > 0x10000'. The sum is now formed in uint32_t, and ranges ending exactly at block 65535 are now allowed. GKI_freebuf(p_cmd_buf) is added before the LOG/android_errorWriteLog error path, so the command buffer is released when the range is rejected.",
      "rw_t3t_send_next_ndef_update_cmd: the loop condition now uses '(uint32_t)first_block_to_write + ndef_blocks_to_write', so the counter and the bound are compared at the same 32-bit width.",
      "libnfc-nci/src/nfc/tags/rw_t3t.cc:rw_t3t_send_next_ndef_check_cmd: the same three changes for the read/check path, using first_block_to_read and cur_blocks_to_read (uint32_t block_id, 32-bit sum check against 0x10000 with GKI_freebuf on failure, 32-bit loop bound)."
     ],
     "completeness": [
      "Both the NDEF update (write) path and the NDEF check (read) path in rw_t3t.cc are patched the same way.",
      "In the check path, the guard comes after UINT8_TO_STREAM(p, cur_blocks_to_read) has already written the block count into the command buffer. The fix does not change this ordering, and the buffer is now freed on rejection.",
      "The fix only covers the two NDEF-driven T3T command builders. This commit does not touch other T3T command builders in the same file, and the material does not show whether they handle ranges in a comparable way.",
      "The change does not alter how first_block_to_* and *_blocks_to_* are derived upstream. It only changes the arithmetic where they are used."
     ],
     "behavior_changes": [
      "Block ranges ending exactly at block 65535 (first + count == 0x10000) are now accepted and processed. The previous guard rejected them.",
      "When the range check fails, the GKI command buffer is now freed instead of leaked. The error log and the android_errorWriteLog(0x534e4554, \"508389055\") call are kept.",
      "The block ID encoding branch (block IDs below 256 in 2-byte form) is not modified."
     ],
     "preconditions": [
      "The device is in NFC reader/writer mode and is performing an NDEF update (write) or NDEF check (read) against a Type 3 Tag, which reaches rw_t3t_send_next_ndef_update_cmd or rw_t3t_send_next_ndef_check_cmd. The index search shows no callers, so the exact call path is not in the material.",
      "The block range held in the tRW_T3T_CB control block must reach the top of the 16-bit block ID space. The material does not show how those values are filled in."
     ],
     "per_major": {
      "14": "same fix: platform/system/nfc c1fa4599a13e in android-security-14.0.0_r29, file src/nfc/tags/rw_t3t.cc, identical hunks at slightly different line offsets.",
      "15": "same fix: platform/system/nfc 78dd34a6a026 in android-security-15.0.0_r17, file src/nfc/tags/rw_t3t.cc, identical hunks.",
      "16": "same fix: platform/packages/modules/Nfc 92d884176dcd in android-security-16.0.0_r8, file libnfc-nci/src/nfc/tags/rw_t3t.cc, identical hunks.",
      "17": "same fix: platform/packages/modules/Nfc 442e948e4be2 in android-security-17.0.0_r1 (cherry-pick of 241c49442390), file libnfc-nci/src/nfc/tags/rw_t3t.cc."
     },
     "backport_notes": [
      "Every hunk's pre-image assumes the earlier guard tagged android_errorWriteLog(0x534e4554, \"508389055\") is already present in both functions. On a branch without that guard, the whole check block has to be added before this diff will apply.",
      "On Android 14/15 the fix lands in platform/system/nfc (src/nfc/tags/rw_t3t.cc). On 16 and 17 it lands in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_t3t.cc). Only the path prefix differs."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the earlier 508389055 guard in the pre-image belongs to the same CVE remediation or to a separate earlier fix. The material only links this commit (bug 503544522) to the CVE.",
      "The exact return value and flow after the LOG on the error path (the lines after the log are not shown).",
      "How first_block_to_write/first_block_to_read and the block counts are derived from tag data, and therefore how far a tag controls the range.",
      "Why the bulletin rates this Critical EoP. Given the existing guard, the diff itself mainly shows a buffer leak fix and wider counter arithmetic, and the material does not describe the impact."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49882": {
     "cve": "CVE-2026-49882",
     "summary": "In rw_mfc_handle_read_op (libnfc-nci rw_mfc.cc), the fix snapshots ndef_length and ndef_start_pos before rw_nfc_decodeTlv on the first block read, treats a TLV decode that fails or inflates ndef_length as a failure, and rolls both fields back so an inflated length does not persist in the Mifare Classic control block and drive later copies into p_ndef_buffer.",
     "component": "platform/packages/modules/Nfc (Android 16/17) and platform/system/nfc (Android 14/15), libnfc-nci NFC tag reader/writer, Mifare Classic NDEF read (rw_mfc.cc)",
     "weakness": "Ineffective bounds validation leading to heap out-of-bounds write (inflated NDEF length not rolled back after failed check)",
     "root_cause": "rw_mfc_handle_read_op copies tag block bytes into p_mfc->p_ndef_buffer up to p_mfc->ndef_length. On the first read (work_offset == 0), rw_nfc_decodeTlv(data) re-derives ndef_length and ndef_start_pos from TLV data supplied by the tag. The old code snapshotted saved_length = p_mfc->ndef_length on every call. It refused the copy (failed = true, android_errorWriteLog) when saved_length < ndef_length, but it left the inflated p_mfc->ndef_length and ndef_start_pos in the control block. A later call would therefore take the inflated value as its baseline, so the guard could stop catching the inflation and the copy loop could be bounded by a length larger than intended. The material does not show the exact path to the out-of-bounds write. The commit message describes the result as a heap buffer overflow in com.android.nfc during Mifare Classic NDEF reads.",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: removes the function-scope saved_length that was re-snapshotted on every read. Instead, inside the work_offset == 0 branch, it saves saved_len = p_mfc->ndef_length and saved_pos = p_mfc->ndef_start_pos immediately before rw_nfc_decodeTlv(data).",
      "Treats the first read as failed if rw_nfc_decodeTlv returns false OR the decoded p_mfc->ndef_length exceeds saved_len. In the inflation case it emits android_errorWriteLog(0x534e4554, \"178725766\").",
      "On failure it restores p_mfc->ndef_length = saved_len and p_mfc->ndef_start_pos = saved_pos (roll back) and sets failed = true, so the inflated values do not carry into later block reads.",
      "Simplifies the copy guard to `if (!failed)` and removes the old else branch. The while loop that copies p[offset] into p_mfc->p_ndef_buffer[p_mfc->work_offset] is now only reached when ndef_length does not exceed its value from before the decode."
     ],
     "completeness": [
      "Covers the only site in the diff where ndef_length is re-derived from tag data during the read path (first read, work_offset == 0), and restores both ndef_length and ndef_start_pos on failure.",
      "The copy loop itself is unchanged: it still bounds the write by p_mfc->ndef_length, so safety relies on ndef_length never exceeding the size p_ndef_buffer was allocated for. Neither the allocation nor the code that first sets ndef_length is shown in the material.",
      "The commit message says control flow was updated to prevent further block reads on failure. The diff shows this only through the failed flag and the rollback; the code after the shown hunk (handling of failed/done and the next block read) is not in the material.",
      "rw_nfc_decodeTlv itself is not modified. The fix validates and rolls back after the parser runs rather than changing the TLV parser."
     ],
     "behavior_changes": [
      "A Mifare Classic tag whose NDEF TLV declares a length larger than the previously detected ndef_length now makes the read fail on the first block instead of continuing with an inflated length. The control block keeps the original ndef_length and ndef_start_pos.",
      "When rw_nfc_decodeTlv fails, ndef_length and ndef_start_pos are now also rolled back, and android_errorWriteLog(0x534e4554, \"178725766\") is no longer emitted. It is emitted only in the inflation case.",
      "No change in the copy path for tags whose TLV length is equal to or smaller than the previously detected length."
     ],
     "preconditions": [
      "A Mifare Classic tag presented to the device, with the NFC stack performing an NDEF read (rw_mfc_handle_read_op reached with work_offset == 0 so that rw_nfc_decodeTlv runs).",
      "The tag's NDEF TLV data must declare a length larger than the previously established ndef_length.",
      "The code runs in the com.android.nfc process per the commit message. Per index search, no caller outside the Nfc component was found."
     ],
     "per_major": {
      "14": "adapted fix: platform/system/nfc 7eaf8e16d9ad (android-security-14.0.0_r29), file src/nfc/tags/rw_mfc.cc. Same logic; the log statement uses DLOG_IF(INFO, nfc_debug_enabled) instead of LOG(VERBOSE). The resolver reports 7/8 lines of the patched region present.",
      "15": "same fix: platform/system/nfc 5dd24f8b4a6d (android-security-15.0.0_r17), file src/nfc/tags/rw_mfc.cc, identical hunk content with LOG(VERBOSE). 7/8 lines present.",
      "16": "same fix: platform/packages/modules/Nfc b79e5fd8cf14 (android-security-16.0.0_r8), file libnfc-nci/src/nfc/tags/rw_mfc.cc, identical hunk at different line numbers. 7/8 lines present.",
      "17": "same fix: platform/packages/modules/Nfc 19fc329133fe (android-security-17.0.0_r1), cherry-pick of 50df1ba6f310, file libnfc-nci/src/nfc/tags/rw_mfc.cc. 7/8 lines present."
     },
     "backport_notes": [
      "Single self-contained hunk in rw_mfc_handle_read_op. The file lives in platform/system/nfc (src/nfc/tags/rw_mfc.cc) on Android 14/15 and in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) on 16/17. The only difference between branches is the verbose-log macro on 14 (DLOG_IF instead of LOG(VERBOSE))."
     ],
     "confidence": "medium",
     "unknowns": [
      "Where p_ndef_buffer is allocated and how its size relates to the initial ndef_length (not in the material).",
      "The exact sequence of calls by which the old code reached the out-of-bounds write.",
      "The code following the shown hunk (handling of failed/done, scheduling of the next block read), which the commit message says changed in control flow; the diff shows only the failed flag and the rollback.",
      "Internals of rw_nfc_decodeTlv and whether it can also move ndef_start_pos in a way that matters beyond the rollback.",
      "Whether the issue requires a physical tag or can be triggered by an emulated tag; the material only says it occurs during Mifare Classic NDEF reads.",
      "Why the resolver reports 7/8 lines of the patched region on every branch."
     ],
     "meta": {
      "generated": "2026-10-01",
      "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-2026-49884": {
     "cve": "CVE-2026-49884",
     "summary": "In rw_mfc_handle_read_op (libnfc-nci rw_mfc.cc), the fix snapshots p_mfc->ndef_length and p_mfc->ndef_start_pos before rw_nfc_decodeTlv on the first block read. It treats a decode failure or an inflated ndef_length as a failure and rolls both fields back to the snapshot, so the tag-supplied, inflated length is no longer left in the control block to bound copies into p_ndef_buffer.",
     "component": "platform/packages/modules/Nfc (Android 16/17) and platform/system/nfc (Android 14/15), libnfc-nci Mifare Classic reader/writer (rw_mfc.cc)",
     "weakness": "Heap out-of-bounds write (CWE-787): a tag-controlled NDEF length was left in control-block state, with no rollback, after validation failed",
     "root_cause": "rw_mfc_handle_read_op copies NDEF bytes into p_mfc->p_ndef_buffer while work_offset < p_mfc->ndef_length. On the first block read (work_offset == 0), rw_nfc_decodeTlv(data) re-parses the TLV from the tag response and overwrites p_mfc->ndef_length and p_mfc->ndef_start_pos. The earlier mitigation (android_errorWriteLog 178725766) only compared saved_length with the new ndef_length to skip the copy for that block and set failed. It never restored p_mfc->ndef_length or p_mfc->ndef_start_pos. The inflated length therefore stayed in the control block and remained the bound for later processing, such as the `work_offset >= ndef_length` completion check and, per the commit message, possibly further block reads. The commit message says this led to a heap buffer overflow during Mifare Classic NDEF reads (b/503557185).",
     "fix_analysis": [
      "libnfc-nci/src/nfc/tags/rw_mfc.cc:rw_mfc_handle_read_op: removes the function-scope saved_length. Inside the `work_offset == 0` branch it instead snapshots `saved_len = p_mfc->ndef_length` and `saved_pos = p_mfc->ndef_start_pos` immediately before rw_nfc_decodeTlv(data).",
      "Same function: the failure condition becomes `!rw_nfc_decodeTlv(data) || p_mfc->ndef_length > saved_len`. In that case it logs, emits android_errorWriteLog(0x534e4554, \"178725766\") when inflation is detected, restores p_mfc->ndef_length = saved_len and p_mfc->ndef_start_pos = saved_pos, and sets failed = true. The offset is then taken from the restored ndef_start_pos.",
      "Same function: the copy loop is now guarded only by `!failed`. The old `saved_length >= ndef_length` comparison and its else branch are deleted, because the inflation check and rollback now happen before the copy. The loop body is unchanged: p_ndef_buffer[work_offset] = p[offset], bounded by work_offset < ndef_length, which is now the pre-decode (rolled-back) length."
     ],
     "completeness": [
      "Covers the only site in the diff where rw_nfc_decodeTlv changes ndef_length/ndef_start_pos during the read operation. On any failure path, both fields are restored to their pre-decode values.",
      "Only an increase of ndef_length is rejected. A decoded length smaller than or equal to the snapshot is accepted.",
      "The commit message says control flow is updated to prevent further block reads on failure, but the diff only shows the rollback and the `failed` flag. The downstream handling of `failed` in the rest of rw_mfc_handle_read_op is not in the material, so whether further reads are suppressed cannot be verified from the diff alone.",
      "Other rw_mfc paths that may call rw_nfc_decodeTlv (e.g. NDEF detection) are not touched and are not shown in the material."
     ],
     "behavior_changes": [
      "A TLV decode that yields a larger ndef_length than the stored one now fails the read and restores the previous ndef_length/ndef_start_pos, instead of leaving the inflated values in the control block. A plain decode failure now also restores both fields.",
      "android_errorWriteLog(0x534e4554, \"178725766\") is now emitted only when inflation is detected on the first read. Previously it was also emitted when `failed` was set by a rw_nfc_decodeTlv failure.",
      "The verbose log \" FAILED finding TLV\" is now also printed on inflation, not only on decode failure.",
      "No functional change for tags whose decoded length is unchanged or smaller."
     ],
     "preconditions": [
      "NFC is active and a Mifare Classic tag (or emulation) presenting an NDEF TLV is in range and is processed by com.android.nfc through libnfc-nci.",
      "An NDEF read operation reaches rw_mfc_handle_read_op with p_mfc->work_offset == 0 (the first block of the message), so rw_nfc_decodeTlv re-parses the TLV from the tag response.",
      "The TLV in the tag response encodes an NDEF length larger than the ndef_length already held in the control block.",
      "The index search did not surface any callers of rw_mfc_handle_read_op, and the material does not show the dispatch path within rw_mfc.cc."
     ],
     "per_major": {
      "14": "Same fix, applied in platform/system/nfc (src/nfc/tags/rw_mfc.cc) as 7eaf8e16d9ad in android-security-14.0.0_r29. The only difference is the logging macro (DLOG_IF(INFO, nfc_debug_enabled) instead of LOG(VERBOSE)).",
      "15": "Same fix, applied in platform/system/nfc (src/nfc/tags/rw_mfc.cc) as 5dd24f8b4a6d in android-security-15.0.0_r17. The hunks are identical.",
      "16": "Same fix, applied in platform/packages/modules/Nfc (libnfc-nci/src/nfc/tags/rw_mfc.cc) as b79e5fd8cf14 in android-security-16.0.0_r8. The hunks are identical, at different line offsets.",
      "17": "Same fix, platform/packages/modules/Nfc 19fc329133fe in android-security-17.0.0_r1 (cherry-pick of 50df1ba6f310). The hunks are identical."
     },
     "backport_notes": [
      "Single self-contained commit. The file is src/nfc/tags/rw_mfc.cc in platform/system/nfc for 14/15 and libnfc-nci/src/nfc/tags/rw_mfc.cc in platform/packages/modules/Nfc for 16/17 (libnfc-nci moved into the Nfc mainline module in newer releases, general platform knowledge). For older trees, adjust only the log macro (DLOG_IF vs LOG(VERBOSE))."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of rw_mfc_handle_read_op (how `failed` and `done` are handled afterwards, and whether further block reads are issued) is not in the material, so the commit message's claim about preventing further block reads cannot be verified from the diff.",
      "How p_ndef_buffer is allocated and sized (the caller, and how its size relates to the detected ndef_length) is not shown.",
      "The resolver reports 7/8 patched-region lines present in every security tag. The material does not identify the differing line."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49918": {
     "cve": "CVE-2026-49918",
     "summary": "Adds 64-bit overflow checks to the cupsBytesPerLine and line/page buffer size computations in libcupsfilters (imagetoraster.c, pwgtoraster.c, raster.c) so that large raster dimensions no longer wrap to undersized heap allocations.",
     "component": "platform/external/libcupsfilters, raster header processing and PWG/image-to-raster filter functions (cupsfilters/imagetoraster.c, cupsfilters/pwgtoraster.c, cupsfilters/raster.c)",
     "weakness": "Integer overflow in buffer size calculation leading to undersized heap allocation and heap out-of-bounds write (missing bounds check)",
     "root_cause": "Buffer sizes were computed with 32-bit (unsigned int / int) arithmetic from raster header fields. cupsBytesPerLine = (cupsBitsPerPixel * cupsWidth + 7) / 8, multiplied by cupsNumColors for CUPS_ORDER_BANDED. In pwgtoraster.c out_page() the same applied to inlineoffset = bitmapoffset[0] * 3, inlinesize = cupsWidth * 3, the line buffer size max(cupsBytesPerLine * res_up_factor[0], inlineoffset + inlinesize) (held in int i, j), the lineavg size cupsBytesPerLine * res_down_factor[1], the pagebuf size cupsHeight * inlinesize and the preBuf2 size cupsWidth * 3. In imagetoraster.c it applied to the row buffer malloc(2 * cupsBytesPerLine). With large width, depth or height values these products could wrap, so calloc/malloc received an undersized length. According to the commit message, this led to heap out-of-bounds writes when processing crafted raster streams.",
     "fix_analysis": [
      "cupsfilters/raster.c:cfRasterPrepareHeader: computes bpl as uint64_t (casting cupsBitsPerPixel first) and applies the BANDED multiply by cupsNumColors to the 64-bit value. If bpl > UINT32_MAX it logs an error, calls cupsFreeOptions(num_options, options) and returns -1. Otherwise it stores (unsigned int)bpl in h->cupsBytesPerLine.",
      "cupsfilters/raster.c:cfRasterSetColorSpace: computes (cupsWidth * cupsBitsPerPixel + 7) / 8 the same way in 64 bits and returns -1 (without logging) when the result exceeds UINT32_MAX.",
      "cupsfilters/imagetoraster.c:cfFilterImageToRaster: computes cupsBytesPerLine (including the BANDED multiply) as uint64_t. On overflow it logs, calls cfImageClose(img) and returns 1. The row buffer size 2ULL * cupsBytesPerLine is checked against UINT32_MAX before malloc, with the same error handling.",
      "cupsfilters/pwgtoraster.c:out_page: i and j change from int to uint64_t. bytesPerLine is checked against UINT32_MAX before the BANDED multiply, and cupsBytesPerLine is checked again after it (ret = false; goto out). In the cupsNumColors == 3 branch, inlineoffset (bitmapoffset[0] * 3) and inlinesize (cupsWidth * 3) are computed in uint64_t, and the function returns false on overflow. The 'line' calloc size is computed in 64 bits and rejected if it exceeds UINT32_MAX. The lineavg, pagebuf and preBuf2 callocs are each guarded the same way. The preBuf2 path also frees preBuf1 before goto out.",
      "patches/libcupsfilters-2.1.1-Fix-integer-overflows-in-raster-header-processing.patch: a new file containing the same change as a patch, in the project's patches directory. The material does not show how the build uses it."
     ],
     "completeness": [
      "Covers every cupsBytesPerLine computation in the diff (cfRasterPrepareHeader, cfRasterSetColorSpace, cfFilterImageToRaster, out_page) and the line, lineavg, pagebuf, preBuf2 and row allocations.",
      "The checks only reject values that do not fit in 32 bits. Any size up to UINT32_MAX still goes to malloc/calloc, and the diff adds no NULL check on the allocation results (for example row = malloc(...) in cfFilterImageToRaster).",
      "In out_page, the inlineoffset/inlinesize overflow paths use return (false) instead of ret = false; goto out, unlike the other new checks. The material does not show whether this skips cleanup done at the out label.",
      "Only the cupsNumColors == 3 branch of the inlineoffset/inlinesize computation changes. The diff does not show the other input_color_mode branches, so their arithmetic cannot be checked.",
      "cfRasterSetColorSpace now returns -1 on overflow. The material does not show whether its callers check the return value."
     ],
     "behavior_changes": [
      "cfRasterPrepareHeader returns -1 (after freeing options), and cfRasterSetColorSpace returns -1, when the computed bytes-per-line does not fit in 32 bits.",
      "cfFilterImageToRaster returns 1, and out_page in pwgtoraster.c returns false, on oversized headers or buffers, logging new CF_LOGLEVEL_ERROR messages that mention '(overflow)'.",
      "Jobs whose raster dimensions or color depth produce sizes above UINT32_MAX are now rejected instead of processed."
     ],
     "preconditions": [
      "Raster stream or image header values (cupsWidth, cupsHeight, cupsBitsPerPixel, cupsNumColors, bitmapoffset, resolution factors) reach cfFilterPWGToRaster/out_page, cfFilterImageToRaster, cfRasterPrepareHeader or cfRasterSetColorSpace.",
      "The dimensions are large enough that bits-per-pixel * width, width * 3, height * inlinesize or a similar product exceeds 32 bits.",
      "The material does not show which Android process calls these libcupsfilters functions. Per index search, the matches in packages/services/BuiltInPrintService/jni/plugins/lib_pwg.c use libcups raster writer APIs (cupsRasterWritePixels, cups_page_header2_t), not the patched functions."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin; platform/external/libcupsfilters is not present in android-security-14.0.0_r29.",
      "15": "not affected: not listed by the bulletin; project not present in android-security-15.0.0_r17.",
      "16": "not affected per the per-major facts (not listed by the bulletin); project not present in android-security-16.0.0_r8. The bulletin's affected-versions field mentions 16-qpr2, but the material contains no 16-qpr2 commit.",
      "17": "same fix: commit 5c0e5bf182a5 (Change-Id I1901738698979c964f89f244cfe8f6be0ebf501f, cherry-picked from two internal commits) in android-security-17.0.0_r1; 8/8 lines of the patched region present."
     },
     "backport_notes": [
      "Single self-contained commit. The hunks rely on <stdint.h> plus the log/ld, ret/out, cfImageClose and cupsFreeOptions constructs already present in the surrounding code. The commit also adds a patches/*.patch copy of the change.",
      "cfRasterSetColorSpace gains a new -1 return path. When backporting, check that its callers in the target branch treat a non-zero return as failure."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Android component or process feeds raster streams into these libcupsfilters filter functions, and therefore what attack surface the Critical EoP rating assumes.",
      "Whether a 16-qpr2 branch received the same change: the affected-versions list includes it, but no commit for it is shown.",
      "Whether the return (false) paths in out_page skip resource cleanup at the out label.",
      "The body of the non-RGB inlineoffset/inlinesize branches in out_page, which the diff does not touch.",
      "How the build uses the added patches/*.patch file, if at all.",
      "Where the out-of-bounds writes described in the commit message actually happen (the write sites are not in the material)."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49919": {
     "cve": "CVE-2026-49919",
     "summary": "Replaces the unchecked `size = rows * pitch; FT_ALLOC(buf, size)` in tt_face_colr_blend_layer (src/sfnt/ttcolr.c) with FT_ALLOC_MULT(buf, rows, pitch), so the allocator detects a multiplication overflow instead of allocating a wrapped size.",
     "component": "platform/external/freetype, sfnt driver, COLR (color glyph) layer blending (src/sfnt/ttcolr.c)",
     "weakness": "Integer overflow in allocation size computation (undersized allocation; a heap buffer overflow is the likely consequence but is not shown in the material)",
     "root_cause": "In tt_face_colr_blend_layer the size of the temporary buffer was computed as `size = rows * pitch` with no overflow check and passed to FT_ALLOC. If the product wrapped, the allocation could succeed with a buffer smaller than rows * pitch. The code then goes on to use dstSlot->bitmap.buffer. The commit message references FreeType issue #1389 and says the overflow is now detected in ft_mem_qrealloc.",
     "fix_analysis": [
      "src/sfnt/ttcolr.c:tt_face_colr_blend_layer: the two lines `size = rows * pitch;` and `if ( FT_ALLOC( buf, size ) )` are replaced by `if ( FT_ALLOC_MULT( buf, rows, pitch ) )`. FT_ALLOC_MULT passes the count and item size separately to the memory layer, which (per the commit message) detects the multiplication overflow in ft_mem_qrealloc and returns an error instead of allocating a wrapped size.",
      "On error the function returns `error` as before; no other control flow changes. It is a single-commit fix, with the same change on all branches."
     ],
     "completeness": [
      "Covers only the one allocation in tt_face_colr_blend_layer; the diff changes nothing about how rows and pitch are derived or bounded.",
      "Removing the `size = rows * pitch` assignment is the only other change. The material does not show whether the `size` variable is still declared or used elsewhere in the function, or whether other row*pitch products exist in ttcolr.c (none are shown)."
     ],
     "behavior_changes": [
      "Rendering a COLR glyph whose layer bitmap rows*pitch overflows now fails with an allocation error from tt_face_colr_blend_layer instead of continuing with an undersized buffer.",
      "No API or output change for fonts whose rows*pitch does not overflow."
     ],
     "preconditions": [
      "A font with a COLR table processed by the FreeType sfnt driver that reaches tt_face_colr_blend_layer (color glyph layer blending) with a bitmap whose rows*pitch wraps.",
      "Per index search, platform font code such as frameworks/rs/rsFont.cpp (FT_New_Memory_Face / FT_New_Face on caller-supplied data) and frameworks/minikin uses FreeType. The material does not show whether those paths reach COLR blending."
     ],
     "per_major": {
      "14": "same fix: platform/external/freetype ab9caf444825 in android-security-14.0.0_r29, identical FT_ALLOC_MULT replacement (hunk at line 1803).",
      "15": "same fix: platform/external/freetype 9f67316aa41d in android-security-15.0.0_r17, identical diff (hunk at line 1803).",
      "16": "same fix: platform/external/freetype 6770ef4d9faf in android-security-16.0.0_r8, identical diff (hunk at line 1803).",
      "17": "same fix: platform/external/freetype 65f3c6ba20e7 in android-security-17.0.0_r1, identical diff (hunk at line 1805)."
     },
     "backport_notes": [
      "A single small hunk in one file with no dependencies. It applies cleanly wherever tt_face_colr_blend_layer contains the `size = rows * pitch; FT_ALLOC( buf, size )` pair. If `size` becomes unused after the change, check for a compiler warning treated as an error in the local build."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include how rows and pitch are derived or bounded, or the copy loop after the allocation.",
      "Whether the `size` variable is still declared or used elsewhere in tt_face_colr_blend_layer after the fix.",
      "Details of upstream FreeType issue #1389 beyond the reference in the commit message.",
      "Whether the platform callers found by index search (rsFont, minikin) reach COLR layer blending."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49921": {
     "cve": "CVE-2026-49921",
     "summary": "Adds SDP_MAX_ATTR_LEN bounds checks and a dynamically sized serialization buffer to the Bluetooth SDP server so an oversized attribute in the local SDP database can no longer be serialized past a fixed 400-byte heap allocation (or read past it via a continuation offset) when building partial attribute responses.",
     "component": "platform/packages/modules/Bluetooth, system/stack/sdp (SDP server: sdp_server.cc, sdp_utils.cc, sdp_db.cc)",
     "weakness": "Heap buffer overflow (missing bounds check on attribute length against a fixed-size allocation) plus out-of-bounds read via unchecked continuation offset",
     "root_cause": "sdpu_build_partial_attrib_entry in system/stack/sdp/sdp_utils.cc always allocated a fixed buffer of SDP_MAX_ATTR_LEN (400) bytes and then called sdpu_build_attrib_entry to serialize the whole attribute (header plus p_attr->len payload bytes) into it without checking the attribute size. The database insertion path sdp_add_attribute_to_record (SDP_AddAttributeToRecord on older branches) in sdp_db.cc only checked attributes against SDP_MAX_PAD_LEN (the record's pad space), not SDP_MAX_ATTR_LEN, so attributes larger than 400 bytes could be stored. Per the commit message, the L2CAP continuation request branches in process_service_attr_req and process_service_search_attr_req lacked an explicit length check (on Android 14 the pre-fix check existed only in the 'rem_len < attr_len' branch). The function also computed len_to_copy from attr_len - *offset without verifying *offset < attr_len, so the memcpy could read beyond the serialized buffer.",
     "fix_analysis": [
      "system/stack/sdp/sdp_utils.cc:sdpu_build_partial_attrib_entry: computes attr_len = sdpu_get_attrib_entry_len(p_attr) first. If p_attr->len > SDP_MAX_ATTR_LEN it logs an error and returns p_out without writing. It allocates exactly attr_len bytes (osi_malloc(attr_len)) instead of a fixed SDP_MAX_ATTR_LEN buffer and bails out on nullptr. Only then does it call sdpu_build_attrib_entry. It adds a check that *offset < attr_len, and otherwise frees the buffer and returns p_out. The existing clamp of len to SDP_MAX_ATTR_LEN is kept.",
      "system/stack/sdp/sdp_server.cc:process_service_attr_req and process_service_search_attr_req: immediately after sdp_db_find_attr_in_rec returns a p_attr, if p_attr->len > SDP_MAX_ATTR_LEN the server logs 'SDP attr too big', calls sdpu_build_n_send_error(p_ccb, trans_num, tSDP_STATUS::SDP_NO_RESOURCES, NULL) and returns. As a result, neither the full nor the partial/continuation serialization paths run for such attributes.",
      "system/stack/sdp/sdp_db.cc:sdp_add_attribute_to_record: the truncation condition becomes 'attr_len > SDP_MAX_ATTR_LEN || free_pad_ptr + attr_len >= SDP_MAX_PAD_LEN'. max_allowed_len = min(SDP_MAX_ATTR_LEN, SDP_MAX_PAD_LEN - free_pad_ptr) is used to truncate TEXT_STR_DESC_TYPE values (NUL-terminated at max_allowed_len - 1). Other types that hit this branch get attr_len = 0, as before for pad overflow.",
      "Tests: stack_sdp_test.cc adds write_read_max_attr_len_slicing (a 400-byte attribute served via continuation slicing with max_list_len 50) and SDP_AddAttribute__exceed_max_attr_len_truncation (410 bytes truncated to 400). stack_sdp_utils_test.cc adds sdpu_build_partial_attrib_entry_exceeds_max_attr_len (len 500 returns p_out unchanged) and sdpu_build_partial_attrib_entry_within_bounds."
     ],
     "completeness": [
      "Covers three layers: the DB insertion cap, per-request attribute rejection in both attribute request handlers, and a self-defending sdpu_build_partial_attrib_entry (dynamic allocation, payload-length check, offset check, null check).",
      "The check inside sdpu_build_partial_attrib_entry is on p_attr->len (payload), not on the serialized attr_len (payload plus header). Because the allocation is now exactly attr_len, this no longer matters for the overflow.",
      "The server-side rejection covers the whole attribute loop, not only the partial/continuation branches, so oversized attributes are refused even where they would have fit in the response.",
      "The 17 diff for sdp_server.cc only adds lines (the two new checks). The removal of the old 'attr_len >= SDP_MAX_ATTR_LEN' check in the partial branch, which the Android 14 diff shows, does not appear in the 17 diff. The commit message's 'corrects a couple of mistaken bounds checks elsewhere in sdp_server.cc' is not visible in the 17 diff.",
      "Attributes already in the DB before an oversized insertion is blocked are handled by the server-side checks. The DB cap only affects new insertions."
     ],
     "behavior_changes": [
      "SDP_AddAttribute now truncates TEXT_STR_DESC_TYPE values to at most SDP_MAX_ATTR_LEN (400) bytes, with a warning log. Non-text attributes longer than 400 bytes are stored with length 0.",
      "Service attribute and service search attribute requests that hit a record attribute with len > SDP_MAX_ATTR_LEN now get an SDP error response with SDP_NO_RESOURCES instead of attribute data.",
      "sdpu_build_partial_attrib_entry returns p_out unchanged (writes nothing, does not advance offset) when the attribute is oversized, the allocation fails, or *offset >= attr_len. It now allocates exactly the serialized entry length."
     ],
     "preconditions": [
      "An attribute in the local SDP server database whose serialized entry exceeds the fixed 400-byte buffer. Attributes are added locally through SDP_AddAttribute -> sdp_add_attribute_to_record (per index search). The material does not show which local profiles or apps can register such sizes.",
      "A remote peer sending SDP service attribute or service search attribute requests to the device's SDP server (process_service_attr_req / process_service_search_attr_req) such that the attribute is sent partially, i.e. a small max_list_len or a continuation request with an attribute offset.",
      "Bluetooth enabled and an L2CAP SDP connection accepted from the peer (the bulletin rates this Critical RCE)."
     ],
     "per_major": {
      "14": "adapted fix (51b0cc33b16c in android-security-14.0.0_r29): same three-layer approach, but the function is SDP_AddAttributeToRecord with SDP_TRACE_* logging. In sdp_server.cc the p_attr->len > SDP_MAX_ATTR_LEN check sits after attr_len = sdpu_get_attrib_entry_len(p_attr), and the old 'attr_len >= SDP_MAX_ATTR_LEN' check in the partial branch is removed. sdp_utils.cc clamps len before the offset check. Tests use StackSdpMainTest and test::mock L2CA_DataWrite, and add osi_malloc/osi_free mocks. 7/8 lines of the patched region present.",
      "15": "adapted fix (c738041c8ce8 in android-security-15.0.0_r17): same logic as 17, with SDP_AddAttributeToRecord naming, log:: logging, SDP_NO_RESOURCES without the tSDP_STATUS scope, and test::mock L2CA_DataWrite in tests. 8/8 lines present.",
      "16": "same fix (947e8bbe7dc6 in android-security-16.0.0_r8): identical logic to 17. The only differences are SDP_AddAttributeToRecord instead of sdp_add_attribute_to_record and the &(p_ccb->device_address) argument form. 7/8 lines present.",
      "17": "same fix (4a9e8875e210 in android-security-17.0.0_r1), the reference commit; 7/8 lines of the patched region present."
     },
     "backport_notes": [
      "Google shipped one commit per branch. For trees that look like Android 14 (SDP_TRACE_* logging, SDP_AddAttributeToRecord, pre-fix 'attr_len >= SDP_MAX_ATTR_LEN' check only in the 'rem_len < attr_len' branch), follow the Android 14 variant: it moves the check before the continuation/partial branches and removes the old one. It also adds osi_malloc/osi_free mock bodies to the StackSdpUtilsTest fixture alongside the new tests."
     ],
     "confidence": "high",
     "unknowns": [
      "Which local profiles or app-facing APIs can register SDP attributes larger than 400 bytes; the material only shows SDP_AddAttribute as the entry point.",
      "The value of SDP_MAX_PAD_LEN and the size of p_ccb->rsp_list, so the exact overflow margins are not visible.",
      "Whether the pre-fix Android 17 sdp_server.cc still contained the old 'attr_len >= SDP_MAX_ATTR_LEN' check that the Android 14 diff removes (the 17 diff does not touch it even though the commit message mentions corrected bounds checks).",
      "Whether the fix has dependencies on other commits in downstream trees."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49926": {
     "cve": "CVE-2026-49926",
     "summary": "Removes `integer_overflow: true` from the sanitize block of the `libjxl` cc_library in external/libjxl Android.bp, so that unsigned integer wrap-around that upstream libjxl uses on purpose no longer triggers UBSan aborts.",
     "component": "platform/external/libjxl, Android.bp build configuration for the `libjxl` cc_library (JPEG XL library)",
     "weakness": "Reachable abort: the unsigned-integer-overflow sanitizer, enabled in the build, traps on unsigned wrap-around in library code that is well-defined behavior (the commit message says it is not a memory-safety bug)",
     "root_cause": "The Android build of libjxl (cc_library \"libjxl\" in Android.bp) enabled Soong's `integer_overflow` sanitizer on top of `all_undefined`. Upstream libjxl uses unsigned wrap-around on purpose in several places (the commit names ClampedGradient and backward loops). Unsigned overflow is defined behavior in C++, but with this sanitizer enabled those wrap-arounds triggered UBSan aborts on Android, which the bulletin rates as a Critical denial of service.",
     "fix_analysis": [
      "Android.bp :: cc_library { name: \"libjxl\" }: the single line `integer_overflow: true,` is deleted from the `sanitize` block, and `all_undefined: true` is kept. According to the commit message, signed-overflow and other UB sanitizers stay enabled, and only the unsigned-overflow instrumentation that caused the aborts is removed.",
      "No C++ source is changed. The fix is only a build-configuration change, chosen (per the commit) to avoid maintaining extensive local patches against upstream libjxl.",
      "The commit names `atest CtsGraphicsTestCases:android.graphics.cts.ImageDecoderTest#testInterestingDngs` as its test."
     ],
     "completeness": [
      "The fix covers every source compiled into the `libjxl` module (libjxl_dec_sources, libjxl_dec_box_sources, libjxl_dec_jpeg_sources, libjxl_enc_sources, libjxl_threads_sources, lib/jxl/cms/jxl_cms.cc), because the sanitizer setting applies to the whole module.",
      "No libjxl code is changed: the unsigned wrap-around sites stay as upstream wrote them, and the sanitizer simply no longer traps on them. If any such site were a real logic error rather than intentional, it would now go unnoticed instead of aborting. The material shows no such case.",
      "`all_undefined: true` remains, so per the commit, signed-overflow and other UB checks are still enabled in this module. A genuine signed overflow in libjxl would therefore still abort. The material shows none.",
      "Only the `libjxl` cc_library in Android.bp is shown. The material does not show whether any other module enables integer_overflow."
     ],
     "behavior_changes": [
      "libjxl no longer aborts its host process when unsigned arithmetic wraps. Those operations now complete with normal modular arithmetic.",
      "Unsigned-overflow instrumentation is removed from libjxl, so future unsigned wrap-around in this library will no longer be reported by UBSan on Android builds.",
      "The diff shows no API change."
     ],
     "preconditions": [
      "libjxl is built from the pre-fix Android.bp, where `integer_overflow: true` is set in the module's sanitize block.",
      "A process using libjxl runs into code that wraps unsigned values (the commit cites ClampedGradient and backward loops). The material does not show which processes use libjxl, which input triggers the wrap, or whether that input can come from a remote source."
     ],
     "per_major": {
      "14": "not affected per the bulletin; external/libjxl is not present in android-security-14.0.0_r29.",
      "15": "not affected per the bulletin; external/libjxl is not present in android-security-15.0.0_r17.",
      "16": "not affected per the bulletin; external/libjxl is not present in android-security-16.0.0_r8.",
      "17": "same fix: commit 069d75c482c7 in android-security-17.0.0_r1, a cherry-pick of commit 2b1649223ad2 (Change-Id I5bac14ad6973b3bc7dbbf865c2159f9f233f728c). At that tag the sanitize block contains only `all_undefined: true`, which matches the one-line deletion."
     },
     "backport_notes": [
      "Single one-line Android.bp change with no code dependency. On any branch that carries external/libjxl, remove `integer_overflow: true` from the sanitize block of the `libjxl` cc_library, then check with the CTS test named in the commit."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which processes link libjxl on Android 17, and how outside data reaches them. The index results (AImageDecoder, stb_image, libjpeg) are not direct libjxl callers.",
      "Exact libjxl source locations and inputs that trigger the unsigned wrap, beyond the commit's mention of ClampedGradient and backward loops.",
      "How the DNG test named in the commit relates to libjxl.",
      "The bulletin's affected-versions list includes 16-qpr2, but the per-major facts treat Android 16 as not affected and the project is absent from the Android 16 security tag. The state of 16-qpr2 is unclear.",
      "Whether a global sanitizer setting could re-enable integer_overflow for this module. The fix does not add an explicit `integer_overflow: false`.",
      "Whether the abort happened in every build configuration."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49927": {
     "cve": "CVE-2026-49927",
     "summary": "Adds 64-bit overflow checks when ppdRasterInterpretPPD derives cupsBytesPerLine. In write_flate it rejects cupsBytesPerLine*6 above INT32_MAX and replaces the header-sized stack VLAs and per-line mallocs with single checked heap allocations, freed through a common cleanup path.",
     "component": "platform/external/libppd, ppd raster interpreter (ppd/raster-interpret.c) and raster-to-PostScript filter (ppd/rastertops.c)",
     "weakness": "Integer overflow in size computation leading to undersized buffers and heap/stack out-of-bounds write (missing bounds check)",
     "root_cause": "In ppd/rastertops.c write_flate, buffer sizes came from the raster page header field header.cupsBytesPerLine using 32-bit unsigned arithmetic. This applied to the stack VLAs in[header.cupsBytesPerLine * 6] and out[header.cupsBytesPerLine * 6], and to the per-line heap allocation convertedpix = malloc(header.cupsBytesPerLine * 6). A large cupsBytesPerLine wraps the multiplication, so the buffers end up much smaller than the data convert_pixels then writes, which gives a heap out-of-bounds write. The conversion branch is used when cupsBitsPerColor == 1 and the color space is in a set that includes CUPS_CSPACE_RGB. The VLAs also let a header value drive unbounded stack allocation. In ppd/raster-interpret.c ppdRasterInterpretPPD, h->cupsBytesPerLine = (h->cupsBitsPerPixel * h->cupsWidth + 7) / 8 and the CUPS_ORDER_BANDED multiply by h->cupsNumColors were also computed in 32 bits with no overflow detection. The old write_flate also leaked pixdata on the fwrite error path.",
     "fix_analysis": [
      "ppd/raster-interpret.c:ppdRasterInterpretPPD: adds #include <stdint.h>. Computes bpl = ((uint64_t)h->cupsBitsPerPixel * h->cupsWidth + 7) / 8. If bpl > UINT32_MAX it calls _ppdRasterAddError(\"Invalid raster dimensions (overflow).\") and returns -1, before storing into h->cupsBytesPerLine.",
      "ppd/raster-interpret.c:ppdRasterInterpretPPD: for CUPS_ORDER_BANDED, computes (uint64_t)h->cupsBytesPerLine * h->cupsNumColors. If the result exceeds UINT32_MAX it reports \"Invalid raster dimensions/color (overflow).\" and returns -1. Otherwise it stores the value.",
      "ppd/rastertops.c:write_flate: removes the VLAs in[]/out[]. Computes expanded_size = (uint64_t)header.cupsBytesPerLine * 6. If expanded_size > INT32_MAX it logs via doc->logfunc at CF_LOGLEVEL_ERROR and returns Z_MEM_ERROR, before any allocation or deflateInit.",
      "ppd/rastertops.c:write_flate: allocates in, out and convertedpix once with malloc((size_t)expanded_size), and pixdata with malloc(header.cupsBytesPerLine). If any allocation is NULL it logs and goes to cleanup with Z_MEM_ERROR. convertedpix is always sized to the 6x maximum whichever branch the flag selects, and alloc = (int)expanded_size in the conversion branch.",
      "ppd/rastertops.c:write_flate: removes the per-iteration malloc/free of pixdata and convertedpix inside the compression loop. The fwrite failure path now sets ret = Z_ERRNO and jumps to cleanup, and success sets ret = Z_OK. A new cleanup label calls deflateEnd only if deflate_initialized is set, then frees in, out, pixdata and convertedpix.",
      "patches/libppd-2.1.1-Fix-integer-overflow-and-heap-OOB-write-errors.patch: records the same change as a patch file against upstream libppd 2.1.1, alongside the direct source edits."
     ],
     "completeness": [
      "Covers both size computations in write_flate (stack VLAs and heap convertedpix) with one early check of cupsBytesPerLine*6 against INT32_MAX. No path in write_flate allocates from a wrapped value.",
      "Covers both multiplications in ppdRasterInterpretPPD (bits*width, and *numColors for banded order).",
      "write_flate still does not check the return value of cupsRasterReadPixels. A short read leaves pixdata partly uninitialized but within bounds.",
      "The commit message says unsigned long long and INT_MAX, but the code uses uint64_t and INT32_MAX/UINT32_MAX. These are equivalent on Android targets."
     ],
     "behavior_changes": [
      "ppdRasterInterpretPPD now fails with -1 and a raster error string when bytes-per-line would exceed UINT32_MAX.",
      "write_flate returns Z_MEM_ERROR, with a CF_LOGLEVEL_ERROR log, when cupsBytesPerLine*6 exceeds INT32_MAX or when any allocation fails, instead of continuing.",
      "Buffers are now heap allocated once per call instead of on the stack or per line. Peak heap use is up to 4 buffers (three of them 6x cupsBytesPerLine) for the whole call.",
      "The fwrite error path now frees all buffers. Previously it leaked pixdata."
     ],
     "preconditions": [
      "A CUPS raster page header with an attacker-influenced cupsBytesPerLine that reaches write_flate in ppd/rastertops.c (the ppdFilterRasterToPS filter), per the diff.",
      "Or PPD/page device values that make cupsBitsPerPixel*cupsWidth (or *cupsNumColors for banded order) overflow 32 bits in ppdRasterInterpretPPD.",
      "In the old code, the heap OOB write through convertedpix needs the 6x conversion branch: cupsBitsPerColor == 1 and a color space set that includes CUPS_CSPACE_RGB (the full set is not shown). The stack VLA sizing applied to every call."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin. external/libppd is not present in android-security-14.0.0_r29.",
      "15": "not affected: not listed by the bulletin. external/libppd is not present in android-security-15.0.0_r17.",
      "16": "not affected: Android 16 is not listed by the bulletin, and the project is not present at android-security-16.0.0_r8. The bulletin does list 16-qpr2 separately as affected, but the material has no 16-qpr2 tag facts.",
      "17": "same fix: commit 6ce8492f2566 in android-security-17.0.0_r1, with 8/8 lines of the patched region present. Cherry-picked from two internal changes under Change-Id I934add5868d832899dfebed68a1eff00f535e62d."
     },
     "backport_notes": [
      "Apply both halves together: the direct edits to ppd/raster-interpret.c and ppd/rastertops.c, and the new patches/libppd-2.1.1-Fix-integer-overflow-and-heap-OOB-write-errors.patch. The code needs <stdint.h> for uint64_t/UINT32_MAX/INT32_MAX."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which Android process or service runs ppdFilterRasterToPS or ppdRasterInterpretPPD on untrusted input, so the privilege boundary behind the Critical EoP rating is not shown.",
      "Whether 16-qpr2 received the same commit. The bulletin lists it as affected but no tag facts are provided.",
      "Where the header passed to write_flate comes from, and whether header fields read directly from a raster stream are validated anywhere besides write_flate's own INT32_MAX check.",
      "The full set of color spaces that turns on the 6x conversion branch (the condition is cut off in the diff)."
     ],
     "meta": {
      "generated": "2026-10-01",
      "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-2026-49932": {
     "cve": "CVE-2026-49932",
     "summary": "Hardens PduParser.parseParts in frameworks/base telephony/common so that malformed MMS multipart PDUs no longer cause StackOverflowError (unbounded recursion into nested multipart/alternative parts), OutOfMemoryError (allocation of a part buffer sized by an unchecked declared dataLength), or NullPointerException/ArrayIndexOutOfBoundsException (taking getPart(0) of a null or empty nested body). The parser now returns null in these cases.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parser (com.google.android.mms.pdu.PduParser)",
     "weakness": "Missing input validation in a recursive parser: uncontrolled recursion depth, unchecked length used for allocation, and unchecked null/empty result before indexing",
     "root_cause": "In telephony/common/com/google/android/mms/pdu/PduParser.java, parseParts(ByteArrayInputStream) reads a per-part dataLength via parseUnsignedInt. When dataLength > 0 it allocates new byte[dataLength] without checking it against pduDataStream.available(), so a declared length far larger than the actual data causes a huge allocation (OutOfMemoryError). If the part's content type is ContentType.MULTIPART_ALTERNATIVE, parseParts recursed on the part data with no depth bound, so deeply nested parts could exhaust the stack (StackOverflowError). The result of the recursive call was used directly as childBody.getPart(0) without checking that childBody was non-null or had at least one part, which produced NullPointerException or ArrayIndexOutOfBoundsException on malformed nested bodies.",
     "fix_analysis": [
      "PduParser.java: adds private static final int MAX_PARSING_DEPTH = 30, with a comment justifying the value (real-world nesting typically < 5, background thread stacks 256KB-512KB).",
      "PduParser.java:parseParts(ByteArrayInputStream): the existing protected method becomes a thin wrapper that calls a new private parseParts(ByteArrayInputStream pduDataStream, int depth) with depth 0. The protected signature is preserved.",
      "PduParser.java:parseParts(stream, depth): after the existing null-stream check, it logs with Log.e and returns null if depth > MAX_PARSING_DEPTH. This bounds the recursion.",
      "PduParser.java:parseParts(stream, depth): inside the dataLength > 0 branch, before new byte[dataLength], it logs with Log.e and returns null if dataLength > pduDataStream.available(). The allocation therefore cannot exceed the bytes actually left in the stream.",
      "PduParser.java:parseParts(stream, depth): the MULTIPART_ALTERNATIVE recursion now calls parseParts(new ByteArrayInputStream(partData), depth + 1). If the returned childBody is null or childBody.getPartsNum() == 0, the method logs and returns null instead of calling childBody.getPart(0).",
      "Adds tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/PduParserTest.java with three fixed byte payloads (AIOOBE, NPE and OOM triggers). Each test asserts that new PduParser(payload, true).parse() returns null."
     ],
     "completeness": [
      "All three guards are confined to parseParts. The depth counter is incremented only along the MULTIPART_ALTERNATIVE recursion, which is the only recursive call shown in the diff.",
      "The dataLength vs available() check protects only the part data allocation in parseParts. The diff does not show validation of other length-driven values in the same method (headerLength, the count from parseUnsignedInt). Whether they are bounded elsewhere is not visible in the material.",
      "The commit message says skipWapValue and parameter skipping were changed to use InputStream.skip instead of allocating temporary byte arrays. The provided diff (32 changed lines in PduParser.java) contains no such change.",
      "When a nested alternative fails or is empty, the fix returns null for the whole body, not just the offending part. This is a fail-closed choice; before the fix, such input threw an exception.",
      "Per index search, packages/apps/Messaging carries its own copies of PduParser (com/android/messaging/mmslib/pdu/PduParser.java and android/support/v7/mms/pdu/PduParser.java), and this frameworks/base commit does not touch them. The visible prologue of the mmslib parseParts has no depth parameter or depth check. The rest of those methods is not shown."
     ],
     "behavior_changes": [
      "PduParser.parse() now returns null, with Log.e messages tagged LOG_TAG, in three cases: multipart/alternative nesting deeper than 30, a declared part dataLength larger than the remaining stream bytes, or a nested alternative body that parses to null or zero parts. Per the commit message, such inputs could previously throw NullPointerException, ArrayIndexOutOfBoundsException, OutOfMemoryError or StackOverflowError.",
      "Truncated PDUs (dataLength larger than the remaining bytes) are now rejected as a whole instead of being read short.",
      "A new test class, PduParserTest, is added to TelephonyCommonTests."
     ],
     "preconditions": [
      "The device must parse an attacker-supplied MMS PDU with PduParser, along a path where parse() calls parseParts. In the Messaging copies shown per index search, that means message type SEND_REQ or RETRIEVE_CONF (in mmslib, RETRIEVE_CONF also needs retrieve status OK). The frameworks/base parse() is not shown.",
      "Per index search, packages/services/Mms MmsService.parsePduForAnyCarrier calls new PduParser(data, ...).parse() and catches only RuntimeException. StackOverflowError and OutOfMemoryError are java.lang.Error subclasses, so that handler would not catch them, while NPE and AIOOBE would be caught (general platform knowledge).",
      "The parser itself does not check any permission; it operates on raw PDU bytes."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 8325de4e11e9 in android-security-14.0.0_r29, same RESTRICT AUTOMERGE title and identical diff, 8/8 patched lines present. The only visible difference is file mode 100755 on the pre-image.",
      "15": "same fix: platform/frameworks/base b86eef8c2ffe in android-security-15.0.0_r17, identical diff, 8/8 patched lines present.",
      "16": "same fix: platform/frameworks/base a28fb127a57b in android-security-16.0.0_r8, identical diff, 8/8 patched lines present.",
      "17": "same fix: platform/frameworks/base 1c37e9ef69ea in android-security-17.0.0_r1, 8/8 patched lines present."
     },
     "backport_notes": [
      "The patch is self-contained in one file plus a new test. The parseParts hunks have the same offsets on all four branches (pre-image blob 62eac7ac942a on 14/15/16, ffd2a8b419bb on 17), so it is expected to cherry-pick cleanly onto a tree with the stock parseParts.",
      "If a tree contains forked copies of PduParser (for example the packages/apps/Messaging copies found per index search), they need separate review and porting. This commit does not change them."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether the skipWapValue / InputStream.skip optimisation named in the commit message exists in some other commit for this bug. It is absent from every diff provided.",
      "Whether the three test payloads are the reporter's exact triggers or synthetic reproductions.",
      "Whether other PduParser call sites (beyond MmsService.parsePduForAnyCarrier, seen per index search) wrap parse() in exception handling, and where MmsService's data comes from.",
      "Which process crashes. The bulletin lists only DoS as the type, and the material gives no further detail.",
      "Whether the Messaging PduParser copies contain the same unbounded recursion and the unchecked getPart(0); only partial code is shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55256": {
     "cve": "CVE-2026-55256",
     "summary": "Adds null checks in PduParser.parsePartHeaders so that a null return from parseWapString for the Content-Disposition value, the filename parameter, or a text header name/value is no longer passed to PduPart setters or new String(), preventing potential NullPointerExceptions while parsing an MMS PDU.",
     "component": "platform/frameworks/base, telephony/common MMS PDU parser (com.google.android.mms.pdu.PduParser); same code duplicated in platform/development samples/ApiDemos mmslib",
     "weakness": "Missing null check (NULL pointer dereference) on parser return values",
     "root_cause": "In PduParser.parsePartHeaders, parseWapString(pduDataStream, TYPE_TEXT_STRING) can return null, but its result was used without a check. part.setContentDisposition(...) and part.setFilename(...) were called directly with the return value. In the text-header branch, new String(tempHeader) was evaluated and tempValue was passed to part.setContentTransferEncoding with neither checked for null. The commit describes these as potential NullPointerExceptions, and the bulletin classifies the issue as DoS.",
     "fix_analysis": [
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (Content-Disposition, Token-text branch): the parseWapString result is stored in a local byte[] contentDisposition, and part.setContentDisposition is only called when it is non-null.",
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (filename parameter, P_FILENAME): the parseWapString result is stored in a local byte[] filename, and part.setFilename is only called when it is non-null.",
      "telephony/common/com/google/android/mms/pdu/PduParser.java:parsePartHeaders (text header branch): the condition 'true == PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))' is replaced by 'null != tempHeader && null != tempValue && ...equalsIgnoreCase(new String(tempHeader))'. As a result, new String() is never called on a null header name and a null value is never stored.",
      "samples/ApiDemos/src/com/example/android/mmslib/pdu/PduParser.java:parsePartHeaders in platform/development receives the identical three-hunk change (commit 21dbe0e7ba1f on 17, 6aa9341a7695 on 14)."
     ],
     "completeness": [
      "Covers the three null-unsafe uses of parseWapString results inside parsePartHeaders: the Content-Disposition token-text value, the filename parameter, and the generic text header name/value pair.",
      "On a null value the fix skips the setter without any signal and continues parsing. It does not abort the part or report an error.",
      "This change does not touch other parseWapString call sites in PduParser outside parsePartHeaders. In the shown parseHeaders context, new String(bVal) is called on an unchecked parseWapString result, but only under the LOCAL_LOGV guard.",
      "The fix is applied to both copies of the parser (frameworks/base and the ApiDemos sample in platform/development)."
     ],
     "behavior_changes": [
      "If a part's Content-Disposition value or filename parameter fails to parse, that field is now left unset instead of being set to null.",
      "A text header whose name or value fails to parse is now ignored. Content-Transfer-Encoding is only recorded when both name and value are non-null.",
      "No new error return, log, or exception is introduced. Parsing proceeds past the malformed header."
     ],
     "preconditions": [
      "A PDU reaches PduParser.parsePartHeaders, i.e. a multipart MMS PDU with part headers is parsed by the MMS stack (frameworks/base telephony/common copy) or by the ApiDemos sample.",
      "The part contains a Content-Disposition header with a token-text value or a filename parameter, or a text-string header, encoded so that parseWapString returns null.",
      "The specific malformed encoding that makes parseWapString return null is not shown in the material.",
      "The frameworks/base telephony/common PduParser is the shared MMS PDU parser used by the platform MMS/telephony code (general platform knowledge); exact callers are not shown in the material."
     ],
     "per_major": {
      "14": "same fix: platform/development 6aa9341a7695 in android-security-14.0.0_r29, identical diff. The resolver reports it resolved by bug_id, but the material shows no frameworks/base commit for 14.",
      "15": "same fix: platform/frameworks/base e2b80df1360a in android-security-15.0.0_r17, identical diff (same pre-image blob ecab9562f72e as 16). No platform/development commit is shown.",
      "16": "same fix: platform/frameworks/base 01f51483eb2f in android-security-16.0.0_r8, identical diff. No platform/development commit is shown.",
      "17": "same fix: platform/frameworks/base 724976bd27bd and platform/development 21dbe0e7ba1f in android-security-17.0.0_r1, identical diffs, cherry-picked from internal changes (Cherrypick-From)."
     },
     "backport_notes": [
      "Two independent single-file commits (frameworks/base and development) with no ordering dependency. The hunks target the original setContentDisposition/setFilename/'true ==' pattern in parsePartHeaders. The pre-image blobs differ between 17 (15448a6a77f4) and 15/16 (ecab9562f72e), although the hunk content and offsets are the same.",
      "The platform/development copy belongs to the ApiDemos sample app, which is not normally part of device builds (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which input condition makes parseWapString return null is not shown (the parseWapString body is not in the material).",
      "Whether PduPart.setContentDisposition / setFilename / setContentTransferEncoding throw an NPE on null themselves, or the NPE occurs later, is not shown.",
      "The material lists only the platform/development commit for Android 14, not a frameworks/base commit.",
      "The resolver reports '6/8 lines of the patched region present' on every major, including 17 where the commit is shown. The material does not explain the mismatch.",
      "The material does not show which process the crash occurs in, so the practical impact behind the Critical DoS rating is unclear.",
      "How the frameworks/base pre-images on 15/16 and 17 differ is not shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55273": {
     "cve": "CVE-2026-55273",
     "summary": "aapt2 now sanitizes XML-derived comment text before writing it into the Javadoc blocks of the generated R.java. It replaces the \"*/\" terminator with \"*&#47;\" and the backslash of any backslash-u sequence with \"&#92;\", so the comment cannot close the Javadoc block and inject Java code.",
     "component": "platform/frameworks/base, tools/aapt2 (Java R class generation, AnnotationProcessor)",
     "weakness": "Improper neutralization of special elements in generated code (code injection via Javadoc comment breakout)",
     "root_cause": "AnnotationProcessor::AppendCommentLine in tools/aapt2/java/AnnotationProcessor.cpp took comment text extracted from XML resources and appended it to the Javadoc block that is later printed into the generated R.java. Before the fix it only processed API annotation tokens (@SystemApi, @TestApi, @FlaggedApi, @hide, @deprecated). It never neutralized the Javadoc terminator \"*/\" or Java Unicode escapes such as backslash-u002f, which javac decodes before comments are parsed. Per the commit message, a comment containing either could break out of the Javadoc block and inject executable Java code into R.java.",
     "fix_analysis": [
      "tools/aapt2/java/AnnotationProcessor.cpp: adds a static helper SanitizeJavadoc(std::string*) driven by a table of SanitizationRule {find_target, replace_length, replacement}. Rule 1 replaces every \"*/\" (2 chars) with \"*&#47;\". Rule 2 finds every backslash-u (C++ literal \"\\\\u\") and replaces only the backslash (1 char) with \"&#92;\", keeping the 'u'. Each rule loops with find/replace and advances past the inserted replacement, so inserted text is not rescanned.",
      "tools/aapt2/java/AnnotationProcessor.cpp:AnnotationProcessor::AppendCommentLine: calls SanitizeJavadoc(&comment) after the annotation-rule processing loop and before the TrimWhitespace empty-line check.",
      "tools/aapt2/java/AnnotationProcessor_test.cpp: adds TEST(AnnotationProcessorTest, SanitizesJavadocTermination). It covers plain \"*/\", star followed by backslash-u002f, backslash-u002a followed by '/', the multi-u form, adjacent escapes, and an escape preceded by an extra backslash, and asserts the exact printed Javadoc."
     ],
     "completeness": [
      "Covers every comment line that goes through AnnotationProcessor::AppendCommentLine (the test reaches it via AppendComment). The call sits at function level after annotation processing, so it is not gated on add_api_annotations.",
      "Rule ordering is safe: the \"*/\" replacement (\"*&#47;\") adds no backslash, and the backslash replacement (\"&#92;\") adds no '/' or '*'.",
      "Only the lowercase backslash-u prefix is matched. The multi-u form is handled because its leading backslash-u still matches (the test covers it).",
      "The fix only touches AnnotationProcessor. The material does not show whether any other aapt2 Java generation path writes XML comment text into R.java without going through AppendCommentLine (for example, the consumer of AnnotationProcessor::ExtractFirstSentence)."
     ],
     "behavior_changes": [
      "Generated R.java Javadoc now contains \"*&#47;\" wherever a resource comment contained \"*/\", and \"&#92;u\" wherever it contained backslash-u. The rendered HTML is intended to look the same, but the raw source text differs.",
      "Java Unicode escapes in XML resource comments are no longer decoded by javac in the generated Javadoc.",
      "No new error path: the sanitization is a silent rewrite, with no rejection or warning in the diff."
     ],
     "preconditions": [
      "An attacker-influenced XML resource comment, extracted by aapt2 and passed to AnnotationProcessor::AppendComment/AppendCommentLine, that contains \"*/\" or a Java Unicode escape.",
      "aapt2 generates R.java for those resources and the generated file is then compiled, per the commit message.",
      "Which build configurations or resource inputs expose this is not shown in the material."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). android-security-14.0.0_r29 has 6/8 lines of the patched region. No fix commit is shown for this branch.",
      "15": "Not affected per the bulletin (not listed). android-security-15.0.0_r17 has 7/8 lines of the patched region. No fix commit is shown for this branch.",
      "16": "Same fix: commit 74f5e6fa6a20 in android-security-16.0.0_r8, with an identical SanitizeJavadoc body, call site and test. Only the hunk context differs (sAnnotationRules has 3 entries, with no @hide rule, and the line offsets differ). 8/8 lines present.",
      "17": "Same fix: commit 28830f292a06 in android-security-17.0.0_r1 (Change-Id I6a566be2d3772bfbf86897f1ccf70bc8c7250e5f, cherry-picked from 863026ecf4f3 / 5874a9196fef). 8/8 lines present."
     },
     "backport_notes": [
      "The change is self-contained: one new static function, one call in AppendCommentLine, and a test, with no header or build changes. It uses std::string_view, which the existing AppendCommentLine code already uses (sDeprecated). The only difference seen between branches is the sAnnotationRules entry count (4 on 17, 3 on 16), which affects only hunk context."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether any other aapt2 code path emits XML comment text into generated Java (e.g. the consumer of AnnotationProcessor::ExtractFirstSentence) without going through AppendCommentLine.",
      "Which attack surface the bulletin's EoP rating refers to, beyond the commit message's statement about generated R.java.",
      "Whether Android 14 and 15 contain any part of this change: the material gives only partial line counts for the patched region and no fix commit for those branches."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55277": {
     "cve": "CVE-2026-55277",
     "summary": "Adds a bounds check in RoutingManager::checkUiccListenConfigNeeded so a new UICC listen configuration is only appended to mNfceeListenConfig.config when nb_config is below NFA_EE_MAX_EE_SUPPORTED, otherwise it logs an error and returns false.",
     "component": "platform/packages/modules/Nfc, NfcNci JNI layer (NfcNci/nci/jni/RoutingManager.cpp, RoutingManager)",
     "weakness": "Missing bounds check leading to out-of-bounds write (array indexed by an unchecked counter)",
     "root_cause": "In NfcNci/nci/jni/RoutingManager.cpp, RoutingManager::checkUiccListenConfigNeeded searches mNfceeListenConfig.config[] for an entry whose nfcee_id matches eeHandle. When no entry is found, it writes nfcee_id and tech_mask into mNfceeListenConfig.config[mNfceeListenConfig.nb_config] and increments nb_config without checking nb_config against NFA_EE_MAX_EE_SUPPORTED, the maximum number of supported execution environments. Per the commit message, exceeding that maximum allowed an out-of-bounds write, potentially corrupting adjacent synchronization primitives and causing crashes or deadlocks in the NFC system service.",
     "fix_analysis": [
      "NfcNci/nci/jni/RoutingManager.cpp:RoutingManager::checkUiccListenConfigNeeded: inside the `if (!found)` branch, before the append, a new check `if (mNfceeListenConfig.nb_config >= NFA_EE_MAX_EE_SUPPORTED)` logs an error with LOG(ERROR) (\"Exceeded max EE supported count\") and returns false. When the limit is reached, the writes to config[nb_config].nfcee_id / .tech_mask and the nb_config++ therefore never execute.",
      "The existing path (entry found, tech_mask updated in place) is unchanged. The `found` loop iterates only up to nb_config, which the new check now keeps bounded at NFA_EE_MAX_EE_SUPPORTED.",
      "The commit message says a unit test was added (atest RoutingManagerTest), but this cherry-pick touches only RoutingManager.cpp (1 file, 5 insertions). The test is not part of this commit as shown."
     ],
     "completeness": [
      "Covers the single append site in checkUiccListenConfigNeeded, which is the only place in the shown context that grows mNfceeListenConfig.nb_config.",
      "When the limit is reached, the function returns false, the same value it returns when no change is made. Apart from the error log, the new EE is not tracked in the listen config, and no other handling (e.g. eviction or replacement) is added.",
      "No other writers of mNfceeListenConfig are shown in the material, so the code provided does not show whether other sites index config[] with an unchecked value."
     ],
     "behavior_changes": [
      "When mNfceeListenConfig already holds NFA_EE_MAX_EE_SUPPORTED entries and an unknown eeHandle arrives, the function now logs an ERROR and returns false instead of writing out of bounds. The new EE's listen config is not recorded.",
      "No change in behavior when the count is below the limit or when the eeHandle is already present."
     ],
     "preconditions": [
      "checkUiccListenConfigNeeded must be called with more distinct eeHandle values than NFA_EE_MAX_EE_SUPPORTED. In the shown context, nb_config is only ever incremented and never reset.",
      "The code runs inside the NFC system service JNI layer (NfcNci). The material does not show which callers supply eeHandle, and per index search no other chunk references the function."
     ],
     "per_major": {
      "14": "not affected per the bulletin; the platform/packages/modules/Nfc project is not present in android-security-14.0.0_r29 (code absent).",
      "15": "not affected per the bulletin; the project is not present in android-security-15.0.0_r17 (code absent).",
      "16": "not affected per the bulletin (Android 16 is not listed; the bulletin does list 16-qpr2). At android-security-16.0.0_r8, 0/2 lines of the patched region are present.",
      "17": "resolved: same fix, commit 8181cc81e300 in android-security-17.0.0_r1 (cherry-pick of b19f65e0b8b0, Change-Id I7d30e4f68b62858056ae2f50cc5676886057ec54); 2/2 patched lines present."
     },
     "backport_notes": [
      "Single-commit, 5-line self-contained change that uses NFA_EE_MAX_EE_SUPPORTED. For a forked RoutingManager.cpp, insert the check in checkUiccListenConfigNeeded's `if (!found)` branch before the first config[nb_config] write. The RoutingManagerTest case mentioned in the commit message is not in this cherry-pick."
     ],
     "confidence": "high",
     "unknowns": [
      "Which callers of checkUiccListenConfigNeeded feed eeHandle, and whether the EE count can be influenced from outside the NFC service (the material does not show the call chain or the trigger).",
      "What the 'adjacent synchronization primitives' are, and the declared size of mNfceeListenConfig.config (the member layout and headers are not shown).",
      "The fix status of the 16-qpr2 branch (listed by the bulletin) and whether the vulnerable code exists at android-security-16.0.0_r8.",
      "The unit test mentioned in the commit message (RoutingManagerTest) is not in this diff; whether it landed separately is not shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55285": {
     "cve": "CVE-2026-55285",
     "summary": "Adds a range check on the channel number byte that the Secure Element returns in the MANAGE CHANNEL OPEN response. The check runs before the byte is used to index mOpenedChannels in openLogicalChannel, across the NXP pn8x and snxxx SecureElement/VirtualISO HAL variants. The fix also makes pn8x closeChannel return early on an invalid channel number.",
     "component": "platform/hardware/nxp/secure_element, NXP Secure Element HAL (pn8x 1.0/1.1 HIDL; snxxx 1.0/1.1/1.2 HIDL and AIDL, SecureElement and VirtualISO)",
     "weakness": "Missing bounds check on an array index taken from an external (Secure Element) response, leading to an out-of-bounds write (CWE-787 / CWE-129)",
     "root_cause": "In each patched openLogicalChannel, when phNxpEse_Transceive succeeded and the response status word was 0x90 0x00, the code took rspApdu.p_data[0] (the channel number assigned by the Secure Element) and used it directly as resApduBuff.channelNumber and as the index in `mOpenedChannels[resApduBuff.channelNumber] = true`. There was no check against the array bound (MAX_LOGICAL_CHANNELS for pn8x, mMaxChannelCount for snxxx; snxxx 1.2 and aidl SecureElement resize mOpenedChannels to mMaxChannelCount) and no check that the value was non-zero. A response carrying an out-of-range byte therefore caused a write of `true` outside mOpenedChannels, which the commit message describes as an intra-object out-of-bounds write. Separately, pn8x closeChannel detected `channelNumber >= MAX_LOGICAL_CHANNELS || mOpenedChannels[channelNumber] == false` but only set sestatus = FAILED instead of returning.",
     "fix_analysis": [
      "pn8x/1.0/SecureElement.cpp and pn8x/1.1/SecureElement.cpp :: SecureElement::openLogicalChannel: the 0x9000 success branch is wrapped in `if (rspApdu.p_data[0] < MAX_LOGICAL_CHANNELS && rspApdu.p_data[0] > 0)`. Only inside that check are channelNumber, mOpenedchannelCount++ and mOpenedChannels[...] = true set, with sestatus = SUCCESS. Otherwise sestatus = SecureElementStatus::CHANNEL_NOT_AVAILABLE, and this else branch does not touch resApduBuff.channelNumber.",
      "pn8x/1.0/SecureElement.cpp and pn8x/1.1/SecureElement.cpp :: SecureElement::closeChannel: the invalid-channel branch (`channelNumber >= MAX_LOGICAL_CHANNELS || mOpenedChannels[channelNumber] == false`) now does `return SecureElementStatus::FAILED;` instead of `sestatus = SecureElementStatus::FAILED;`.",
      "snxxx/1.0, 1.1, 1.2 SecureElement.cpp and VirtualISO.cpp :: openLogicalChannel (HIDL): the same guard, using `rspApdu.p_data[0] < mMaxChannelCount && rspApdu.p_data[0] > 0`. On failure it sets resApduBuff.channelNumber = 0xff and sestatus = SecureElementStatus::CHANNEL_NOT_AVAILABLE.",
      "snxxx/aidl/SecureElement.cpp and snxxx/aidl/VirtualISO.cpp :: openLogicalChannel (AIDL): the same guard against mMaxChannelCount. On failure it sets resApduBuff.channelNumber = -1 and sestatus = ISecureElement::CHANNEL_NOT_AVAILABLE.",
      "After the guard, the existing failure path runs because sestatus != SUCCESS. The code frees the APDU buffers and calls seHalDeInit when no channel is open (pn8x: isSeInitialized() && mOpenedchannelCount == 0; snxxx: mOpenedchannelCount == 0). It then reports the error to the caller, per the surrounding code shown."
     ],
     "completeness": [
      "Covers the MANAGE CHANNEL OPEN success path in the 10 files the commit touches (pn8x 1.0/1.1 SecureElement; snxxx 1.0/1.1/1.2/aidl SecureElement and VirtualISO). The material does not show whether other openLogicalChannel implementations exist in the project.",
      "The channel number is checked against MAX_LOGICAL_CHANNELS for pn8x and mMaxChannelCount for snxxx. snxxx 1.2 and aidl SecureElement show mOpenedChannels.resize(mMaxChannelCount, false) in openLogicalChannel. For snxxx 1.0/1.1 SecureElement and the VirtualISO files, the material does not show how mMaxChannelCount and mOpenedChannels are initialized.",
      "Channel 0 (the basic channel) is also rejected by the `> 0` check, so a success response that claims channel 0 is treated as CHANNEL_NOT_AVAILABLE.",
      "The closeChannel early return was added only to the pn8x variants. This commit does not touch snxxx closeChannel, and the material does not show it.",
      "In the pn8x else branch resApduBuff.channelNumber is not set explicitly (snxxx sets 0xff or -1). The value returned on this path depends on how resApduBuff was initialized, which the material does not show.",
      "The status-word reads `rspApdu.p_data[rspApdu.len - 2]` and `[rspApdu.len - 1]` in the same branch chain still happen without a visible check that rspApdu.len >= 2. The fix does not address this existing pattern.",
      "openBasicChannel and other HAL entry points are not in the diff and are not shown. The fix is limited to openLogicalChannel, plus closeChannel in pn8x."
     ],
     "behavior_changes": [
      "A 0x9000 MANAGE CHANNEL response whose first byte is 0, or is at or above the channel bound, now yields CHANNEL_NOT_AVAILABLE instead of SUCCESS. mOpenedchannelCount is not incremented and no channel is marked open.",
      "On that path the snxxx HIDL variants report channelNumber 0xff and the AIDL variants report -1, which matches the existing 0x6A81 CHANNEL_NOT_AVAILABLE handling in the same functions.",
      "Because this is now a failure path, seHalDeInit may run if no channel is open. That is existing behavior for any non-SUCCESS status.",
      "pn8x closeChannel now returns FAILED immediately when the channel number is invalid or not open."
     ],
     "preconditions": [
      "The device uses the NXP secure_element HAL (pn8x or snxxx, SecureElement or VirtualISO endpoint).",
      "openLogicalChannel is invoked on the HAL. Per index search, it is reached from the framework SecureElement service (packages/apps/SecureElement Terminal.openLogicalChannel), which apps reach through android.se.omapi Session.openLogicalChannel in frameworks/base.",
      "The Secure Element (a compromised SE, per the commit message) answers MANAGE CHANNEL OPEN with status 0x9000 and an out-of-range channel byte.",
      "For the pn8x closeChannel change: a caller passes a channelNumber >= MAX_LOGICAL_CHANNELS, or one that is not open."
     ],
     "per_major": {
      "14": "Not affected (not listed by the bulletin). At android-security-14.0.0_r29, 8/8 lines of the patched region are present. The material does not show how they got there.",
      "15": "Not affected (not listed by the bulletin). At android-security-15.0.0_r17, 8/8 lines of the patched region are present. The material does not show how they got there.",
      "16": "Same fix: commit 466c2e78559e in android-security-16.0.0_r8, with the same changes across the same 10 files. Both snxxx/aidl files have different pre-image blob ids. Only snxxx/aidl/SecureElement.cpp has a different hunk offset (@@ -418 vs @@ -414 on 17). 8/8 patched lines are present.",
      "17": "Fix commit addc064ca4ec in android-security-17.0.0_r1, touching 10 files. It is a cherry-pick of 8ca0d691fa4b (Change-Id I54a5cb16202b49d0929e6306626ef11e2aa329b8). 8/8 patched lines are present."
     },
     "backport_notes": [
      "Google shipped one commit per branch, and each hunk wraps the same 4-line success block locally. Use MAX_LOGICAL_CHANNELS for pn8x and mMaxChannelCount for snxxx, plus the matching invalid value (0xff for HIDL, -1 for AIDL). snxxx/aidl/SecureElement.cpp has a different hunk offset between 16 and 17, so expect small context adjustments there."
     ],
     "confidence": "high",
     "unknowns": [
      "The rest of pn8x closeChannel after the invalid-channel branch is not shown, so what the old fall-through executed with an invalid channelNumber is not visible.",
      "Whether snxxx closeChannel has a similar fall-through (it is not in the diff or the context).",
      "How resApduBuff is initialized in pn8x openLogicalChannel, and therefore what channelNumber the pn8x else branch returns.",
      "How mMaxChannelCount and mOpenedChannels are initialized in snxxx 1.0/1.1 SecureElement and in all VirtualISO variants.",
      "Whether the 10 touched files cover every openLogicalChannel implementation in the project.",
      "Why Android 14 and 15 are not listed by the bulletin even though the patched region is present in their latest security tags."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58823": {
     "cve": "CVE-2026-58823",
     "summary": "Adds length and array-capacity checks in stpropnci_process_std() of the ST NFC HAL, so that RF_NFCEE_DISCOVERY_REQ_NTF and NFCEE_MODE_SET_NTF messages from the NFC controller can no longer write past the fixed-size ee_info and active_nfcee_ids arrays in the global stpropnci_state.",
     "component": "platform/hardware/st/nfc, libstpropnci (libstpropnci-vendor), libstpropnci/src/stpropnci_std.cc",
     "weakness": "Missing bounds check (out-of-bounds write into fixed-size arrays in global state; CWE-787 style)",
     "root_cause": "In stpropnci_process_std(), the NCI_MSG_RF_EE_DISCOVERY_REQ notification handler took the entry count from payload[3]. For every NFCEE id not already stored, it wrote stpropnci_state.ee_info[stpropnci_state.nb_ee_info].nfcee_id and incremented nb_ee_info without checking the capacity of the ee_info array. The only length check was `payloadlen <= NFC_EE_DISCOVER_ENTRY_LEN`, which did not relate payloadlen to the claimed entry count, so the loop could also read entries (payload[6 + i * 5]) beyond the received payload. Similarly, whenever wait_nfcee_ntf was set, the NFCEE_MODE_SET_NTF handler wrote stpropnci_state.active_nfcee_ids[stpropnci_state.nb_active_nfcees] and incremented the counter with no capacity check. Both counters are driven by notifications from the controller. Per the commit message, a compromised NFC controller could push them past the array bounds and overwrite adjacent global state.",
     "fix_analysis": [
      "libstpropnci/src/stpropnci_std.cc:stpropnci_process_std, case NCI_MSG_RF_EE_DISCOVERY_REQ (NTF): the length check changes from `payloadlen <= NFC_EE_DISCOVER_ENTRY_LEN` to `payloadlen < 4 + payload[3] * NFC_EE_DISCOVER_ENTRY_LEN`, so the payload must contain the number of entries it claims. If it does not, the handler calls LOG_E and breaks, and the notification is neither backed up nor parsed.",
      "Same case, inside the per-entry loop when the NFCEE id is new (idx == 0xFF): before `idx = stpropnci_state.nb_ee_info` and the write to ee_info[idx].nfcee_id, a check `nb_ee_info >= sizeof(ee_info)/sizeof(ee_info[0])` logs \"nb_ee_info array is full\" and breaks out of the entry loop. This stops the write and the counter increment once the array is full. Entries already processed in that notification keep their effect; any remaining entries in the same notification are skipped.",
      "Same file, NFCEE_MODE_SET_NTF block (activation path, when stpropnci_state.wait_nfcee_ntf is set): the write `active_nfcee_ids[nb_active_nfcees] = waiting_nfcee_id; nb_active_nfcees++` is wrapped in `if (nb_active_nfcees < sizeof(active_nfcee_ids)/sizeof(active_nfcee_ids[0]))`. Its else branch only logs \"nb_active_nfcees array is full\". The handling that follows (the waiting_nfcee_id == 0x86 case) still runs in both branches."
     ],
     "completeness": [
      "Covers both array writes named in the commit message: ee_info[] in the RF_EE_DISCOVERY_REQ NTF handler and active_nfcee_ids[] in the NFCEE_MODE_SET_NTF handler. Each check is derived from the actual array size (sizeof/sizeof).",
      "The new length check reads payload[3] before confirming payloadlen >= 4. Depending on the value of NFC_EE_DISCOVER_ENTRY_LEN (not shown), the old check may have rejected such short payloads before any indexed access. The material does not show whether callers guarantee a minimum length.",
      "If NFC_EE_DISCOVER_ENTRY_LEN is 5 (matching the i * 5 stride, but not shown), the nfcee_id read payload[6 + i * 5] for i < payload[3] stays within payloadlen under the new check. The rest of the per-entry parsing after line 715 is not shown.",
      "The backup `memcpy(stpropnci_state.last_ee_discovery_req, payload, payloadlen)` in the same handler is unchanged. The material does not show the size of last_ee_discovery_req relative to the maximum payloadlen, so the diff does not show whether this copy needs its own bound.",
      "The fix does not change where nb_ee_info and nb_active_nfcees are reset. Once the arrays are full, new NFCEE ids are dropped with an error log instead of being stored."
     ],
     "behavior_changes": [
      "RF_NFCEE_DISCOVERY_REQ_NTF payloads shorter than 4 + payload[3] * NFC_EE_DISCOVER_ENTRY_LEN are now rejected (logged as \"length too short\") and are not backed up into last_ee_discovery_req. Before the fix, only payloads of NFC_EE_DISCOVER_ENTRY_LEN bytes or fewer were rejected.",
      "When ee_info is full, new NFCEE ids in a discovery notification are no longer recorded. The entry loop stops with LOG_E \"nb_ee_info array is full\".",
      "When active_nfcee_ids is full, an NFCEE_MODE_SET_NTF activation no longer records the id or increments nb_active_nfcees. It logs \"nb_active_nfcees array is full\" and the rest of the handler continues."
     ],
     "preconditions": [
      "The device uses the ST NFC HAL with libstpropnci (hardware/st/nfc). Per index search, stpropnci_process_std is reached from stpropnci_process and stpropnci_inform in libstpropnci/src/stpropnci.cc after stpropnci_init. stpropnci_inform returns early when passthrough_mode is set.",
      "The attacker-controlled input is NCI notifications from the NFC controller. The commit message frames the attacker as a compromised NFC controller.",
      "For the active_nfcee_ids path, stpropnci_state.wait_nfcee_ntf must be set."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. libstpropnci/src/stpropnci_std.cc is absent at android-security-14.0.0_r29 (code absent).",
      "15": "Not affected per the bulletin. The patched file is absent at android-security-15.0.0_r17 (code absent).",
      "16": "Not affected per the bulletin. The patched file is absent at android-security-16.0.0_r8 (code absent).",
      "17": "Same fix. Commit 8a18f3300dd5 (cherry-pick of 95123eb7e942, Change-Id If415e9cff5a3320849be70d0b35a84bd649c8e8e) is in android-security-17.0.0_r1. The resolver reports 7/8 lines of the patched region present, and the code context shown contains the new length check and the nb_ee_info bound."
     },
     "backport_notes": [
      "A single self-contained commit that touches only libstpropnci/src/stpropnci_std.cc. Its sizeof-based checks depend on ee_info and active_nfcee_ids being fixed-size arrays in stpropnci_state. The patched file is absent in the 14, 15 and 16 security tags."
     ],
     "confidence": "high",
     "unknowns": [
      "The sizes of stpropnci_state.ee_info, active_nfcee_ids and last_ee_discovery_req, and the value of NFC_EE_DISCOVER_ENTRY_LEN, are not shown.",
      "Whether callers guarantee payloadlen >= 4 before the handler reads payload[3] is not shown.",
      "Which fields of stpropnci_state sit next to the overflowed arrays is not shown.",
      "The material does not explain why the resolver reports 7/8 rather than 8/8 lines present at android-security-17.0.0_r1. The context shown matches the fix.",
      "The per-entry parsing after line 715 is not shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48564": {
     "cve": "CVE-2025-48564",
     "summary": "On Android 17 the commit linked to this CVE is a test-only change for IntentForwarderActivityTest, plus two Injector hooks in IntentForwarderActivity for the executor and the device-provisioned state. The substantive fix, seen on Android 14/15/16, makes IntentForwarderActivity resolve the intent MIME type once and pass that same resolvedType to both the canForwardTo check and the target resolveActivityAsUser call. It also sanitizes the selector intent before launching the resolver.",
     "component": "platform/frameworks/base, com.android.internal.app.IntentForwarderActivity (cross-profile intent forwarding); on 16 and later also platform/packages/modules/IntentResolver, com.android.intentresolver.IntentForwarderActivity; PackageManager/ApplicationPackageManager API",
     "weakness": "The intent MIME type was resolved twice, separately: once for the cross-profile permission check (IPackageManager.canForwardTo) and once for target activity resolution (resolveActivityAsUser), a check/use mismatch. The selector intent was also not passed through sanitizeIntent in the resolver launch path.",
     "root_cause": "Before the substantive fix, IntentForwarderActivity.canForwardInner called intent.resolveTypeIfNeeded(contentResolver) itself to get the resolvedType it passed to packageManager.canForwardTo. ApplicationPackageManager.resolveActivityAsUser then called intent.resolveTypeIfNeeded(...) again on its own when resolving the forwarded intent in the target user. So the type used to authorize the forward and the type used to resolve the target activity were computed separately. Since resolveTypeIfNeeded can ask a content provider for a URI's type (general platform knowledge), the two values could in principle differ. Separately, the path that forwards intentReceived to the resolver (the code around findSelectedProfile/EXTRA_SELECTED_PROFILE) sanitized only the root intent, not intentReceived.getSelector(). On 14/15, the EXTRA_SKIP_USER_CONFIRMATION privileged-caller check also read the caller package with getCallingPackage() instead of getLaunchedFromPackage(). On Android 17 the linked commit fixes only the test. IntentForwarderActivityTest was excluded from the FrameworksCoreTests_internal_app module. When it did run, it waited 30 s per test on awaitTermination for an executor that was never shut down, and that delay hid race conditions. It also wrote Settings.Global.DEVICE_PROVISIONED directly, lacked HIDE_OVERLAY_WINDOWS (so it crashed when setHideOverlayWindows() was called), and had backwards assertions in the sanitization test (assertNull on objects that must be non-null).",
     "fix_analysis": [
      "Substantive fix (Android 14/15: frameworks/base 'Add resolveActivityAsUserForExplicitType api to pm'; Android 16: IntentResolver 'Use the new packageManager API to forward intents'). In core/java/android/content/pm/PackageManager.java (14/15), two hidden overloads are added: resolveActivityAsUser(Intent, String resolvedType, int flags, int userId), which delegates, and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int), which throws UnsupportedOperationException in the base class. core/java/android/app/ApplicationPackageManager.java implements them: it uses the explicit resolvedType when it is non-null and falls back to intent.resolveTypeIfNeeded(...) only when it is null. The existing overloads now delegate with resolvedType = null.",
      "IntentForwarderActivity.onCreate computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once. It passes that value to canForward(...) and to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). canForward and canForwardInner change their last parameter from ContentResolver to String resolvedType and no longer call resolveTypeIfNeeded. The same resolvedType is used for both canForwardTo checks (root intent and selector). Injector/InjectorImpl gain resolveActivityAsUser(Intent, String, int, int), which calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId).",
      "IntentForwarderActivity resolver launch path: after sanitizeIntent(intentReceived), the fix adds 'if (intentReceived.getSelector() != null) sanitizeIntent(intentReceived.getSelector())'.",
      "On 14/15 only, the callingPackage used in the EXTRA_SKIP_USER_CONFIRMATION privileged-caller check switches from getCallingPackage() to getLaunchedFromPackage().",
      "Callers updated for the new canForward signature: AbstractMultiProfilePagerAdapter (14/15) and IntentResolver emptystate/CrossProfileIntentsChecker.java (16) now pass intent.resolveTypeIfNeeded(mContentResolver).",
      "Android 17 linked commit (frameworks/base 323c8b968a88, 'Fix & speed up IntentForwarderActivityTest'), production side: IntentForwarderActivity.onCreate gets mExecutorService from mInjector.getExecutorService(), and isDeviceProvisioned() delegates to mInjector.isDeviceProvisioned(). InjectorImpl implements both with the previous behavior (Executors.newSingleThreadExecutor(); Settings.Global.DEVICE_PROVISIONED != 0).",
      "Android 17 linked commit, test side: IntentForwarderWrapperActivity.onCreate drops the 30 s awaitTermination. TestInjector supplies a synchronous AbstractExecutorService and isDeviceProvisioned()=true; shouldSkipDisclosure_duringDeviceSetup stubs it to false. The backwards assertNull(capturedIntents.get(i)) checks become assertNotNull. core/tests/coretests/AndroidManifest.xml adds android.permission.HIDE_OVERLAY_WINDOWS. core/tests/coretests/Android.bp removes IntentForwarderActivityTest from the exclude_filters of FrameworksCoreTests_internal_app."
     ],
     "completeness": [
      "The substantive fix uses one resolvedType, derived from the original intentReceived, for both canForwardTo checks (root intent and selector) and for the target resolveActivityAsUser call. The resolver launch path now sanitizes the selector as well as the root intent.",
      "Because resolvedType comes from intentReceived, the selector's canForwardTo check gets that same string, not a type derived from the selector itself. The material does not show whether this matches what the system later uses to resolve the selector.",
      "Other callers of PackageManager.resolveActivityAsUser(Intent, int/ResolveInfoFlags, int) keep the old behavior (type resolved inside ApplicationPackageManager), because the old overloads pass resolvedType = null.",
      "The Android 17 linked commit changes no production security logic. It only makes the sanitization regression test actually run and assert correctly; the old assertNull on capturedIntents.get(i) could never pass alongside the adjacent lines."
     ],
     "behavior_changes": [
      "Substantive fix: the MIME type of a forwarded intent is resolved once in IntentForwarderActivity.onCreate and reused for the canForwardTo checks and for target resolution. PackageManager gains hidden resolveActivityAsUser overloads that take an explicit resolvedType. The selector of intentReceived is sanitized before the resolver is launched. On 14/15 the privileged skip-confirmation check uses getLaunchedFromPackage().",
      "Android 17 linked commit: no observable production change (InjectorImpl keeps newSingleThreadExecutor and the DEVICE_PROVISIONED read). IntentForwarderActivityTest is re-enabled in FrameworksCoreTests_internal_app, and FrameworksCoreTests requests HIDE_OVERLAY_WINDOWS."
     ],
     "preconditions": [
      "An intent reaches IntentForwarderActivity through one of its forwarding components (className FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE). Per IntentForwarderActivity.onCreate in the context, this involves a profile relationship (managed profile or profile parent).",
      "The intent's type is obtained through Intent.resolveTypeIfNeeded, and/or the intent carries a selector intent, so that type resolution and selector sanitization matter."
     ],
     "per_major": {
      "14": "adapted fix: frameworks/base 042b52a8551f in android-security-14.0.0_r24 ('Add resolveActivityAsUserForExplicitType api to pm'). It adds the PackageManager/ApplicationPackageManager overloads, threads resolvedType through IntentForwarderActivity.canForward/canForwardInner/Injector, sanitizes the selector, switches to getLaunchedFromPackage(), and updates AbstractMultiProfilePagerAdapter and the test. At android-security-14.0.0_r29, 6 of 8 lines of the patched region are present.",
      "15": "adapted fix: frameworks/base bf9e58d7edb5 in android-security-15.0.0_r12. Same content as 14, with the surrounding code including the private-profile path (buildAndExecuteForPrivateProfile, singleTabOnly). At android-security-15.0.0_r17, 6 of 8 lines of the patched region are present.",
      "16": "adapted fix: platform/packages/modules/IntentResolver 4e6cf5285d0b in android-security-16.0.0_r3 ('Use the new packageManager API to forward intents'). It applies the resolvedType threading and selector sanitization to com.android.intentresolver.IntentForwarderActivity and updates emptystate/CrossProfileIntentsChecker. It calls PackageManager.resolveActivityAsUser(intent, resolvedType, flags, userId), so it needs those overloads in frameworks/base. At android-security-16.0.0_r8, 8 of 8 lines of the patched region are present.",
      "17": "The linked commit is frameworks/base 323c8b968a88 in android-security-17.0.0_r1: a test fix plus the Injector hooks getExecutorService/isDeviceProvisioned. 8 of 8 lines are present. The test it repairs is the sanitization test added by the substantive fix, which indicates that fix was already in the frameworks/base copy. The substantive Android 17 diff itself is not in the material."
     },
     "backport_notes": [
      "Order: the frameworks/base PackageManager/ApplicationPackageManager resolvedType overloads must land before the IntentForwarderActivity change, and before the IntentResolver change (a separate project) on 16. The Android 17 test commit depends on the substantive fix's test additions (the times(2) canForwardTo capture and the capturedIntents assertions) and changes the Injector interface. Any out-of-tree Injector implementations must therefore add getExecutorService() and isDeviceProvisioned().",
      "Where IntentForwarderActivity exists both in frameworks/base (com.android.internal.app) and in packages/modules/IntentResolver, every copy that is actually in use needs the resolvedType and selector-sanitization change. The 17 commit touches only the frameworks/base copy."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not include the substantive (resolvedType) diff for Android 17, or for the frameworks/base copy on Android 16. Its presence there is inferred from the test content and the per-major line counts.",
      "The material does not show whether the Android 16 IntentResolver commit came with a frameworks/base commit adding the PackageManager overloads on that branch.",
      "The material does not show which IntentForwarderActivity copy (frameworks/base or IntentResolver) is live on each branch.",
      "The material does not describe the concrete trigger or the impact beyond the bulletin rating (High, EoP)."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48565": {
     "cve": "CVE-2025-48565",
     "summary": "The Android 17 commit linked to this CVE is a test follow-up that moves executor creation and the DEVICE_PROVISIONED read in IntentForwarderActivity behind the Injector and re-enables IntentForwarderActivityTest; the substantive fix (seen in the 14/15/16 commits) resolves the intent MIME type once in IntentForwarderActivity.onCreate and passes that same resolvedType to both the canForwardTo check and the target resolveActivityAsUser call, sanitizes the selector in the mini-resolver path, and uses getLaunchedFromPackage() for the privileged skip-consent check.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity, android.app.ApplicationPackageManager, android.content.pm.PackageManager)",
     "weakness": "Check/use inconsistency: the intent MIME type used for the cross-profile forwarding permission check (IPackageManager.canForwardTo) was resolved separately from the type used to resolve the target activity, so the two could differ; additionally the selector was not sanitized on the mini-resolver path and getCallingPackage() was used for a privilege decision.",
     "root_cause": "In IntentForwarderActivity (pre-fix, per the 14/15/16 diffs) canForwardInner() called intent.resolveTypeIfNeeded(contentResolver) itself for the root intent and again for the selector, and the subsequent mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which independently called intent.resolveTypeIfNeeded(mContext.getContentResolver()) before mPM.resolveIntent. Because the type was re-derived at each step rather than fixed once, the type validated by canForwardTo was not guaranteed to be the type used to pick the target activity in the other profile. Separately, in the mini-resolver path (the block that calls startActivityAsCaller(intentReceived, ...)) only the root intentReceived was passed through sanitizeIntent(), not intentReceived.getSelector(), while canForward() did sanitize the selector on the forward copy; and the privilegedCallerAskedToSkipUserConsent decision used getCallingPackage(). The Android 17 commit itself addresses test defects: IntentForwarderWrapperActivity.onCreate awaited termination of an executor that was never shut down (30 s per test), which hid races; the test wrote Settings.Global.DEVICE_PROVISIONED directly; and the test lacked HIDE_OVERLAY_WINDOWS. The test was listed in exclude_filters in core/tests/coretests/Android.bp, and this commit re-enables it.",
     "fix_analysis": [
      "Substantive fix (commit 'Add resolveActivityAsUserForExplicitType api to pm', shown on 14/15/16): core/java/android/content/pm/PackageManager.java adds hidden overloads resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int); core/java/android/app/ApplicationPackageManager.java implements them, passing resolvedType to mPM.resolveIntent and only falling back to intent.resolveTypeIfNeeded when resolvedType is null; the existing overloads delegate with resolvedType = null. On 17, the test pre-image of the linked commit already contains the assertions this commit added, which suggests it is present there, but the 17 production code carrying it is not shown.",
      "IntentForwarderActivity.onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...) and to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). The canForward() and canForwardInner() signatures change from ContentResolver to String resolvedType, and these methods no longer call resolveTypeIfNeeded; the same resolvedType is used for both the root intent and the selector checks. Injector gains resolveActivityAsUser(Intent, String, int, int), implemented via getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId).",
      "IntentForwarderActivity mini-resolver path: after sanitizeIntent(intentReceived), adds 'if (intentReceived.getSelector() != null) sanitizeIntent(intentReceived.getSelector());' before putting EXTRA_SELECTED_PROFILE / EXTRA_CALLING_USER and calling startActivityAsCaller.",
      "IntentForwarderActivity: the callingPackage used for privilegedCallerAskedToSkipUserConsent changes from getCallingPackage() to getLaunchedFromPackage().",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java: updated to the new canForward signature, passing intent.resolveTypeIfNeeded(mContentResolver).",
      "Test coverage in the substantive commit: IntentForwarderActivityTest verifies that canForwardTo is called twice (root and selector) and checks sanitization of both; two of these assertions were written backwards (assertNull on the captured intent).",
      "Android 17 linked commit 323c8b968a88 (test follow-up): in core/java/com/android/internal/app/IntentForwarderActivity.java, onCreate now obtains mExecutorService from mInjector.getExecutorService() (the default impl returns Executors.newSingleThreadExecutor()), and isDeviceProvisioned() delegates to mInjector.isDeviceProvisioned() (the default impl is the unchanged Settings.Global.DEVICE_PROVISIONED read); both methods are added to the Injector interface and InjectorImpl.",
      "Android 17 linked commit, tests: IntentForwarderActivityTest.TestInjector supplies a synchronous AbstractExecutorService and an isDeviceProvisioned() that returns true. The commit removes the 30 s awaitTermination from IntentForwarderWrapperActivity.onCreate and the Settings.Global writes/restores, and changes the backwards assertNull assertions to assertNotNull. shouldSkipDisclosure_duringDeviceSetup stubs sInjector.isDeviceProvisioned() to false. core/tests/coretests/AndroidManifest.xml adds android.permission.HIDE_OVERLAY_WINDOWS, and core/tests/coretests/Android.bp removes IntentForwarderActivityTest from the exclude_filters of FrameworksCoreTests_internal_app."
     ],
     "completeness": [
      "The substantive fix pins one resolvedType for the whole IntentForwarderActivity.onCreate flow: canForwardTo for the root intent, canForwardTo for the selector, and the target resolveActivityAsUser all receive the same string. It does not change how the type is first obtained (still intentReceived.resolveTypeIfNeeded(getContentResolver()) at the start).",
      "Selector sanitization is now applied on both the forward copy (canForward) and the mini-resolver path (startActivityAsCaller of intentReceived).",
      "The Android 17 linked commit changes no production security logic: InjectorImpl.getExecutorService() and InjectorImpl.isDeviceProvisioned() return exactly what the inlined code returned before. Its value is restoring the disabled regression test.",
      "Other ApplicationPackageManager query paths (e.g. the non-resolvedType overloads) still resolve the type internally via intent.resolveTypeIfNeeded when resolvedType is null. The fix only gives callers a way to pass an explicit type, and in the diff only IntentForwarderActivity uses it."
     ],
     "behavior_changes": [
      "IntentForwarderActivity passes the MIME type it validated with canForwardTo to PackageManager.resolveIntent instead of letting ApplicationPackageManager re-resolve it.",
      "Selector intents reaching the mini-resolver are sanitized (same sanitizeIntent as the root intent) before startActivityAsCaller.",
      "The skip-user-confirmation privilege check keys on getLaunchedFromPackage() rather than getCallingPackage().",
      "New hidden PackageManager overloads resolveActivityAsUser(Intent, String, int, int) and (Intent, String, ResolveInfoFlags, int); PackageManager subclasses that do not override the ResolveInfoFlags variant throw UnsupportedOperationException from it.",
      "The Injector interface gains getExecutorService() and isDeviceProvisioned() (17 commit); any other Injector implementation must implement them.",
      "IntentForwarderActivityTest is re-enabled in FrameworksCoreTests_internal_app and no longer blocks 30 s per test or mutates Settings.Global.DEVICE_PROVISIONED."
     ],
     "preconditions": [
      "An intent is delivered to IntentForwarderActivity via the FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE component (for any other class name, onCreate logs Slog.wtf and sets targetUserId to USER_NULL), i.e. a device with a managed profile / profile parent configured for cross-profile forwarding.",
      "The relevant input is the intent's data/type, whose resolved MIME type (intentReceived.resolveTypeIfNeeded) feeds both IPackageManager.canForwardTo and the target resolution; the selector path additionally requires intentReceived.getSelector() != null."
     ],
     "per_major": {
      "14": "adapted fix: 'Add resolveActivityAsUserForExplicitType api to pm' (042b52a8551f) at android-security-14.0.0_r24, resolved by bug_id; includes the resolvedType plumbing, selector sanitization, getLaunchedFromPackage, and the (backwards) test assertions. The 17 test follow-up is only partially reflected (6/8 lines of its region at 14.0.0_r29).",
      "15": "adapted fix: same substantive commit content (bf9e58d7edb5) at android-security-15.0.0_r12, with the private-profile branch (buildAndExecuteForPrivateProfile) present in context; 6/8 lines of the 17 follow-up region at 15.0.0_r17.",
      "16": "adapted fix: same substantive commit content (28579dff4305) at android-security-16.0.0_r3; 6/8 lines of the 17 follow-up region at 16.0.0_r8.",
      "17": "The linked commit is the test follow-up 323c8b968a88 (change_id) at android-security-17.0.0_r1, 8/8 lines present. The pre-image of IntentForwarderActivityTest in this commit already contains the capturedIntents assertions introduced by the substantive commit, which suggests the substantive resolvedType fix is in the 17 tree, but neither the 17 production code nor the 17 commit carrying it is shown in the material."
     },
     "backport_notes": [
      "Apply the substantive commit ('Add resolveActivityAsUserForExplicitType api to pm') first. The 17 test commit depends on it (it corrects assertions that commit introduced) and is otherwise optional for production behaviour. When backporting the substantive commit, PackageManager.java, ApplicationPackageManager.java, IntentForwarderActivity.java and AbstractMultiProfilePagerAdapter.java must move together because canForward's signature changes; any vendor Injector or PackageManager subclass must implement the new methods."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not identify the Android 17 commit that carries the substantive resolvedType fix, and does not show the 17 production code for it; only the test follow-up is linked for 17.",
      "The concrete trigger scenario and why the bulletin rates it High EoP are not shown; the analysis of what was wrong is inferred from the diff only.",
      "Whether the two lines of the 17 follow-up region missing in 14/15/16 security tags are the Injector refactor or test-only lines is not shown.",
      "Why IntentForwarderActivityTest was originally excluded in Android.bp is not stated."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2025-48566": {
     "cve": "CVE-2025-48566",
     "summary": "IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that same resolvedType to both the cross-profile canForwardTo check and the target-user activity resolution, via a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload. It also sanitizes the selector intent before startActivityAsCaller in the selected-profile launch path, and identifies the privileged skip-confirmation caller with getLaunchedFromPackage(). The commit linked for Android 17 is a test-only follow-up that makes the executor and device-provisioned state injectable.",
     "component": "platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity) and the PackageManager resolve API",
     "weakness": "Two flaws, with the same check-versus-use pattern: (1) the intent's resolved data type was derived separately for the cross-profile forwarding check and for the later activity resolution; (2) the selector intent was not fully sanitized. In addition, the privileged-caller check used getCallingPackage().",
     "root_cause": "From the Android 14/15/16 fix diffs:\n- In IntentForwarderActivity.onCreate, the forwarding check (canForward, then canForwardInner) derived the type with intent.resolveTypeIfNeeded(contentResolver) and passed it to IPackageManager.canForwardTo.\n- The later call mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) ends in ApplicationPackageManager.resolveActivityAsUser, which derived the type again on its own with intent.resolveTypeIfNeeded(mContext.getContentResolver()). The new javadoc says the explicit parameter is for when 'the data type contained in the intent cannot be trusted'.\n- The path that adds EXTRA_SELECTED_PROFILE and calls startActivityAsCaller sanitized only the root intentReceived, not intentReceived.getSelector().\n- The EXTRA_SKIP_USER_CONFIRMATION privileged-caller check used getCallingPackage() instead of getLaunchedFromPackage().",
     "fix_analysis": [
      "Substantive fix (commit 'Add resolveActivityAsUserForExplicitType api to pm', shown for 14/15/16). core/java/android/content/pm/PackageManager.java adds two hidden overloads: resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). By default the ResolveInfoFlags variant throws UnsupportedOperationException, and the int variant delegates to it.",
      "core/java/android/app/ApplicationPackageManager.java: resolveActivity(Intent, ResolveInfoFlags), resolveActivityAsUser(Intent, int, int) and resolveActivityAsUser(Intent, ResolveInfoFlags, int) now call resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) with resolvedType null. That method passes resolvedType to mPM.resolveIntent when it is non-null, and falls back to intent.resolveTypeIfNeeded() only when it is null.",
      "core/java/com/android/internal/app/IntentForwarderActivity.java, onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once. It passes this value to canForward(..., resolvedType) and to mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). canForward and canForwardInner now take String resolvedType instead of ContentResolver, and canForwardInner no longer calls intent.resolveTypeIfNeeded. The Injector interface gains resolveActivityAsUser(Intent, String resolvedType, int, int); InjectorImpl implements it with CompletableFuture.supplyAsync around getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId).",
      "IntentForwarderActivity.java, selected-profile launch path: after sanitizeIntent(intentReceived), it now also calls sanitizeIntent(intentReceived.getSelector()) when a selector is present. This runs before EXTRA_SELECTED_PROFILE and EXTRA_CALLING_USER are added and before startActivityAsCaller.",
      "IntentForwarderActivity.java: the privileged skip-user-consent check changes String callingPackage = getCallingPackage() to getLaunchedFromPackage().",
      "core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java: this caller of IntentForwarderActivity.canForward now passes intent.resolveTypeIfNeeded(mContentResolver).",
      "Test follow-up (commit 323c8b968a88, linked on Android 17, Flag: EXEMPT TEST_ONLY). Production code in IntentForwarderActivity.java: onCreate uses mInjector.getExecutorService() instead of Executors.newSingleThreadExecutor(); isDeviceProvisioned() delegates to mInjector.isDeviceProvisioned(); InjectorImpl and the Injector interface gain getExecutorService() and isDeviceProvisioned(). Test changes: removes the 30-second awaitTermination in IntentForwarderWrapperActivity.onCreate; injects a synchronous AbstractExecutorService and stubs isDeviceProvisioned instead of writing Settings.Global; replaces the backwards assertNull checks on captured intents with assertNotNull; adds HIDE_OVERLAY_WINDOWS to the coretests AndroidManifest.xml; removes the IntentForwarderActivityTest exclude filter from core/tests/coretests/Android.bp."
     ],
     "completeness": [
      "Covers the IntentForwarderActivity.onCreate forwarding path. canForwardInner checks both the root intent and the selector with the single precomputed resolvedType, and the same type is used to resolve the activity in the target user.",
      "The added selector sanitization in the startActivityAsCaller path mirrors the sanitization canForward already applies to its copy's selector. The 14/15/16 test meant to verify this contains backwards assertNull asserts, and the test was excluded in Android.bp. Commit 323c8b968a88 corrects the asserts and re-enables the test.",
      "The old Injector.resolveActivityAsUser(Intent, int, int) overload, without resolvedType, is kept in the interface and in InjectorImpl. The diff does not show whether any remaining caller still uses it.",
      "The default PackageManager.resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) throws UnsupportedOperationException. Any PackageManager subclass other than ApplicationPackageManager must therefore override it, or the new call path fails.",
      "The commit linked for Android 17 is test-only. In production code it only moves the executor creation and the DEVICE_PROVISIONED read behind the Injector."
     ],
     "behavior_changes": [
      "New hidden PackageManager overloads: resolveActivityAsUser(Intent, String resolvedType, int, int) and resolveActivityAsUser(Intent, String, ResolveInfoFlags, int). The base implementation throws UnsupportedOperationException unless a subclass overrides it.",
      "IntentForwarderActivity.onCreate resolves the intent type once. Resolution in the target user uses the type that was checked instead of deriving it again.",
      "Selector intents are sanitized in the IntentForwarderActivity path that calls startActivityAsCaller with EXTRA_SELECTED_PROFILE.",
      "The EXTRA_SKIP_USER_CONFIRMATION privileged-caller check now uses getLaunchedFromPackage() instead of getCallingPackage().",
      "Test-only commit: IntentForwarderActivity obtains its ExecutorService and device-provisioned state through the Injector, with production behavior unchanged. IntentForwarderActivityTest is re-enabled in FrameworksCoreTests_internal_app."
     ],
     "preconditions": [
      "An intent is routed through IntentForwarderActivity with component class FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE (cross-profile forwarding), so the device needs a parent or managed profile.",
      "The intent's data type is derived via Intent.resolveTypeIfNeeded, and/or the intent carries a selector. The material does not show the exact triggering conditions.",
      "The bulletin rates this as High severity EoP. Affected versions: 14, 15, 16, 16-qpr2, 17, 17-next."
     ],
     "per_major": {
      "14": "Adapted fix: platform/frameworks/base 042b52a8551f in android-security-14.0.0_r24 ('Add resolveActivityAsUserForExplicitType api to pm'). Same logic as 15/16, but the 14 code has no private-profile or singleTabOnly branches. The patched region of the test-only follow-up is only partly present at android-security-14.0.0_r29 (6 of 8 lines).",
      "15": "Same fix: platform/frameworks/base bf9e58d7edb5 in android-security-15.0.0_r12, with essentially the same content as the 16 commit. The patched region of the test-only follow-up is only partly present at android-security-15.0.0_r17 (6 of 8 lines).",
      "16": "Same fix: platform/frameworks/base 28579dff4305 in android-security-16.0.0_r3. The patched region of the test-only follow-up is only partly present at android-security-16.0.0_r8 (6 of 8 lines).",
      "17": "The commit linked by change_id is 323c8b968a88 in android-security-17.0.0_r1, the test-only follow-up 'Fix & speed up IntentForwarderActivityTest'; all 8 of 8 lines of its region are present. Its pre-image already contains the root/selector capturedIntents assertions added by the substantive fix, which suggests that fix is in the 17 tree. The production code of the substantive change at 17 is not shown."
     },
     "backport_notes": [
      "Backport the substantive commit 'Add resolveActivityAsUserForExplicitType api to pm' first; it touches PackageManager.java, ApplicationPackageManager.java, IntentForwarderActivity.java and AbstractMultiProfilePagerAdapter.java. Commit 323c8b968a88 is test-only and optional. If you take it, it builds on the substantive fix's test code and on the Injector interface, and it includes the AndroidManifest.xml HIDE_OVERLAY_WINDOWS permission and the Android.bp exclude-filter removal.",
      "Any OEM or vendor PackageManager subclass must override the new resolveActivityAsUser(Intent, String, ResolveInfoFlags, int), because the base version throws UnsupportedOperationException. Any custom Injector implementation must add the new resolveActivityAsUser overload (and getExecutorService/isDeviceProvisioned if the test commit is taken)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether the production code at android-security-17.0.0_r1 contains the substantive resolvedType, selector and getLaunchedFromPackage changes. Only the test-only follow-up is shown for 17, though its context suggests the substantive test changes are present.",
      "How the type derived for the check and the type used for resolution could differ in practice (the exact trigger).",
      "Which of the three code changes (type consistency, selector sanitization, getLaunchedFromPackage) is the primary fix for the reported EoP.",
      "Whether any remaining caller uses the retained Injector.resolveActivityAsUser(Intent, int, int) overload without an explicit resolvedType."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28581": {
     "cve": "CVE-2026-28581",
     "summary": "Hardens CallIntentProcessor.doesUserHaveDialerRoleHolder in Telecomm so that a dialer role holder reported by RoleManager for a work profile counts only if PackageManager, queried in that profile's user context, finds the package and reports it enabled.",
     "component": "platform/packages/services/Telecomm, CallIntentProcessor (outgoing call intent handling, work profile initiating-user resolution)",
     "weakness": "Improper validation of a cross-user role query result: the ROLE_DIALER holder list obtained for a work profile was trusted without checking that the package exists and is enabled in that profile.",
     "root_cause": "fixInitiatingUserIfNecessary decides whether a call started from a managed (work) profile should be re-attributed to the parent user when the profile has no dialer. An earlier commit (\"Fix issue initiating calls from the work profile.\") moved this decision to doesUserHaveDialerRoleHolder. That helper returned roleManager.getRoleHolders(RoleManager.ROLE_DIALER).size() > 0 on a context created for the work profile user. The commit message says getting getQualifiedDefaultHolderPackageName from Role used the system user. On Android 14 this could falsely indicate that the default dialer role was active in the work profile even though no matching enabled dialer existed there. The check could therefore return true for a work profile with no usable dialer, so the call was not treated as coming from the parent user.",
     "fix_analysis": [
      "Commit 1 (earlier, 'Fix issue initiating calls from the work profile.'): in src/com/android/server/telecom/CallIntentProcessor.java, fixInitiatingUserIfNecessary stops using DefaultDialerManager.getInstalledDialerApplications(...).size() == 0. It now computes noDialerInstalled = !doesUserHaveDialerRoleHolder(context, initiatingUser). The commit adds the static helper doesUserHaveDialerRoleHolder, which creates a context for the user with context.createContextAsUser(user, 0) and returns whether RoleManager.getRoleHolders(ROLE_DIALER) is non-empty.",
      "Commit 2 (this CVE's commit c1104b6c1bbe, 'Update fix for initiating calls from work profile'): in src/com/android/server/telecom/CallIntentProcessor.java, doesUserHaveDialerRoleHolder now stores the role holder list and returns false if it is null or empty. It takes the first holder (roleHolders.get(0)) and calls userContext.getPackageManager().getApplicationInfo(dialerPackage, 0) in the work profile's user context. It returns info.enabled, or false if PackageManager.NameNotFoundException is thrown. The commit also adds imports for android.content.pm.ApplicationInfo and java.util.List.",
      "Net effect: a role holder counts as present for the work profile only if the package is installed and enabled in that profile. This works around the role query reporting a holder that comes from the system user."
     ],
     "completeness": [
      "Covers the single decision point in CallIntentProcessor.fixInitiatingUserIfNecessary through doesUserHaveDialerRoleHolder. Per index search, this is the only caller visible in the material.",
      "Only the first entry of the role holder list (roleHolders.get(0)) is cross-checked; any further entries are ignored. ROLE_DIALER is normally exclusive, so a single holder is expected (general platform knowledge).",
      "The cross-check only verifies that the package is present and that ApplicationInfo.enabled is true in the profile. It does not verify that the package still meets dialer role requirements.",
      "The fix is a Telecomm-side workaround. This commit does not change the underlying role behaviour described in the commit message (getQualifiedDefaultHolderPackageName using the system user).",
      "Adds a null guard on the getRoleHolders result, which the earlier commit lacked."
     ],
     "behavior_changes": [
      "Consider a call started from a work profile whose RoleManager-reported dialer package is not installed in that profile, or is disabled there. The profile is now treated as having no work dialer, and fixInitiatingUserIfNecessary re-attributes the call to the parent user.",
      "doesUserHaveDialerRoleHolder returns false if getRoleHolders returns null; previously that case would have thrown.",
      "One extra PackageManager.getApplicationInfo query per call intent that comes from a managed profile and has at least one reported ROLE_DIALER holder."
     ],
     "preconditions": [
      "The device has a managed (work) profile, and the call intent's KEY_INITIATING_USER refers to that profile (UserUtil.isManagedProfile returns true).",
      "Call intents reach CallIntentProcessor through the UserCallIntentProcessor trampoline (per the class javadoc in the context).",
      "The role query reports a ROLE_DIALER holder for the profile even though no enabled dialer exists there. The commit message says this happened on Android 14."
     ],
     "per_major": {
      "14": "Same fix, in two steps. The earlier commit 13272bca51ee (android-security-14.0.0_r28) adds doesUserHaveDialerRoleHolder and the RoleManager-based check. On 14, fixInitiatingUserIfNecessary has no FeatureFlags parameter and uses UserManager.get(context). The patched region (8/8 lines) is present at android-security-14.0.0_r29.",
      "15": "Same fix, in two steps. The earlier commit 34079b659664 (android-security-15.0.0_r16) adds the RoleManager-based helper. On 15, fixInitiatingUserIfNecessary takes FeatureFlags and uses featureFlags.telecomResolveHiddenDependencies(). The patched region (8/8 lines) is present at android-security-15.0.0_r17.",
      "16": "Same fix, in two steps. The earlier commit 510bcce16c24 (android-security-16.0.0_r7) adds the RoleManager-based helper. The patched region (8/8 lines) is present at android-security-16.0.0_r8.",
      "17": "Same fix: commit c1104b6c1bbe (the update) is in android-security-17.0.0_r1, with the full patched region present (8/8 lines). The diff shows the earlier helper was already in place there."
     },
     "backport_notes": [
      "Two-commit sequence. First apply 'Fix issue initiating calls from the work profile.', which adds doesUserHaveDialerRoleHolder and replaces the DefaultDialerManager.getInstalledDialerApplications check. Then apply 'Update fix for initiating calls from work profile', which adds the PackageManager enabled cross-check. The second commit's import hunk may need adapting: for example, the Android 14 imports shown in the material do not include android.content.pm.PackageManager, which the new code needs for NameNotFoundException.",
      "fixInitiatingUserIfNecessary differs by branch. On 14 it has no FeatureFlags parameter and uses UserManager.get(context); on 15 and 16 it takes FeatureFlags and uses telecomResolveHiddenDependencies(). The helper body is identical across 14, 15 and 16."
     ],
     "confidence": "high",
     "unknowns": [
      "What information is disclosed (bulletin type ID) and the attack scenario. The material only shows that a work profile could wrongly be treated as having a dialer role holder.",
      "Whether the role issue (getQualifiedDefaultHolderPackageName using the system user) was fixed separately in the role component.",
      "The full body of fixInitiatingUserIfNecessary at Android 17 is not in the material; only the 14, 15 and 16 versions are shown.",
      "The commit hashes of the update commit on 14, 15 and 16 are not in the material; only the presence of the patched region is shown."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28582": {
     "cve": "CVE-2026-28582",
     "summary": "ConfirmDeviceCredentialActivity.onCreate now rejects any ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL launch whose intent component class name is not the alias com.android.settings.ConfirmRemoteDeviceCredentialActivity: it logs a warning and calls finish(). Callers can therefore no longer reach the remote-credential flow by targeting the underlying activity directly.",
     "component": "platform/packages/apps/Settings, password/ConfirmDeviceCredentialActivity (remote device credential confirmation)",
     "weakness": "Missing authorization: the permission check on an activity-alias could be bypassed by launching the target activity directly with the same action",
     "root_cause": "ConfirmDeviceCredentialActivity.onCreate chose the remote validation path for KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL from intent.getAction() alone. That path is meant to be entered through the alias com.android.settings.ConfirmRemoteDeviceCredentialActivity, which the fix comment ties to permission checks (\"Remote validation must use the alias to avoid bypassing permission checks\"). Before the fix, the activity did not check which component had been launched. A caller that explicitly addressed ConfirmDeviceCredentialActivity with the remote-validation action therefore reached the same code path without going through the alias. The InternalActivity subclass, which inherits onCreate, would also be rejected by the new check, which suggests it was a second way in. The bulletin rates the impact as information disclosure (ID), High.",
     "fix_analysis": [
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: adds the constant CONFIRM_REMOTE_DEVICE_CREDENTIAL_ACTIVITY_ALIAS = \"com.android.settings.ConfirmRemoteDeviceCredentialActivity\".",
      "ConfirmDeviceCredentialActivity.onCreate: the new check sits right after `remoteValidation` is computed (true when the action equals KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL) and before mTaskOverlay is set. On Android 15 to 17, prepareRepairMode is also set after it. When remoteValidation is true, the code reads intent.getComponent(). If the component is null or its getClassName() does not equal the alias, it logs \"Caller bypassing alias for action: ...\" with Log.w, calls finish() and returns. The rest of onCreate does not run.",
      "tests/robotests/.../ConfirmDeviceCredentialActivityTest.java (new file): onCreate_remoteValidationWithAlias_doesNotFinish launches with component (com.android.settings, alias) and asserts the activity is not DESTROYED. onCreate_remoteValidationWithoutAlias_finishes launches with component ConfirmDeviceCredentialActivity.class.getName() and asserts DESTROYED.",
      "tests/robotests/testutils/.../ShadowLockPatternUtils.java: adds a getCredentialTypeForUser shadow that returns CREDENTIAL_TYPE_PIN when the user is marked secure and CREDENTIAL_TYPE_NONE otherwise. This is a test-only change."
     ],
     "completeness": [
      "The check applies only when the action is ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL. This commit does not change how other actions handled in the same onCreate are processed.",
      "The check is in ConfirmDeviceCredentialActivity.onCreate. The InternalActivity subclass in the same file has an empty body, so it inherits onCreate and the check.",
      "The check compares only ComponentName.getClassName() against the alias string. The package name is not compared.",
      "The fix adds a gate inside the target activity. The diff does not touch the manifest, so the alias's permission enforcement is not visible in the material.",
      "A null component is treated as a bypass and rejected on the remote-validation path."
     ],
     "behavior_changes": [
      "Launching ConfirmDeviceCredentialActivity (or InternalActivity) directly with ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL now logs a Log.w warning (\"Caller bypassing alias for action: ...\") and immediately finishes the activity instead of continuing the flow. The new code does not call setResult.",
      "Launches through the alias com.android.settings.ConfirmRemoteDeviceCredentialActivity pass the new check.",
      "The Robolectric ShadowLockPatternUtils now implements getCredentialTypeForUser. This is test-only and may affect other Settings robotests that use this shadow."
     ],
     "preconditions": [
      "An intent with action KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL is delivered to ConfirmDeviceCredentialActivity (or its InternalActivity subclass) with a component other than the ConfirmRemoteDeviceCredentialActivity alias. The material does not show the target activity's manifest exported or permission attributes, so it does not show who could launch it directly.",
      "The Robolectric tests mark the user as secure with a PIN credential and set a ManagedSubscriptionsPolicy. These are test setup steps, not necessarily preconditions of the issue."
     ],
     "per_major": {
      "14": "Adapted fix, marked RESTRICT AUTOMERGE (Settings 4b69be85e55d in android-security-14.0.0_r29). Same alias constant and onCreate check, inserted after the non-final `boolean frp` and `boolean remoteValidation` declarations, with no prepareRepairMode in the surrounding code. The robotest and ShadowLockPatternUtils changes are not part of this commit. 7/7 lines of the patched region are present.",
      "15": "Same fix, marked RESTRICT AUTOMERGE (Settings 839f92783965 in android-security-15.0.0_r17), including the new ConfirmDeviceCredentialActivityTest and ShadowLockPatternUtils.getCredentialTypeForUser. 8/8 lines of the patched region are present.",
      "16": "Same fix, marked RESTRICT AUTOMERGE (Settings 3ee7e15a126f in android-security-16.0.0_r8), including the test and the shadow. 8/8 lines of the patched region are present.",
      "17": "Same fix, cherry-picked from 4e2cf5e22953 (Settings 642944e4c4e6 in android-security-17.0.0_r1), including the test and the shadow. 8/8 lines of the patched region are present."
     },
     "backport_notes": [
      "One self-contained hunk in ConfirmDeviceCredentialActivity.onCreate plus one constant. It relies on the existing `remoteValidation` boolean and needs the android.content.ComponentName and android.util.Log imports; check that both are present on the target branch. The Android 14 fix ships without the test changes.",
      "Check that the target branch's AndroidManifest declares the activity-alias com.android.settings.ConfirmRemoteDeviceCredentialActivity pointing at this activity. Otherwise the check will reject all remote-validation launches."
     ],
     "confidence": "high",
     "unknowns": [
      "The manifest definitions of the ConfirmRemoteDeviceCredentialActivity alias and of ConfirmDeviceCredentialActivity (exported state, permission) are not in the material, so the exact permission that was bypassed is not shown.",
      "The material does not show what information the remote validation flow exposes; the bulletin only says ID.",
      "The material does not show whether the component delivered to onCreate carries the alias name when the activity is reached through implicit intent resolution to the alias. The tests only cover explicit component launches.",
      "The material does not show whether leaving out the package-name comparison matters.",
      "The material does not show why the Android 14 fix omits the test changes."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28584": {
     "cve": "CVE-2026-28584",
     "summary": "PackageInstallerService now deduplicates SessionParams.whitelistedRestrictedPermissions (rebuilding the list from a HashSet) before intersecting it with the platform restricted permission set, so the list can no longer be made arbitrarily long with repeated valid entries.",
     "component": "platform/frameworks/base, PackageManager / PackageInstallerService (install session parameters)",
     "weakness": "Missing input sanitization leading to unbounded resource consumption (no deduplication / no size bound on a caller-supplied list)",
     "root_cause": "In services/core/java/com/android/server/pm/PackageInstallerService.java, the only check on params.whitelistedRestrictedPermissions was retainAll(mPm.getAllPlatformRestrictedPermissions()). retainAll removes unknown permission names but keeps every duplicate of a valid name, so the caller controlled the length of the list. The field is a hidden API, but the commit message says it can be set directly through reflection. A caller could therefore supply a very large list of repeated valid restricted permission names that passed this check. The regression test is named SessionSizeLimitTest, which suggests the impact is growth in session size; the bulletin rates it DoS.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java, inside the `if (params.whitelistedRestrictedPermissions != null)` block (around line 1081): before the existing retainAll, the list is replaced with `new ArrayList<>(new HashSet<>(params.whitelistedRestrictedPermissions))`, which removes all duplicates.",
      "The existing `retainAll(mPm.getAllPlatformRestrictedPermissions())` is kept and now runs on the deduplicated list, so the result can be no longer than the number of distinct platform restricted permissions.",
      "Adds `import java.util.HashSet;`. Single-commit fix, 3 lines added."
     ],
     "completeness": [
      "Covers only SessionParams.whitelistedRestrictedPermissions in this validation block of PackageInstallerService.java. After the fix the list can be no longer than the number of distinct platform restricted permissions.",
      "The dedup replaces the list object (new ArrayList) instead of changing it in place. Entry order is not preserved (HashSet iteration order).",
      "The fix adds no explicit size limit; the bound comes from retainAll against the platform set. This commit does not touch the other list or collection fields of SessionParams, and the material does not show whether they have comparable validation."
     ],
     "behavior_changes": [
      "Duplicate entries in whitelistedRestrictedPermissions are silently collapsed; no error is returned.",
      "After validation, the entries in params.whitelistedRestrictedPermissions are no longer in the caller's order.",
      "params.whitelistedRestrictedPermissions is replaced with a new ArrayList instance rather than mutated in place."
     ],
     "preconditions": [
      "The caller must be able to create a PackageInstaller session and populate SessionParams.whitelistedRestrictedPermissions. The field is hidden, but the commit says it can be set through reflection.",
      "Only entries that are valid platform restricted permission names survive retainAll, so the list could grow only through repeated valid names."
     ],
     "per_major": {
      "14": "not affected per the bulletin (not listed). At android-security-14.0.0_r29, 6/8 lines of the patched region are present, consistent with the block existing without the dedup lines.",
      "15": "not affected per the bulletin (not listed). At android-security-15.0.0_r17, 6/8 lines of the patched region are present, consistent with the block existing without the dedup lines.",
      "16": "not affected per the bulletin (not listed). At android-security-16.0.0_r8, 6/8 lines of the patched region are present, consistent with the block existing without the dedup lines.",
      "17": "same fix: 89e9c4b37577 in android-security-17.0.0_r1 (cherry-pick of 174693f1586b), 8/8 lines present."
     },
     "backport_notes": [
      "The patch is self-contained: one hunk plus a HashSet import. Per the per-major facts, most of the patched region (6/8 lines) is present in the 14/15/16 security tags, so a fork that wants the hardening should be able to apply it with little adaptation. Add SessionSizeLimitTest if it exists on the branch."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show the downstream consumer where the oversized list causes the DoS (persisted session XML, memory, or IPC). Only the test name SessionSizeLimitTest hints at session size.",
      "The enclosing method name is not visible in the provided context.",
      "The material does not explain why 14/15/16 are treated as not affected when they contain the same unpatched block. It also does not explain why the bulletin's affected list includes 16-qpr2 while Android 16 is not listed."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28593": {
     "cve": "CVE-2026-28593",
     "summary": "The Live TV app's \"Send Feedback\" settings action now builds an explicit intent targeting com.google.android.feedback/.FeedbackActivity instead of an implicit Intent.ACTION_APP_ERROR intent, so the intent can no longer resolve to an arbitrary app that registered for that action.",
     "component": "platform/packages/apps/TV (Live TV), settings side panel (com.android.tv.ui.sidepanel.SettingsFragment)",
     "weakness": "Implicit intent used for a sensitive, app-internal operation (unintended intent receiver)",
     "root_cause": "In SettingsFragment, the \"Send Feedback\" ActionItem.onSelected() built `new Intent(Intent.ACTION_APP_ERROR)` with no package or component set, then created an ApplicationErrorReport with packageName/processName set to the TV app's package and a timestamp. Because the intent was implicit, any installed app declaring an intent filter for ACTION_APP_ERROR could be resolved as the target. The commit message gives the purpose of the fix as \"To avoid opening malicious apps\".",
     "fix_analysis": [
      "src/com/android/tv/ui/sidepanel/SettingsFragment.java: adds the constants FEEDBACK_PACKAGE = \"com.google.android.feedback\" and FEEDBACK_CLASS = \"com.google.android.feedback.FeedbackActivity\" and imports android.content.ComponentName.",
      "src/com/android/tv/ui/sidepanel/SettingsFragment.java:ActionItem.onSelected() (settings_send_feedback item): replaces `new Intent(Intent.ACTION_APP_ERROR)` with `new Intent().setComponent(new ComponentName(FEEDBACK_PACKAGE, FEEDBACK_CLASS))`, which makes the intent explicit so only the named feedback activity can be resolved. The visible lines that build the ApplicationErrorReport are unchanged."
     ],
     "completeness": [
      "Covers the single implicit ACTION_APP_ERROR intent in the \"Send Feedback\" action of SettingsFragment. The intent now has an explicit component and no action string.",
      "The fix hardcodes a Google-specific feedback component. The diff shows no check that the component exists and no fallback, so the material does not show what happens on devices without com.google.android.feedback.",
      "This commit touches no other call sites in packages/apps/TV. The material does not show whether other implicit intents exist elsewhere in the app."
     ],
     "behavior_changes": [
      "\"Send Feedback\" now targets com.google.android.feedback.FeedbackActivity explicitly, so third-party apps registering for ACTION_APP_ERROR are no longer resolved.",
      "The intent no longer carries the ACTION_APP_ERROR action string, only the component.",
      "On builds without the Google feedback package the explicit intent will not resolve. How the unchanged launch path handles that is not shown in the diff."
     ],
     "preconditions": [
      "The Live TV app is installed and its settings side panel is reachable; the user selects the \"Send Feedback\" action item (SettingsFragment, settings_send_feedback).",
      "Before the fix: another installed app declares an intent filter for Intent.ACTION_APP_ERROR, so the implicit intent resolves to it."
     ],
     "per_major": {
      "14": "same fix: identical diff applied as commit 2e9c27acedc2 in android-security-14.0.0_r29 (8/8 patched lines present).",
      "15": "same fix: identical diff applied as commit 7e516e62a48d in android-security-15.0.0_r17 (8/8 patched lines present).",
      "16": "same fix: identical diff applied as commit d688b27298d4 in android-security-16.0.0_r8 (8/8 patched lines present).",
      "17": "same fix: commit 724154d5db03 in android-security-17.0.0_r1 (cherry-pick of 22867ae71e25), 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained Java change with the same file blob (762a190c -> 1ac5d81e) on all four branches. It should apply cleanly to any tree with an unmodified SettingsFragment. Forks that ship a different feedback app would need to put in their own explicit ComponentName (or setPackage) rather than go back to an implicit intent."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the rest of onSelected(): whether the report is attached to the intent, how the intent is started, and whether a failure to resolve is handled.",
      "The material does not show the TV app's privilege level or what data a resolved receiver would get.",
      "The material does not show whether other implicit intents exist in packages/apps/TV."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28594": {
     "cve": "CVE-2026-28594",
     "summary": "Makes MemoryIntArray pass one explicit element count from Java to both nativeOpen and nativeClose so mmap and munmap always use the same length instead of re-querying the fd's ashmem size, and hardens libcutils ashmem_valid() so a memfd is only accepted as an ashmem replacement when it carries both F_SEAL_GROW and F_SEAL_SHRINK.",
     "component": "platform/frameworks/base, android.util.MemoryIntArray (Java + core/jni/android_util_MemoryIntArray.cpp); platform/system/core, libcutils ashmem-dev.cpp",
     "weakness": "Time-of-check to time-of-use on the size of a shared-memory fd: mmap and munmap lengths were derived independently from the fd, so a size change (or a different fd) between open and close yields a munmap with the wrong length (memory-safety / unmapping of adjacent mappings).",
     "root_cause": "In core/jni/android_util_MemoryIntArray.cpp, android_util_MemoryIntArray_open called ashmem_get_size_region(fd) to choose the mmap length, and android_util_MemoryIntArray_close called ashmem_get_size_region(fd) again to choose the munmap length. Nothing tied the two values together. If the size reported for the fd was different at close time (the fd's backing region changed size, or a different fd was in mFd), munmap(ashmemAddr, newSize) unmapped a range that did not match the original mapping. Separately, libcutils is_memfd_fd() (used by ashmem_valid()) accepted any fd whose /proc/self/fd link started with \"/memfd:\", regardless of file seals. A memfd whose size could still change was therefore treated as a valid ashmem fd, which the commit message says opens time-of-check to time-of-use issues.",
     "fix_analysis": [
      "platform/frameworks/base core/java/android/util/MemoryIntArray.java: both constructors now compute mSize = nativeSize(mFd) first and pass it to nativeOpen(mFd, mIsOwner, mSize). close() passes mSize to nativeClose(mFd, mMemoryAddr, mSize). The native declarations change to nativeOpen(int, boolean, int) and nativeClose(int, long, int), with a comment requiring the same size for both.",
      "platform/frameworks/base core/jni/android_util_MemoryIntArray.cpp: android_util_MemoryIntArray_open and android_util_MemoryIntArray_close take a jint size and compute ashmemSize = arraySize(size) (sizeof(std::atomic_int) * size) instead of calling ashmem_get_size_region(fd). The existing 'bad ashmem size' check on ashmemSize <= 0 is kept. The owner-path placement-new now uses the passed size. The new helpers arraySize/arrayNrElms are used by create and size. JNI registration is updated to \"(IZI)J\" and \"(IJI)V\".",
      "platform/system/core libcutils/ashmem-dev.cpp: after the \"/memfd:\" prefix check, is_memfd_fd() now calls fcntl(fd, F_GET_SEALS). It returns false if that call fails or if F_SEAL_GROW and F_SEAL_SHRINK are not both set, and only otherwise returns true. Because ashmem_valid() returns 1 when is_memfd_fd() is true, unsealed memfds are no longer treated as valid ashmem fds.",
      "platform/system/core libcutils/ashmem_test.cpp: adds AshmemValidTest.UnsealedMemfdTest, which asserts that ashmem_valid() is false for an unsealed memfd and for memfds with only one of the two seals.",
      "platform/frameworks/base core/tests/utiltests: adds testMmapAndMunmapSizeConsistency plus nativeMremap/nativeIsRangeMapped/nativeMunmap helpers. The test swaps in a larger fd before close() and checks that only the original page is unmapped and that an adjacent probe page survives."
     ],
     "completeness": [
      "Covered: the munmap length in android_util_MemoryIntArray_close is now fixed at open time through the Java-held mSize. Neither a size change on the fd nor a different fd in mFd can change the unmapped range.",
      "Covered: libcutils is_memfd_fd(), and so ashmem_valid(), rejects memfds that are not sealed against both growing and shrinking. Which other libcutils entry points depend on is_memfd_fd() is not shown in the material.",
      "Not changed: mSize is still obtained once from nativeSize(mFd) (ashmem_get_size_region) in both constructors, including the Parcel constructor that receives the fd from another process. The mmap length now comes from that single query, but the array length itself is still provided by the fd.",
      "Not changed: nativeGet/nativeSet and the Java-side bounds handling are not in the diff, and the material does not show them.",
      "Only is_memfd_fd() changed in ashmem-dev.cpp. The legacy /dev/ashmem character-device path (__ashmem_is_ashmem) is untouched by this fix."
     ],
     "behavior_changes": [
      "ashmem_valid() now returns false for memfds lacking F_SEAL_GROW and F_SEAL_SHRINK, or when F_GET_SEALS fails (logged with ALOGE). Previously ashmem_valid() accepted any memfd.",
      "MemoryIntArray native method signatures changed (nativeOpen (IZI)J, nativeClose (IJI)V). Java and JNI must be updated together.",
      "Constructor ordering changed: nativeSize(mFd) is now called before nativeOpen, so a size-query failure happens before any mapping is created.",
      "nativeOpen/nativeClose now throw IOException 'bad ashmem size' when the Java-provided size gives a non-positive byte length, rather than when the fd's queried size is non-positive."
     ],
     "preconditions": [
      "A process constructs a MemoryIntArray from a Parcel-supplied fd (MemoryIntArray(Parcel) via pfd.detachFd()), or otherwise holds an fd whose reported ashmem size can differ between nativeOpen and nativeClose, and later calls close().",
      "For the libcutils issue: a memfd without both F_SEAL_GROW and F_SEAL_SHRINK is passed to code that trusts ashmem_valid().",
      "Which system services or processes deserialize MemoryIntArray from untrusted parcels is not shown in the material."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. 6/8 lines of the patched region are present at android-security-14.0.0_r29; no fix is recorded.",
      "15": "Not affected per the bulletin. 8/8 lines of the patched region are present at android-security-15.0.0_r17; no fix is recorded.",
      "16": "Not affected per the bulletin for the android-security-16.0.0 line (16-qpr2 is listed separately). 8/8 lines of the patched region are present at android-security-16.0.0_r8; no fix is recorded.",
      "17": "Same fix: resolved via bug_id at android-security-17.0.0_r1 with frameworks/base a44880c82aac plus system/core 6297cab2c921 and 2de25eeaec55 (all cherry-picks with Merged-In)."
     },
     "backport_notes": [
      "The fix has two independent parts. (a) frameworks/base MemoryIntArray.java and android_util_MemoryIntArray.cpp must land together because the JNI signatures change; the test files are optional. (b) The system/core libcutils ashmem-dev.cpp is_memfd_fd() seal check only matters on branches where ashmem_valid() accepts memfds via is_memfd_fd(). Before porting, check that this helper exists on the target branch and that its fcntl headers provide F_GET_SEALS/F_SEAL_*."
     ],
     "confidence": "medium",
     "unknowns": [
      "Which privileged processes accept MemoryIntArray parcels from less-privileged callers. The index search returned only generic close() callers, nothing specific.",
      "Why Android 14, 15 and 16.0 are not listed as affected. The per-major line counts alone do not show whether the vulnerable code is present there.",
      "Whether libcutils ashmem entry points other than ashmem_valid() call is_memfd_fd() and therefore also get the seal requirement.",
      "Whether nativeGet/nativeSet have Java-side bounds checks against mSize (not in the diff)."
     ],
     "meta": {
      "generated": "2026-10-01",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28599": {
     "cve": "CVE-2026-28599",
     "summary": "ActivityManagerService.addCreatorToken no longer treats intent.getPackage() as the target package when the outer intent has no explicit component. As a result, nested intents inside a package-constrained intent that carries a selector targeting another app now get creator tokens instead of being skipped as \"same app\".",
     "component": "platform/frameworks/base, ActivityManagerService (intent redirect hardening / IntentCreatorToken)",
     "weakness": "Incorrect authorization decision based on an incomplete target determination: the intent selector was not checked when deciding to skip creator tokens",
     "root_cause": "In services/core/java/com/android/server/am/ActivityManagerService.java, addCreatorToken computes targetPackage to decide isCreatorSameAsTarget. When that is true, creator tokens are omitted for all nested intents. When no explicit component was set, the old code fell back to intent.getPackage(). The caller controls the package constraint, and it does not reflect where the intent is actually routed. Per the commit message, an intent with its package set to the creator plus a selector whose explicit component targets another app is routed to the selector's component (ComputerEngine). addCreatorToken still judged that intent as staying in the creator's app and attached no tokens to the embedded intents.",
     "fix_analysis": [
      "ActivityManagerService.java:addCreatorToken: the targetPackage expression changes from `intent.getComponent() != null ? intent.getComponent().getPackageName() : intent.getPackage()` to `... : null`. With no explicit component, targetPackage is null, so creatorPackage.equals(null) is false and isCreatorSameAsTarget is false. forEachNestedCreatorToken then calls createIntentCreatorToken/setCreatorToken for every nested intent.",
      "Note: the commit message describes a selector-aware calculation (outer component first, then selector component or package, then outer package). The merged diff does not implement that. It simply drops the getPackage() fallback, which is stricter: the same-app shortcut now applies only when the outer intent names an explicit component in the creator's package."
     ],
     "completeness": [
      "Covers the described bypass: any outer intent without an explicit component (including one carrying a selector) now always gets creator tokens on its nested intents, whatever setPackage() says.",
      "The same-app shortcut still applies when the outer intent has an explicit component whose package equals creatorPackage, and the selector is still not inspected in that branch. In the platform resolver the selector is only consulted when the outer intent has no component, so an explicit outer component takes precedence over the selector (general platform knowledge). The material itself does not demonstrate this.",
      "The whole method is gated by preventIntentRedirect(). Nothing changes when that flag is off.",
      "Single-line, single-file change. The commit includes no tests (Test: manual PoC verification)."
     ],
     "behavior_changes": [
      "Intents from app A that constrain the target only via setPackage(A) (no explicit component) and contain nested intents now go through token creation for those nested intents. Previously the same-app shortcut skipped them.",
      "The early-return log (INTENT_CREATOR_TOKEN_ADDED with true) is no longer written for those intents. When a token is actually created, INTENT_CREATOR_TOKEN_ADDED is logged with false.",
      "No new error or exception path. The only change is stricter token attachment."
     ],
     "preconditions": [
      "preventIntentRedirect() feature flag enabled (otherwise addCreatorToken returns immediately).",
      "An intent passes through addCreatorToken with nested Intents in its extras that forEachNestedCreatorToken visits.",
      "The outer intent has no explicit component, its getPackage() is set to the creator's own package, and it carries a selector that targets another app's component (per the commit message)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. Only 2/5 lines of the patched region are present at android-security-14.0.0_r29.",
      "15": "Not affected per the bulletin. Only 2/5 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "Same fix: platform/frameworks/base 5108d2606324 in android-security-16.0.0_r8. It is the identical one-line change (getPackage() fallback replaced by null) at a different line offset (hunk at ~19459). 5/5 lines present.",
      "17": "Same fix: platform/frameworks/base ee7ea482d39e in android-security-17.0.0_r1 (line ~21099). 5/5 lines present."
     },
     "backport_notes": [
      "Apply the diff, not the commit message. The merged change is just `: intent.getPackage()` -> `: null` in addCreatorToken, and no selector-handling code needs to be ported.",
      "Relevant on branches that already have addCreatorToken and preventIntentRedirect() (the bulletin lists 16, 16-qpr2, 17, 17-next). On 14 and 15 only part of the region is present, and the bulletin does not list them."
     ],
     "confidence": "high",
     "unknowns": [
      "Which callers invoke addCreatorToken (for example which startActivity, broadcast or service paths). The material does not show this.",
      "How the two Cherrypick-From internal commits (dc03f183, acbde95f) relate, and whether either contained changes not present in the AOSP diff.",
      "Default state of the preventIntentRedirect() flag on affected releases.",
      "Whether addCreatorToken exists in any form on 14 and 15 (only 2/5 lines of the region are present)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28600": {
     "cve": "CVE-2026-28600",
     "summary": "PaymentDefaultDialog.onCreate no longer reads a UserHandle from the Intent.EXTRA_USER extra; the userId passed to buildDialog is now always UserHandle.myUserId().",
     "component": "platform/packages/apps/Settings, NFC payment settings (src/com/android/settings/nfc/PaymentDefaultDialog.java)",
     "weakness": "Trust of a caller-supplied user identifier from an activity intent extra (improper input validation / confused deputy leading to cross-user action)",
     "root_cause": "PaymentDefaultDialog.onCreate took a UserHandle from getIntent().getParcelableExtra(Intent.EXTRA_USER). If the extra was present, it passed userHandle.getIdentifier() as the userId argument to buildDialog(component, category, userId). On BUTTON_POSITIVE, onClick calls mBackend.setDefaultPaymentApp(mNewDefault.componentName, mNewDefault.userId). This means whoever launched the activity controlled the user id given to the dialog-building logic, not the Settings process. The material does not show how mNewDefault.userId is derived inside buildDialog.",
     "fix_analysis": [
      "src/com/android/settings/nfc/PaymentDefaultDialog.java:onCreate: the read of Intent.EXTRA_USER and the userHandle null-check branch are deleted. userId is now always set to UserHandle.myUserId(), the user the Settings activity runs as.",
      "As a result, buildDialog(component, category, userId) always receives the current user's id. No other call sites changed.",
      "The commit title says dialog building logic was removed from onCreate, but the diff only removes the EXTRA_USER handling. buildDialog is still called from onCreate at android-security-17.0.0_r1."
     ],
     "completeness": [
      "Intent.EXTRA_USER is now ignored entirely in onCreate. Whether any other caller-controlled user id path exists in the rest of buildDialog is not shown.",
      "CardEmulation.EXTRA_SERVICE_COMPONENT and CardEmulation.EXTRA_CATEGORY are still taken from the intent. Whether buildDialog validates the component against the current user's installed services is not shown in the material.",
      "The fix leaves an existing pattern in onCreate untouched: when new PaymentBackend(this) throws a NullPointerException, finish() is called but execution continues into getIntent()/buildDialog.",
      "No other files or activities are modified, and the material shows no sibling dialog with the same EXTRA_USER pattern."
     ],
     "behavior_changes": [
      "Any Intent.EXTRA_USER extra passed to PaymentDefaultDialog is silently ignored, and buildDialog always receives the user id of the user the Settings activity runs as.",
      "No new error, result code, or log message is added. The RESULT_CANCELED default and finish() on buildDialog failure are unchanged."
     ],
     "preconditions": [
      "The caller must be able to start the PaymentDefaultDialog activity in Settings with CardEmulation.EXTRA_SERVICE_COMPONENT, CardEmulation.EXTRA_CATEGORY and an Intent.EXTRA_USER extra naming a different user. The material does not show whether the activity is exported or permission-protected.",
      "The user must confirm the dialog (BUTTON_POSITIVE) for setDefaultPaymentApp to be invoked.",
      "PaymentBackend must be constructible; if its constructor throws a NullPointerException, onCreate calls finish()."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). At android-security-14.0.0_r29 all 8 lines of the original patched region are present unmodified, and no fix commit was found.",
      "15": "Same fix: commit 1d0237d77a38 in android-security-15.0.0_r17 (identical diff, resolved by bug_id).",
      "16": "Same fix: commit bf85ca6c87fc in android-security-16.0.0_r8 (identical diff, resolved by bug_id).",
      "17": "Same fix: commit d1db7e8d9994 in android-security-17.0.0_r1 (cherry-pick of c2f680bf6093, Change-Id I9224a3aec3ecb2c8bbf3fe3ee966afaf782cb9c4)."
     },
     "backport_notes": [
      "Single-file change (1 insertion, 8 deletions) with identical context (file index d333b3da6ac..d556b6df929) on 15, 16 and 17. It should apply cleanly wherever onCreate still contains the UserHandle/EXTRA_USER block. UserHandle is still used via UserHandle.myUserId()."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether PaymentDefaultDialog is exported or protected by a permission in AndroidManifest.xml (not in the material).",
      "What buildDialog does with the userId, how mNewDefault.userId is set, and what PaymentBackend.setDefaultPaymentApp does internally (not shown).",
      "Why Android 14 is not listed although the same code region is present there unmodified."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28602": {
     "cve": "CVE-2026-28602",
     "summary": "ClipboardService now reads and writes the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS secure setting with an explicit userId (getIntForUser/putIntForUser), so clipboard access notifications are gated by the setting of the user given in the call.",
     "component": "platform/frameworks/base, services/core clipboard (ClipboardService)",
     "weakness": "Missing per-user scoping of a settings lookup (the setting was not read for the given userId in the notification path); the bulletin classifies the issue as information disclosure",
     "root_cause": "In ClipboardService.java, the method that decides whether to show the clipboard access notification called Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, default) without passing the userId that the method has in scope. So the decision did not consult the setting of the user whose clipboard was being accessed, and the result could be wrong for that user. On the write side, setClipboardAccessNotificationsEnabledForUser called Settings.Secure.putInt on a resolver created via createContextAsUser(UserHandle.of(userId)) and did not pass userId explicitly. The fix makes the target user explicit there too.",
     "fix_analysis": [
      "services/core/java/com/android/server/clipboard/ClipboardService.java :: setClipboardAccessNotificationsEnabledForUser: Settings.Secure.putInt(resolver, CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, enable?1:0) is replaced by Settings.Secure.putIntForUser(resolver, CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, enable?1:0, userId), so the write explicitly targets the given user.",
      "services/core/java/com/android/server/clipboard/ClipboardService.java :: access-notification gating method (its name is not in the hunk header; on Android 17 it is the boolean-returning method that first checks shouldSuppressAccessNotificationForUidLocked(uid), around line 1509): Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, mShowAccessNotifications?1:0) is replaced by Settings.Secure.getIntForUser(..., userId), so the decision uses the setting of the given user. The existing read path in areClipboardAccessNotificationsEnabledForUser already used getIntForUser(..., userId) and is unchanged."
     ],
     "completeness": [
      "Covers both the write (setClipboardAccessNotificationsEnabledForUser) and the read in the notification gating path. areClipboardAccessNotificationsEnabledForUser was already per-user.",
      "The fix only changes which user's setting is consulted. The other gating conditions in the same method (shouldSuppressAccessNotificationForUidLocked, the same-owner check, the mShowAccessNotifications default) are untouched.",
      "The material shows no other Settings.Secure.getInt/putInt call without a user in ClipboardService, so no further unfixed sibling can be judged from it."
     ],
     "behavior_changes": [
      "Clipboard access notifications for a user are now controlled by that user's own CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS value. On multi-user devices this can turn notifications on or off for some users compared with before.",
      "setClipboardAccessNotificationsEnabledForUser writes the setting explicitly for userId."
     ],
     "preconditions": [
      "A device with more than one user, where the CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS value of the user whose clipboard is accessed differs from the value the old call consulted.",
      "An app accessing the clipboard in that user, so that the notification gating method runs for that userId.",
      "Changing the setting through setClipboardAccessNotificationsEnabledForUser requires MANAGE_CLIPBOARD_ACCESS_NOTIFICATION (checked at the top of the method)."
     ],
     "per_major": {
      "14": "adapted fix: DO NOT MERGE commit 6f90eac7f9e5 in android-security-14.0.0_r29. Same two changes, but the second hunk sits in a method that returns nothing ('return' rather than 'return false'), right after a clipboard.primaryClip == null check.",
      "15": "adapted fix: DO NOT MERGE commit 993d9100be4c in android-security-15.0.0_r17. Same shape as the Android 14 backport ('return', after the primaryClip null check).",
      "16": "adapted fix: DO NOT MERGE commit 80b1b1fe63b5 in android-security-16.0.0_r8. Same shape as the Android 14 and 15 backports.",
      "17": "same fix: commit 4b3bf7fb29ec in android-security-17.0.0_r1 (cherry-pick of 2198f8cd4baa). The second hunk is in a boolean-returning method, after the shouldSuppressAccessNotificationForUidLocked(uid) check."
     },
     "backport_notes": [
      "Single-commit, two-hunk change. The only difference between versions is the method around the second hunk ('return' on 14 to 16, 'return false' on 17). In a fork, find the Settings.Secure.getInt(getContext().getContentResolver(), CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, ...) call in the notification path and the putInt call in setClipboardAccessNotificationsEnabledForUser, and make sure a userId variable is in scope in the notification method.",
      "The commit message says a mock test will follow in a separate CL. That test is not part of this fix and is not in the material."
     ],
     "confidence": "high",
     "unknowns": [
      "Which user the old getInt call on the service context's resolver actually resolved to is not shown in the material.",
      "The name of the Android 17 method that contains the second hunk, and where its userId variable comes from, are not shown.",
      "Whether putInt on a resolver created via createContextAsUser already targeted the right user (which would make the first hunk defensive only) is not shown.",
      "The per-major facts report 7/8 lines of the patched region present at each latest tag; the material does not say which line differs.",
      "The follow-up test CL mentioned in the commit message is not provided."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28603": {
     "cve": "CVE-2026-28603",
     "summary": "In Settings' AppRestrictionsFragment, the vetted copy of an app-supplied custom restrictions Intent now has all four FLAG_GRANT_*_URI_PERMISSION flags removed and its data and clipData cleared before Settings starts the activity with it.",
     "component": "platform/packages/apps/Settings, users/AppRestrictionsFragment (app restrictions UI)",
     "weakness": "Missing sanitization of an untrusted Intent forwarded by a privileged app (URI permission grant flags and content URIs passed through)",
     "root_cause": "AppRestrictionsFragment.RestrictionsResultReceiver.assertSafeToStartCustomActivity(Intent) copies the custom activity Intent, resolves it with MATCH_DEFAULT_ONLY, and only checks that the resolved activity's package equals the expected packageName before pinning component and package. Everything else in the copy came from the untrusted Intent, including the FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION flags, the data URI and the ClipData. Settings then calls startActivityForResult(vettedIntent, requestCode) (line 699). The likely concern, consistent with the EoP classification, is that URI grants would be issued under Settings' identity for URIs the app chose.",
     "fix_analysis": [
      "src/com/android/settings/users/AppRestrictionsFragment.java : RestrictionsResultReceiver.assertSafeToStartCustomActivity: after setComponent/setPackage on vettedIntent, the fix calls vettedIntent.removeFlags(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION | FLAG_GRANT_PREFIX_URI_PERMISSION), then vettedIntent.setData(null) and vettedIntent.setClipData(null). The Intent passed to startActivityForResult therefore no longer carries grant flags or URIs.",
      "tests/robotests/src/com/android/settings/users/AppRestrictionsFragmentTest.java (new): the Robolectric test assertSafeToStartCustomActivity_stripsSensitiveData builds an Intent with all four grant flags, a content:// data URI and a ClipData. It mocks mPackageManager.resolveActivity to return a matching package, calls assertSafeToStartCustomActivity through reflection, and asserts that the flags are cleared and data/clipData are null."
     ],
     "completeness": [
      "The fix covers the launch path shown in the context, where the output of assertSafeToStartCustomActivity is passed to startActivityForResult. The material does not show whether other launch paths exist in the receiver or the fragment.",
      "All four URI grant flags are removed and both data and ClipData are cleared. The existing package-match check is kept.",
      "The diff does not touch extras, action, categories or non-grant flags. setData(null) also clears the MIME type (general platform knowledge).",
      "Per index search, there were no relevant users outside Settings among the top results. Other paths inside Settings were not examined in the material."
     ],
     "behavior_changes": [
      "Custom restrictions activities launched through this path no longer receive a data URI, ClipData or URI permission grant flags from the supplied Intent. Apps that relied on passing a URI to their own custom restrictions activity will see null data/clipData.",
      "No new exception or error path is added. The existing SecurityException/ActivityNotFoundException checks are unchanged."
     ],
     "preconditions": [
      "The app restrictions flow in AppRestrictionsFragment must reach RestrictionsResultReceiver with a custom activity Intent, which is passed to assertSafeToStartCustomActivity.",
      "The Intent must resolve to an activity in the expected package (existing package check)."
     ],
     "per_major": {
      "14": "same fix: commit 0f9f6238cf25 in android-security-14.0.0_r29, the same 6-line hunk (at line 687) plus the same new Robolectric test; 4/4 patched lines present.",
      "15": "same fix: commit 856f8ccca12e in android-security-15.0.0_r17, the same hunk (at line 683) plus the same test; 4/4 patched lines present.",
      "16": "same fix: commit 3bf7abe6e14b in android-security-16.0.0_r8, the same hunk (at line 683) plus the same test; 4/4 patched lines present.",
      "17": "same fix: commit a3cbe8fbf286 in android-security-17.0.0_r1, the same hunk (at line 726) plus the same test; 4/4 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit. The six added lines go after vettedIntent.setPackage(activityInfo.packageName) in assertSafeToStartCustomActivity on all branches; only the hunk offsets differ. The test needs SettingsRoboTests and uses reflection into the private method and the mPackageManager field, so adapt it if the fork lacks that infrastructure."
     ],
     "confidence": "high",
     "unknowns": [
      "How the Intent reaches RestrictionsResultReceiver, who can open AppRestrictionsFragment, and whether any entry point other than the Settings UI can drive this flow.",
      "Which content providers were the concern; the test only uses a placeholder content://com.android.settings.files URI.",
      "Whether other launch paths in RestrictionsResultReceiver or elsewhere in Settings forward app-supplied Intents.",
      "The contents of the original internal commits (e2db447a6a08, df6c1b2471a9), which are referenced only as cherry-pick sources."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28606": {
     "cve": "CVE-2026-28606",
     "summary": "Makes AdapterService always drop the per-device entry in mBondAttemptCallerInfo whenever a bond state transition leaves BOND_BONDING. On Android 17 it does this by removing the remove_bond_caller_info aconfig flag gate; on Android 15/16 it adds the removal block directly.",
     "component": "platform/packages/modules/Bluetooth, btservice AdapterService (bonding / caller attribution)",
     "weakness": "Incomplete cleanup / stale state retention: per-device bond caller information was kept in mBondAttemptCallerInfo after the bond attempt had concluded",
     "root_cause": "AdapterService keeps a map, mBondAttemptCallerInfo, keyed by device address. It holds a CallerInfo for the caller that started a bond attempt. Before the fix, the bond-state handler did not remove that entry when the bond attempt finished (transition to BOND_BONDED or BOND_NONE), so stale caller attribution for a device address could outlive the bonding it belonged to. On Android 17 the removal logic existed but was gated behind the aconfig flag Flags.removeBondCallerInfo(), so with the flag disabled the entry was still kept. On Android 15/16 the handler had no removal logic; the material does not show whether any other path cleaned the entry up.",
     "fix_analysis": [
      "android/app/src/com/android/bluetooth/btservice/AdapterService.java:handleBondStateChanged (Android 17): the condition `if (Flags.removeBondCallerInfo() && toState != BOND_BONDING)` becomes `if (toState != BOND_BONDING)`. After the profile handlers and mStorage.onBondStateChanged(...) run, mBondAttemptCallerInfo.remove(device.getAddress()) now always executes when the new state is not BOND_BONDING, and logs a debug line if an entry was present.",
      "flags/pairing.aconfig (Android 17): the definition of the flag `remove_bond_caller_info` (bug 492665910, PURPOSE_BUGFIX) is deleted, so the behavior can no longer be turned off by flag.",
      "android/app/src/com/android/bluetooth/btservice/AdapterService.java (Android 15 9b196f44770c and Android 16 07542331e7c4): adds `import static android.bluetooth.BluetoothDevice.BOND_BONDING;` and inserts the same `if (toState != BOND_BONDING) { mBondAttemptCallerInfo.remove(device.getAddress()); ... }` block, with no flag gate, at the end of the bond-state handling method, directly after the block that resets phonebook/SIM access permissions."
     ],
     "completeness": [
      "Covers every bond state transition that passes through handleBondStateChanged with toState != BOND_BONDING (that is, BOND_BONDED and BOND_NONE), and clears the entry for that device address only.",
      "Entries are cleared only on a state transition. The material shows no other cleanup path (for example adapter off or device removal) and no change to where mBondAttemptCallerInfo is populated or read, so whether other lifetimes of the entry are handled cannot be judged from the diff.",
      "The removal is keyed on device.getAddress(). The material does not show how entries are keyed when inserted, so a key mismatch (if any) cannot be assessed."
     ],
     "behavior_changes": [
      "mBondAttemptCallerInfo no longer keeps an entry for a device once its bond attempt has concluded. A later lookup of that device's bond caller info finds nothing until a new bond attempt records one.",
      "New debug log line 'Removed bond caller info for device: <device>' when an entry is removed.",
      "Android 17: the remove_bond_caller_info aconfig flag no longer exists; the behavior is always on and cannot be disabled through flag configuration."
     ],
     "preconditions": [
      "A bond attempt was started for a device, so an entry was recorded in mBondAttemptCallerInfo for that device address (the insertion site is not shown in the material).",
      "A bond state change for that device reaches AdapterService.handleBondStateChanged (Android 17) or the equivalent bond-state handler (Android 15/16) with toState of BOND_BONDED or BOND_NONE. The caller of this method is not shown in the material.",
      "Android 17 before the fix: the remove_bond_caller_info flag disabled (the flag's default state is not shown)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29 none of the patched region's lines are present (0/8).",
      "15": "Adapted fix: commit 9b196f44770c in android-security-15.0.0_r17 adds the BOND_BONDING static import and inserts the removal block, with no aconfig flag, at the end of the bond-state handler (after the phonebook/SIM permission reset). 8/8 lines present.",
      "16": "Adapted fix: commit 07542331e7c4 in android-security-16.0.0_r8, with the same content as the Android 15 change (import plus unflagged removal block after the permission reset). 8/8 lines present.",
      "17": "Flag-removal fix: commit 1b0d684402b2 in android-security-17.0.0_r1 drops the Flags.removeBondCallerInfo() gate in handleBondStateChanged and deletes the flag from flags/pairing.aconfig. The removal block itself came from an earlier flagged change that is not in the material. 6/8 lines of the patched region are present at r1; the code context shows the unflagged block in place. The material does not say which 2 lines are missing."
     },
     "backport_notes": [
      "On branches without the flagged block (as on 15/16), do not cherry-pick the 17 unflag commit. Instead, insert the unflagged block as in 9b196f44770c / 07542331e7c4, add the BOND_BONDING import, and confirm that mBondAttemptCallerInfo and CallerInfo exist in the target AdapterService (the material does not show when they were introduced).",
      "On branches that carry the flagged block, cherry-pick 1b0d684402b2. It also deletes the flag definition from flags/pairing.aconfig, so remove any other references to Flags.removeBondCallerInfo() (tests included) or the build will fail (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Where mBondAttemptCallerInfo is populated and which code reads the stored CallerInfo, and so the concrete effect of the stale entry that the bulletin rates as High EoP.",
      "Whether any other code path removed entries from mBondAttemptCallerInfo before the fix.",
      "The default value and rollout state of the remove_bond_caller_info flag on Android 17 before the fix.",
      "Which component calls handleBondStateChanged (the bond state machine is not shown).",
      "Whether the earlier flagged commit on Android 17 made other changes beyond the block shown, and which 2 lines of the patched region are missing at 17.0.0_r1."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28607": {
     "cve": "CVE-2026-28607",
     "summary": "Adds a caller permission check (REQUEST_INSTALL_PACKAGES requested, or INSTALL_PACKAGES granted) at the start of UnarchiveErrorActivity.onCreate and moves the existing check in UnarchiveActivity.onCreate ahead of the PIA v2 forward to UnarchiveLaunch, so callers without either permission can no longer reach the unarchive dialogs.",
     "component": "platform/frameworks/base, packages/PackageInstaller (unarchive UI: UnarchiveActivity, UnarchiveErrorActivity, PackageUtil)",
     "weakness": "missing / misordered permission check (authorization bypass)",
     "root_cause": "There were two gaps in the PackageInstaller unarchive activities. (1) In UnarchiveActivity.onCreate, when PackageUtil.isVersionTwoEnabled(this) was true (flag android.content.pm.use_pia_v2), the activity copied the incoming intent, added EXTRA_CALLING_PKG_NAME/EXTRA_CALLING_PKG_UID, and forwarded it to com.android.packageinstaller.v2.ui.UnarchiveLaunch with FLAG_ACTIVITY_FORWARD_RESULT. This happened before the calling UID was resolved and before the REQUEST_INSTALL_PACKAGES / INSTALL_PACKAGES check ran, so the check only protected the legacy (v1) dialog path. (2) UnarchiveErrorActivity.onCreate did no caller validation at all. After super.onCreate(null) it went straight to reading caller-supplied extras (such as EXTRA_UNARCHIVE_STATUS and EXTRA_REQUIRED_BYTES) and showing the error dialog (on 17, it forwarded to UnarchiveLaunch). Any app able to start these activities could therefore drive installer-owned unarchive UI without holding either install permission.",
     "fix_analysis": [
      "PackageUtil.java: adds two static helpers that replace the private copies previously in UnarchiveActivity. getPackageNameForUid(PackageManager, int) returns pm.getPackagesForUid(uid)[0] or null, and now logs on a miss. getRequestedPermissions(PackageManager, String) returns requestedPermissions from getPackageInfo(pkg, GET_PERMISSIONS), or an empty array on failure; it catches Exception instead of only NameNotFoundException.",
      "UnarchiveActivity.java:onCreate (17): removes the early `if (PackageUtil.isVersionTwoEnabled(this)) { ... startActivity(piaV2 -> UnarchiveLaunch); finish(); return; }` block, which ran before any check. The identical block is re-inserted after the existing checks: getLaunchedFromUid() != INVALID_UID, callingPackage != null, and (REQUEST_INSTALL_PACKAGES in requested permissions OR checkPermission(INSTALL_PACKAGES, 0, callingUid) == PERMISSION_GRANTED). Each failure sets RESULT_FIRST_USER and finishes. The v1 dialog path is otherwise unchanged apart from using a single `packageManager` local.",
      "UnarchiveErrorActivity.java:onCreate: adds getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) before super.onCreate(null). It then inserts the same three-step gate (INVALID_UID check, PackageUtil.getPackageNameForUid null check, REQUEST_INSTALL_PACKAGES-requested or INSTALL_PACKAGES-granted check) before the v2 forward to UnarchiveLaunch (17) and before extras are read. Failures log with LOG_TAG, call setResult(RESULT_FIRST_USER) and finish()."
     ],
     "completeness": [
      "Both legacy unarchive activities (UnarchiveActivity, UnarchiveErrorActivity) are now gated before any work, including the v2 forward. The check therefore covers both the v1 and v2 paths entered through these two activities.",
      "For REQUEST_INSTALL_PACKAGES, the check only tests that the caller's manifest requests the permission. A code comment says AppOps is deliberately not consulted because the requester is not the install source. INSTALL_PACKAGES is checked as granted for callingUid with pid 0. Any app that merely declares REQUEST_INSTALL_PACKAGES passes.",
      "getPackageNameForUid uses the first package for the UID. For shared-UID apps, the requested-permission check is evaluated against packagesForUid[0] only.",
      "This commit does not modify the v2 target com.android.packageinstaller.v2.ui.UnarchiveLaunch itself. The material does not show whether it can be reached other than through these forwards.",
      "Once the caller passes the permission gate, caller-supplied extras (EXTRA_PACKAGE_NAME, EXTRA_UNARCHIVE_INTENT_SENDER, EXTRA_UNARCHIVE_STATUS, EXTRA_REQUIRED_BYTES, installer fields) are still trusted. No content validation is added."
     ],
     "behavior_changes": [
      "UnarchiveErrorActivity now finishes immediately with Activity.RESULT_FIRST_USER (and logs under LOG_TAG) in three cases: the launching UID cannot be determined, it maps to no package, or it neither requests REQUEST_INSTALL_PACKAGES nor holds INSTALL_PACKAGES.",
      "On Android 17 with use_pia_v2 enabled, UnarchiveActivity now returns RESULT_FIRST_USER to callers without either permission instead of forwarding them to UnarchiveLaunch.",
      "UnarchiveErrorActivity's window now sets SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, which hides non-system overlays while it is shown.",
      "PackageUtil.getRequestedPermissions swallows any Exception (not only NameNotFoundException) and returns an empty array. PackageUtil.getPackageNameForUid logs an error when the UID has no packages."
     ],
     "preconditions": [
      "The caller must be able to start UnarchiveActivity or UnarchiveErrorActivity in the PackageInstaller app via an intent. Their exported/intent-filter configuration is not shown in the material.",
      "For the UnarchiveActivity bypass on Android 17, the flag android.content.pm.use_pia_v2 must be enabled so that PackageUtil.isVersionTwoEnabled(this) is true. The UnarchiveErrorActivity gap did not depend on the flag.",
      "Before the fix, UnarchiveErrorActivity required no permission of the caller, and neither did the UnarchiveActivity v2 forward."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14, and no 14 fix commit is shown. The per-major facts do report 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Adapted fix (a4b48c54049c, android-security-15.0.0_r17). It adds the same PackageUtil helpers and the same new gate in UnarchiveErrorActivity, plus a LOG_TAG constant that was missing there. In UnarchiveErrorActivity the gate is followed directly by reading extras, with no UnarchiveLaunch forward. The UnarchiveActivity hunks are only the helper refactor; the diff does not touch or move any v2 forward block.",
      "16": "Adapted fix (2e158f2d9a81, android-security-16.0.0_r8). It matches the 15 version: the UnarchiveActivity and UnarchiveErrorActivity changes are the same, with the same blob hashes, and LOG_TAG is added to UnarchiveErrorActivity.",
      "17": "Same fix as analysed (743a34f05af0, android-security-17.0.0_r1). It moves the v2 forward in UnarchiveActivity below the permission checks and adds the gate, before the v2 forward, in UnarchiveErrorActivity, which already had LOG_TAG and the UnarchiveLaunch import."
     },
     "backport_notes": [
      "In the 15/16 diffs, the security-relevant change is the new gate at the top of UnarchiveErrorActivity.onCreate plus the SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS flag. The UnarchiveActivity hunks there are a refactor to the new PackageUtil helpers. If UnarchiveErrorActivity lacks them, add the LOG_TAG constant and the permission/Process/Log/Arrays imports.",
      "If a branch carries the use_pia_v2 forward in UnarchiveActivity (or UnarchiveErrorActivity), make sure the `isVersionTwoEnabled` block ends up after the INSTALL_PACKAGES / REQUEST_INSTALL_PACKAGES check. Refactoring the helpers alone is not enough."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether UnarchiveActivity / UnarchiveErrorActivity are exported, and how they are declared in the PackageInstaller manifest (not in the material).",
      "Whether com.android.packageinstaller.v2.ui.UnarchiveLaunch can be reached independently, and what checks it performs.",
      "Whether UnarchiveActivity on 15/16 contains any v2 forward block outside the shown hunks.",
      "The concrete impact chain behind the High EoP rating (the bulletin gives only the rating and type).",
      "Why the Android 14 security tag shows the patched region present although the bulletin does not list 14 as affected."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28612": {
     "cve": "CVE-2026-28612",
     "summary": "Reorders the activity start paths in ActivityStarter and ActivityStartController so the intent-redirect checks run before supervisor.resolveIntent/resolveActivity. The checks are the EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN abort and the IntentCreatorToken extraction. Resolution can change the intent's component and so invalidate the creator token.",
     "component": "platform/frameworks/base, WindowManager/ActivityManager activity start (services/core/java/com/android/server/wm)",
     "weakness": "Security check performed after the state it depends on is changed (ordering flaw that undermines intent redirect protection)",
     "root_cause": "There are two start paths. One is the ActivityStarter.java path that calls supervisor.resolveIntent (check at around line 641 post-fix). The other is the multi-intent loop in ActivityStartController.java (around line 449). Both first called supervisor.resolveIntent/resolveActivity. Only afterwards did they check Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN and evaluate IntentCreatorToken.isValid(intent) to extract the creator UID and package. Per the commit message, resolveActivity can change the intent's component, which invalidates the creator token. The existing comment in ActivityStartController agrees: \"the intent may be modified in resolveActivity below\". So the creator-token checks ran against an intent that resolution might already have changed. Before the fix, the shown ActivityStartController loop had no abort for the MISSING_CREATOR_OR_INVALID_TOKEN flag.",
     "fix_analysis": [
      "ActivityStarter.java (the method that calls supervisor.resolveIntent; its name is not shown in the material): the redirect-check block stays where it is. It checks EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN (logAndThrowExceptionForIntentRedirect) and reads IntentCreatorToken.isValid(intent) into intentCreatorUid/intentCreatorPackage. Three calls move after it: supervisor.resolveIntent(...), the resolveIntentForLockedOrStoppedProfiles fallback and supervisor.resolveActivity(...). The token is therefore checked before resolution can change the intent.",
      "ActivityStartController.java (multi-intent loop over intents[]/resolvedTypes[]): mSupervisor.resolveActivity(...) and mService.mAmInternal.getActivityInfoForUser(...) move after the IntentCreatorToken.isValid(intent) extraction of creatorUid/creatorPackage.",
      "ActivityStartController.java: a new check runs before resolution. If EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN is set, it calls ActivityStarter.logAndAbortForIntentRedirect(mService.mContext, INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN, intent, creatorUid, creatorPackage, filterCallingUid, callingPackage). If that returns true, the method logs and returns START_CANCELED.",
      "ActivityStarter.java: logAndAbortForIntentRedirect changes from private static to package-private static so ActivityStartController can call it."
     ],
     "completeness": [
      "Both start paths in the material are reordered so the token checks come before resolution: the single-intent path in ActivityStarter and the multi-intent loop in ActivityStartController.",
      "The fix is a reordering plus one new abort. resolveIntent/resolveActivity themselves are unchanged and can still change the intent, but now only after the checks.",
      "In ActivityStarter, the instant-app-installer interception (intent.setComponent(null)) still runs before the token checks. The material does not show whether that change affects token validity.",
      "The material does not show whether logAndAbortForIntentRedirect actually aborts (returns true) or only logs, so how strong the new ActivityStartController abort is cannot be judged from the diff."
     ],
     "behavior_changes": [
      "Multi-intent starts through ActivityStartController can now be rejected with START_CANCELED and a Slog.d message when an intent carries EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN. Before, that path had no such check.",
      "The creator UID/package used for intent redirect checks now comes from the intent as received, before resolution, not from the intent after resolution.",
      "logAndAbortForIntentRedirect becomes package-visible within com.android.server.wm."
     ],
     "preconditions": [
      "An activity start reaches ActivityStarter's resolve path or ActivityStartController's multi-intent loop with an Intent that carries a creator token or the missing/invalid-token extended flag.",
      "Intent resolution (resolveIntent/resolveActivity) changes the intent's component so that the creator token no longer validates. The exact conditions are not shown in the material.",
      "Intent redirect protection (the creator token mechanism) is present and active on the device. How it is gated is not shown."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. 4/8 lines of the patched region are present at android-security-14.0.0_r29. Whether the redirect-check code exists on 14 is not shown.",
      "15": "Not affected per the bulletin. 6/8 lines of the patched region are present at android-security-15.0.0_r17. Whether the redirect-check code exists in this form is not shown.",
      "16": "Same fix: platform/frameworks/base f7398bb64526 in android-security-16.0.0_r8. The diff matches the 17 commit except for line offsets. 8/8 patched lines present.",
      "17": "Fixed by platform/frameworks/base a2f24fcecabd in android-security-17.0.0_r1, a cherry-pick of two internal commits (Change-Id Iae6a7ef5cb569e1436c9a705b686488bf348b5ff). 8/8 patched lines present."
     },
     "backport_notes": [
      "Single commit across two files. The ActivityStartController hunk depends on removing `private` from ActivityStarter.logAndAbortForIntentRedirect, so apply both files together. The bulletin lists 16, 16-qpr2, 17 and 17-next as affected. Confirm the IntentCreatorToken / EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN logic exists in the target branch before applying."
     ],
     "confidence": "high",
     "unknowns": [
      "Exact names of the enclosing methods in ActivityStarter and ActivityStartController (not shown in the material).",
      "How IntentCreatorToken.isValid binds the token to intent fields, and which changes made by resolveActivity invalidate it.",
      "What the pre-fix ordering led to downstream, for example which creator UID was used and how the later redirect checks behaved.",
      "Whether logAndAbortForIntentRedirect always enforces (returns true) or depends on a flag or enforcement mode.",
      "The PoC and concrete scenario mentioned in the commit message (Bug 475542441)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28613": {
     "cve": "CVE-2026-28613",
     "summary": "In Live TV's ChannelImpl.initAppLinkTypeAndIntent, the fix refuses to build a channel app-link Intent when the resolved target activity is exported and declares a permission, in addition to the existing block on activities in the Live TV package itself.",
     "component": "platform/packages/apps/TV (Live TV), channel app-link handling in src/com/android/tv/data/ChannelImpl.java",
     "weakness": "Intent redirection (confused deputy): insufficient validation of a channel-supplied intent URI before it becomes the channel's app-link intent",
     "root_cause": "ChannelImpl.initAppLinkTypeAndIntent parses the channel's mAppLinkIntentUri with Intent.parseUri(..., URI_INTENT_SCHEME), resolves it via intent.resolveActivityInfo(pm, 0), and stores it as mAppLinkIntent. The only validation was that the resolved activity's packageName is not CommonConstants.BASE_PACKAGE or a sub-package of it. An app link pointing at an exported, permission-guarded activity in another package was accepted, which allowed intent redirection to permission-protected activities (per the commit title).",
     "fix_analysis": [
      "src/com/android/tv/data/ChannelImpl.java:initAppLinkTypeAndIntent: after computing isProtectedActivity, compute isPermissionProtected = activityInfo.exported && (activityInfo.permission != null) from the ActivityInfo returned by resolveActivityInfo.",
      "If isPermissionProtected is true, log 'Attempt to add app link to permission protected activity' and return, leaving mAppLinkType = APP_LINK_TYPE_NONE and mAppLinkIntent = null (same outcome as the pre-existing isProtectedActivity block).",
      "The existing isProtectedActivity check is kept unchanged (whitespace-only edit of its Log.w line). The new check runs after it.",
      "A code comment documents that chooser intents and ACTION_SEND were considered and deliberately not restricted: with Intent.parseUri an attacker cannot wrap Intent.EXTRA_INTENT in the app link, and testing showed ACTION_SEND cannot obtain FLAG_GRANT_* through this path."
     ],
     "completeness": [
      "Covers: app-link targets that resolve to an activity that is exported and has a non-null activity-level permission, plus the previously blocked Live TV package activities.",
      "Only the single ActivityInfo returned by resolveActivityInfo(pm, 0) is inspected. The check uses only activityInfo.exported and activityInfo.permission, so this change does not sanitize any other intent attributes (flags, extras, data).",
      "Chooser intents and ACTION_SEND intents are intentionally left unrestricted, per the in-code comment, based on the authors' reasoning about Intent.parseUri and FLAG_GRANT_* behavior.",
      "The fix is confined to initAppLinkTypeAndIntent. The material does not show other code paths that build intents from channel data."
     ],
     "behavior_changes": [
      "Channels whose app link resolves to an exported, permission-protected activity now get APP_LINK_TYPE_NONE from getAppLinkType and a null intent from getAppLinkIntent, and a Log.w warning is emitted.",
      "Legitimate channels whose app link targets an exported, permission-protected activity lose that app link."
     ],
     "preconditions": [
      "A channel record with non-empty app link text and app link intent URI (mAppLinkText, mAppLinkIntentUri).",
      "The URI parses with Intent.parseUri(URI_INTENT_SCHEME) and resolves (resolveActivityInfo) to an activity outside the Live TV package.",
      "ChannelImpl.getAppLinkType or getAppLinkIntent is called for that channel. Both call initAppLinkTypeAndIntent lazily."
     ],
     "per_major": {
      "14": "same fix: commit e64e4b90fc77 in android-security-14.0.0_r29, identical diff (same blob ids 5be1179d..62c06cc2) in ChannelImpl.java.",
      "15": "same fix: commit 481a7a588acd in android-security-15.0.0_r17, identical diff.",
      "16": "same fix: commit 86fb106012ec in android-security-16.0.0_r8, identical diff.",
      "17": "same fix: commit a10682aac138 in android-security-17.0.0_r1 (Change-Id I0f92a361c4f9b78c459905c06ecde5d9a2996c97, cherry-picked from an internal commit), identical diff."
     },
     "backport_notes": [
      "Single self-contained commit touching one method. The pre-image blob (5be1179d) is the same on all four branches, so it should apply cleanly wherever the isProtectedActivity block exists.",
      "Forks with a different CommonConstants.BASE_PACKAGE check should keep it and add the isPermissionProtected check after it, before mAppLinkIntent is assigned."
     ],
     "confidence": "high",
     "unknowns": [
      "Which specific permission-protected activity or privilege gain was demonstrated in the report (bug 329233015) is not shown.",
      "Where and how mAppLinkIntent is ultimately launched or displayed (which component calls getAppLinkIntent and starts the activity) is not included in the material.",
      "Which apps can supply the channel app-link fields is not shown.",
      "Whether resolveActivityInfo(pm, 0) can return a non-exported activity of another package in this context is not shown, so the material does not let us judge how the exported && permission != null condition handles non-exported targets.",
      "Why the resolver reports 7/8 lines of the patched region present on every tag is not explained. The 17.0.0_r1 code context shows all added lines."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28614": {
     "cve": "CVE-2026-28614",
     "summary": "Adds an isCallerValid() check to SystemUI's SlicePermissionActivity.onCreate so the slice permission dialog is only shown when the activity was actually launched by the slice provider's package, SystemUI itself, or the \"android\" system package; otherwise the activity finishes immediately.",
     "component": "platform/frameworks/base, SystemUI (packages/SystemUI, SlicePermissionActivity, Slices permission grant UI)",
     "weakness": "Missing caller/launcher validation on an activity that grants permissions (confused deputy; trusting intent extras for identity)",
     "root_cause": "SlicePermissionActivity.onCreate took the requesting package (mCallingPkg) directly from the intent extra SliceProvider.EXTRA_PKG and the target provider from EXTRA_BIND_URI, then showed an Allow/Deny dialog labelled with those packages. The only existing check, verifyCallingPkg(), compares the optional \"provider_pkg\" extra to the resolved provider package; nothing verified who actually launched the activity. Per the commit message, a malicious app could therefore trick the user into granting slice permissions by spoofing the package name in the intent, with SystemUI acting as the deputy.",
     "fix_analysis": [
      "packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java:onCreate: after resolving mProviderPkg via pm.resolveContentProvider and calling verifyCallingPkg(), a new `if (!isCallerValid()) { finish(); return; }` gate is inserted before the app labels are loaded and the AlertDialog is built, so an untrusted launcher never reaches the dialog.",
      "packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java:isCallerValid (new): determines the real launcher with getLaunchedFromPackage(), falling back to getCallingPackage() when that is null; returns true only if the result is non-null and equals mProviderPkg, equals getPackageName() (SystemUI itself), or equals \"android\". Any other value (including null) logs \"Direct launch blocked. Expected provider ... or system, but got ...\" and returns false.",
      "packages/SystemUI/tests/src/com/android/systemui/SlicePermissionActivityTest.kt (new): unit tests covering launch from provider package, SystemUI package, \"android\", the getCallingPackage() fallback, and an untrusted package which must result in finish()."
     ],
     "completeness": [
      "The gate is placed in onCreate before the dialog is built; whether other paths reach the grant logic is not shown in the material.",
      "Identity is taken from the platform-reported launcher (getLaunchedFromPackage / getCallingPackage), not from intent extras; EXTRA_PKG itself is still not cross-checked against the launcher, so an allow-listed launcher (notably the provider package) can still name any mCallingPkg.",
      "A null launcher identity (both APIs return null) is treated as invalid, so the check fails closed.",
      "The check runs after resolveContentProvider inside the existing try block; the catch clause is not shown, so behaviour when resolution throws is unchanged and not part of this fix.",
      "verifyCallingPkg() is retained unchanged and still only validates the optional \"provider_pkg\" extra."
     ],
     "behavior_changes": [
      "SlicePermissionActivity now finishes immediately, without showing the dialog, when launched by any package other than the resolved slice provider's package, SystemUI's own package, or \"android\".",
      "New Log.e line \"Direct launch blocked. Expected provider <pkg> or system, but got <caller>\" on rejection.",
      "Callers that previously launched the permission dialog directly from a package other than the provider, SystemUI or \"android\" will now be rejected."
     ],
     "preconditions": [
      "The launching app must be able to start SystemUI's SlicePermissionActivity with action SliceManager.ACTION_REQUEST_SLICE_PERMISSION, a valid EXTRA_BIND_URI whose resolver type is SliceProvider.SLICE_TYPE, and a chosen EXTRA_PKG (the existing intent validation in onCreate requires these); reachability (export status) is not shown.",
      "A slice provider must be resolvable for the URI authority (pm.resolveContentProvider must succeed).",
      "User interaction: the user must tap Allow in the displayed dialog."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base c6b5038a9e23 in android-security-14.0.0_r29; identical code and test, only hunk line offsets differ (66/110 vs 72/116); 8/8 patched lines present.",
      "15": "same fix: platform/frameworks/base 971f1d196295 in android-security-15.0.0_r17; identical diff to the 17 commit; 8/8 patched lines present.",
      "16": "same fix: platform/frameworks/base dba6ec2c05ff in android-security-16.0.0_r8; identical diff to the 17 commit; 8/8 patched lines present.",
      "17": "same fix: platform/frameworks/base 7e25b93a1faa in android-security-17.0.0_r1 (cherry-pick of 7f44449562bc, Change-Id I49822e9f7eb2d29278d1266ae2fd51fc154744db); 8/8 patched lines present."
     },
     "backport_notes": [
      "Single self-contained commit; Google applied the same Java change on 14 through 17 with only line-offset differences. Activity.getLaunchedFromPackage() became public API in Android 14 (API 34) and existed earlier as a hidden API (general platform knowledge); forks backporting further should verify its availability.",
      "The new Kotlin test relies on SysuiTestCase and reflection into Activity internals (mActivityInfo, mFragments, mApplication, mWindow, ContextWrapper.mBase); it is optional for the security fix and may need adjustment on forks with modified SystemUI test infrastructure."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether SlicePermissionActivity is exported or otherwise reachable by arbitrary apps is not shown in the material (the manifest is not included).",
      "The onClick grant logic and the catch clause of the surrounding try block are not shown, so the exact grant call performed on Allow, and whether any path other than onCreate reaches it, is not visible here.",
      "How the system or SliceProvider normally launches this activity (and thus which allow-listed identity is used in practice) is not shown, nor whether a provider naming an arbitrary EXTRA_PKG is intended behaviour.",
      "Whether any legitimate flows relied on direct launch from other packages is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28617": {
     "cve": "CVE-2026-28617",
     "summary": "On V+, WifiNetworkSuggestionsManager now clears the vendorData list on each suggestion's WifiConfiguration when suggestions are added. Caller-supplied PersistableBundle vendor data therefore no longer reaches, through the suggestion add path, the XmlUtil OUI-keyed-data serialization/deserialization that the commit message says has no depth bound.",
     "component": "platform/packages/modules/Wifi, WifiNetworkSuggestionsManager (network suggestion add path / suggestion persistence)",
     "weakness": "Per the commit message: a caller-controlled field is not sanitized, and PersistableBundle XML serialization/deserialization has no nesting-depth bound (resource exhaustion / DoS).",
     "root_cause": "Per the commit message, XmlUtil.writeOuiKeyedDataToXml / readPersistableBundleFromBytes serialize and deserialize PersistableBundle vendor data without bounding nesting depth. A WifiNetworkSuggestion's WifiConfiguration could carry vendorData even though, per the commit message, setting it is never allowed for suggestions. The add path in WifiNetworkSuggestionsManager did not strip that field. The commit title describes the result as a persistent DoS. The diff does not show the persistence and reload mechanism.",
     "fix_analysis": [
      "service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java, in the suggestion add method. The hunk does not show the method name; it is the method that logs 'bad wifi suggestion from app' and returns STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED. After the existing validity check and before the convertLegacyFieldsToSecurityParamsIfNeeded loop, a new loop over networkSuggestions calls wns.wifiConfiguration.setVendorData(Collections.emptyList()) on each entry.",
      "The new loop only runs when SdkLevel.isAtLeastV() is true.",
      "The commit adds the import java.util.Collections. No other file is changed in this commit, and XmlUtil itself is not modified here.",
      "The commit message lists two Cherrypick-From sources, but the diff contains only this one-file change. Any companion change that adds depth bounding to XmlUtil is not in the provided material."
     ],
     "completeness": [
      "Covered: the network suggestion add path. On V+, vendor data is dropped for every suggestion before further processing, whatever it contains.",
      "Not changed: XmlUtil.writeOuiKeyedDataToXml / readPersistableBundleFromBytes, which the commit message identifies as the code lacking depth bounding. The fix removes the input source instead of bounding the recursion.",
      "Any other code path that persists a WifiConfiguration with vendorData through the same XmlUtil functions is not touched by this diff. The material does not show whether such paths exist or are handled elsewhere.",
      "When SdkLevel.isAtLeastV() is false, the new code does not run."
     ],
     "behavior_changes": [
      "On V+, any vendorData on a WifiNetworkSuggestion's WifiConfiguration is replaced with an empty list at add time. No error is returned to the caller.",
      "Suggestions added through this path no longer carry vendor data."
     ],
     "preconditions": [
      "SdkLevel.isAtLeastV() must be true for the new code to run (V is Android 15, general platform knowledge).",
      "A caller reaches the WifiNetworkSuggestionsManager add path with a WifiNetworkSuggestion whose WifiConfiguration has a non-empty vendorData list. Per the commit message, setting it is never allowed for suggestions.",
      "The suggestion must pass the existing validity check that runs before the new code."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14. The patched region is present at android-security-14.0.0_r29 (8/8 lines).",
      "15": "Same fix (commit 5dab8604e9c0 in android-security-15.0.0_r17). The hunk is identical; only line offsets differ, because this branch has no BitSet import.",
      "16": "Same fix (commit 4d8225a6d03d in android-security-16.0.0_r8). The hunk is identical, at slightly different line offsets.",
      "17": "Same fix (commit 9b1148745f6b in android-security-17.0.0_r1). This is the reference commit."
     },
     "backport_notes": [
      "Self-contained one-file change: add the Collections import and insert the SdkLevel-gated loop right after the 'bad wifi suggestion from app' early return. The target branch must provide WifiConfiguration.setVendorData and SdkLevel.isAtLeastV().",
      "The commit message refers to XmlUtil depth handling and cites two cherry-pick sources, but the diff contains only this file. Check separately whether a companion XmlUtil change exists and whether the target branch needs it."
     ],
     "confidence": "medium",
     "unknowns": [
      "Whether a separate commit adds depth bounding to XmlUtil.writeOuiKeyedDataToXml / readPersistableBundleFromBytes. Two Cherrypick-From sources are cited, but the diff changes only one file.",
      "The exact name and signature of the patched method (the hunk header shows only the class).",
      "Whether other WifiConfiguration persistence paths (e.g. saved networks) carry vendorData through the same XmlUtil functions, and how they are handled.",
      "How suggestions with vendorData were persisted and reloaded, which would explain why the DoS is 'persistent'.",
      "Why the patched region exists on Android 14 even though the bulletin does not list it as affected, and how the code behaves there."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28620": {
     "cve": "CVE-2026-28620",
     "summary": "Changes the intent-redirection URI grant logic in ActivityStarter and ActivityStartController. When an intent's creator UID differs from the calling UID, URI grants are now applied only if both the caller check and the creator check return grants, and the grants used are the caller's. Previously the two parties' grants were merged. The fix also removes NeededUriGrants.merge.",
     "component": "platform/frameworks/base, activity start in services/core wm (intent redirection hardening, URI grants)",
     "weakness": "Incorrect authorization logic (grants of the two parties were unioned instead of both parties being required to be authorized)",
     "root_cause": "In the intent redirection hardening path, an intent can carry a valid IntentCreatorToken whose creator UID differs from the resolved or filter calling UID. In that case the code called checkGrantUriPermissionFromIntent twice, once for the caller and once for the creator, and then combined the results. If the caller's NeededUriGrants was null, it took the creator's grants as they were. Otherwise it merged (unioned) the creator's URIs into the caller's via NeededUriGrants.merge. The effective grant set was therefore the union of what either party could grant. Either party's authority alone was enough to grant content URI access to the launched activity, instead of requiring both the creator and the launcher to be authorized.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/ActivityStarter.java, contentUriPermissionApis flag-on branch of the grant collection block (hunk near line 687): after creatorIntentGrants is computed for intentCreatorUid, the null fallback and merge are replaced with `if (intentGrants == null || creatorIntentGrants == null) intentGrants = null;`. When both checks return normally, grants proceed only if both results are non-null, and what gets applied is the caller's intentGrants, not a union.",
      "services/core/java/com/android/server/wm/ActivityStarter.java, flag-off else branch (hunk near line 712): the same replacement for the legacy checkGrantUriPermissionFromIntent overload.",
      "services/core/java/com/android/server/wm/ActivityStartController.java, per-intent loop (hunk near line 495): the same replacement for the multi-intent start path where creatorUid != DEFAULT_INTENT_CREATOR_UID.",
      "services/core/java/com/android/server/uri/NeededUriGrants.java: deletes merge(NeededUriGrants), which no longer has callers. The SecurityException handling around the creator check (logAndThrowExceptionForIntentRedirect) is unchanged, and the catch path does not clear intentGrants."
     ],
     "completeness": [
      "All three sites in the material that computed creatorIntentGrants are changed the same way: two in ActivityStarter (flag-on and flag-off) and one in ActivityStartController.",
      "The fix only covers the case where a creator token is present and the creator UID differs from the caller. When they are the same, creatorUid stays at DEFAULT_INTENT_CREATOR_UID, behaviour is unchanged, and the caller's intentGrants are used.",
      "If the creator check throws SecurityException and logAndThrowExceptionForIntentRedirect does not rethrow, the caller's intentGrants stay in place. The material does not show how that helper behaves.",
      "The material does not show other users of IntentCreatorToken grants (for example service or broadcast paths), so coverage outside activity starts cannot be judged from the diff."
     ],
     "behavior_changes": [
      "Stricter: when an intent was created by a different UID than the launcher, the target activity now gets URI grants only if both the creator check and the caller check return non-null grants. Otherwise intentGrants becomes null and no URI grants are applied. Before, the creator's grants were used alone when the caller had none, or were unioned with the caller's.",
      "When grants are applied, the URI set is the caller's NeededUriGrants, never a union that includes URIs only the creator could grant.",
      "The diff adds no new exception or start cancellation. Only the URI grant outcome changes, and a SecurityException from the creator check still goes through logAndThrowExceptionForIntentRedirect as before.",
      "NeededUriGrants.merge is removed (deletion of a public method in services code)."
     ],
     "preconditions": [
      "An activity start through the ActivityStarter grant collection path or the ActivityStartController multi-intent loop, with an intent that has a valid IntentCreatorToken.",
      "The creator UID recorded in the token differs from the resolved or filter calling UID.",
      "The intent requests content URI grants, so that checkGrantUriPermissionFromIntent returns NeededUriGrants for at least one party.",
      "The android.security.Flags.contentUriPermissionApis flag can be on or off, since both ActivityStarter branches are patched."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). The 1/8 patched-region match at android-security-14.0.0_r29 is the same as on the fixed branches, so it does not show whether the hardening code is present.",
      "15": "Not affected per the bulletin (not listed). The 1/8 match at android-security-15.0.0_r17 does not show whether the hardening code is present either.",
      "16": "Same fix: commit 353a1fa497d3 in android-security-16.0.0_r8, with the same logic in all three files. Only the context differs (activityInfo.applicationInfo.uid instead of activityInfo.getUid(), and different line offsets).",
      "17": "Fixed by commit 8df772fb59b4 in android-security-17.0.0_r1 (cherry-pick of two internal commits, Change-Id Ica84e8733595bd2dced557bade0811feacbded19)."
     },
     "backport_notes": [
      "The fix assumes the intent redirection hardening is already present (IntentCreatorToken, DEFAULT_INTENT_CREATOR_UID, creatorIntentGrants, logAndThrowExceptionForIntentRedirect). Because NeededUriGrants.merge is deleted, check that no vendor or OEM code calls it before applying.",
      "The 16 and 17 patches differ in context lines inside the hunks (applicationInfo.uid vs getUid()), so applying them on a fork may need fuzz or a manual context fix as well as offset changes."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show what a null return from checkGrantUriPermissionFromIntent means compared with a SecurityException, so the line between 'no grants needed' and 'not authorized' is inferred from the comment in the diff.",
      "Whether logAndThrowExceptionForIntentRedirect always rethrows is not shown. If it does not, the caller's grants survive a failed creator check.",
      "The names of the enclosing functions in ActivityStarter and ActivityStartController are not shown in the material.",
      "The internal design doc (go/intent-redirection-2) and the two original internal commits are not available. Whether other grant consumers (services, broadcasts) have similar logic is not shown.",
      "The exploit scenario that was tested is not described."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28622": {
     "cve": "CVE-2026-28622",
     "summary": "MediaProvider now returns an empty result whenever latitude/longitude columns appear in the SQL selection, GROUP BY or HAVING clause (regardless of whether a sort order is also present), and neutralizes those columns in the sort order with a case-insensitive, word-bounded replacement instead of a case-sensitive substring replace.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider query builder (geolocation metadata filtering)",
     "weakness": "Incomplete filtering of a column-access restriction (information disclosure). A branch-ordering flaw let a sort-order clause suppress the \"FALSE\" filter for selection/GROUP BY/HAVING, and the sort-order rewrite used case-sensitive matching.",
     "root_cause": "In src/com/android/providers/media/MediaProvider.java, the query-builder method (the one that ends with `return qb;` after the owner-package filtering) computed `filterClauses = getClausesForFilteringGeolocationData(extras, type)` and then used an if/else. If the list contained QUERY_ARG_SQL_SORT_ORDER, the code only rewrote the sort string. Otherwise it appended `FALSE` to the WHERE clause. Because the sort-order branch took priority, a query that put latitude/longitude in the selection, GROUP BY or HAVING clause and also in the sort order was never blocked with `FALSE`, so the restricted columns could still be used to filter rows. The sort-order rewrite also used case-sensitive `String.contains`/`String.replace` with the LATITUDE/LONGITUDE constants, so other letter-case spellings (for example \"LATITUDE\", which the new tests exercise) would not be replaced with NULL.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java: the inline block is moved into a new private method, filterGeolocationClauses(SQLiteQueryBuilder qb, Bundle extras, int type), which is called just before `return qb;` in the query-builder method.",
      "filterGeolocationClauses: returns early when `!indexMediaLatitudeLongitude() || isCallingPackageSelf()` or when getClausesForFilteringGeolocationData(extras, type) returns an empty list. This is the same gating as before.",
      "filterGeolocationClauses: the priority is inverted. If filterClauses contains QUERY_ARG_SQL_SELECTION, QUERY_ARG_SQL_HAVING or QUERY_ARG_SQL_GROUP_BY, `appendWhereStandalone(qb, \"FALSE\")` is applied, so the query returns no rows even when a sort order is also present. The sort string is rewritten only in the `else if` branch (sort order alone).",
      "filterGeolocationClauses: the sort-order rewrite now uses `sortArgs.replaceAll(\"(?i)\\\\b\" + LATITUDE + \"\\\\b\", \"NULL\")` and the same for LONGITUDE. This makes the replacement case-insensitive and limits it to whole words. The result is then stored back into extras under QUERY_ARG_SQL_SORT_ORDER.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: testQueryingMediaGeolocationDataInSelectionShouldReturnEmptyCursor, testQueryingMediaGeolocationDataInOrderByShouldReturnNonEmptyCursor and testQueryingMediaGeolocationDataInGroupByAndHavingShouldReturnEmptyCursor are extended to loop over lowercase (constant) and uppercase column names, including combined LATITUDE OR LONGITUDE selections."
     ],
     "completeness": [
      "Covers the combination case: any geolocation reference detected in selection, HAVING or GROUP BY now forces an empty result regardless of the sort order, which closes the branch-ordering gap.",
      "Covers letter-case variants in the sort-order rewrite through the (?i) regex. The word boundary also prevents replacing part of a longer token.",
      "Detecting which clauses reference the columns still depends entirely on getClausesForFilteringGeolocationData(extras, type), whose body is not in the diff. The material does not show whether its matching is case-insensitive or handles qualified/quoted column names.",
      "The projection path (geolocation columns returned as NULL) is covered by the existing testQueryingMediaGeolocationDataInProjectionShouldReturnNull but is not changed by this commit.",
      "The whole filter is still gated on the indexMediaLatitudeLongitude() flag and skipped when isCallingPackageSelf() is true."
     ],
     "behavior_changes": [
      "Non-self queries that reference latitude/longitude in selection, GROUP BY or HAVING now return an empty cursor even when they also supply a sort order. Previously the sort-order branch took priority and FALSE was not appended.",
      "Sort orders containing latitude/longitude in any letter case, as whole words, are rewritten to NULL, so results are not sorted by those columns. Previously only exact-case substrings matching the constants were replaced.",
      "No new exceptions or error codes. The logic now lives in a new private method, filterGeolocationClauses."
     ],
     "preconditions": [
      "The indexMediaLatitudeLongitude() flag (Flags.FLAG_INDEX_MEDIA_LATITUDE_LONGITUDE in tests) is enabled on the device.",
      "The caller is not MediaProvider itself (isCallingPackageSelf() is false).",
      "A ContentResolver query reaches the MediaProvider query-builder path with query extras for which getClausesForFilteringGeolocationData reports latitude/longitude usage in QUERY_ARG_SQL_SELECTION, QUERY_ARG_SQL_GROUP_BY, QUERY_ARG_SQL_HAVING and/or QUERY_ARG_SQL_SORT_ORDER."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29 only 1/8 lines of the patched region are present. The material does not show what code exists there instead.",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17 only 1/8 lines of the patched region are present. The material does not show what code exists there instead.",
      "16": "Not affected per the bulletin. At android-security-16.0.0_r8, 8/8 lines of the patched region are present, which suggests the fixed form of the code is already in that tag. The material does not show how it got there.",
      "17": "Resolved by this commit (43a7df8794a8, RESTRICT AUTOMERGE, cherry-picked from two internal changes) in android-security-17.0.0_r1. The code context at that tag shows filterGeolocationClauses in place, although the resolver reports only 3/8 patched lines matching."
     },
     "backport_notes": [
      "A single commit in this tag, cherry-picked from two internal changes. It relies on the pre-existing indexMediaLatitudeLongitude() flag, isCallingPackageSelf() and getClausesForFilteringGeolocationData(), so a target branch needs those in place. MediaProvider is a mainline module, so its code can also be updated through module updates (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The implementation of getClausesForFilteringGeolocationData (how it detects column references and whether it is case-insensitive) is not in the material, although the commit message claims case-insensitive matching.",
      "Whether uppercase sort-order spellings were detected before the fix, and therefore whether results were actually sorted by geolocation values.",
      "Why android-security-16.0.0_r8 already contains the full patched region while 16 is not listed by the bulletin.",
      "What code exists in the patched region on 14 and 15 (only 1/8 lines match).",
      "Why the resolver reports 3/8 matching lines at android-security-17.0.0_r1 even though the context shows the fixed method present.",
      "The name of the enclosing query-builder method, which the diff hunk header does not show."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28623": {
     "cve": "CVE-2026-28623",
     "summary": "Stops the generic ranging framework in the Uwb module from serializing the device's Bluetooth MAC address inside BleCsRangingCapabilities and BleRssiRangingCapabilities parcels, substituting a fixed 00:00:00:00:00:00 on the receiving (app) side.",
     "component": "platform/packages/modules/Uwb, generic ranging framework (android.ranging.ble.cs / android.ranging.ble.rssi capability parcelables)",
     "weakness": "Information disclosure: sensitive device identifier (Bluetooth MAC address) written into a Parcelable returned to apps over binder",
     "root_cause": "BleCsRangingCapabilities.writeToParcel wrote mBluetoothAddress with dest.writeString and the Parcel constructor read it back with in.readString. BleRssiRangingCapabilities did the same: its writeToParcel only wrote the address. The objects hold the real device Bluetooth address, set through Builder.setBluetoothAddress or the constructor argument for use inside the uwb service. Per the commit message, sending them through binder leaked that MAC address to user apps, which could read it with getBluetoothAddress().",
     "fix_analysis": [
      "ranging/framework/java/android/ranging/ble/cs/BleCsRangingCapabilities.java:writeToParcel: removes dest.writeString(mBluetoothAddress); only mSupportedSecurityLevels is written.",
      "ranging/framework/java/android/ranging/ble/cs/BleCsRangingCapabilities.java:BleCsRangingCapabilities(Parcel): no longer reads a string; sets mBluetoothAddress to the new constant DEFAULT_MAC_ADDRESS (\"00:00:00:00:00:00\"). The Builder-based constructor is unchanged, so in-process (service-side) instances still carry the real address.",
      "ranging/framework/java/android/ranging/ble/rssi/BleRssiRangingCapabilities.java:writeToParcel: body emptied (previously dest.writeString(mBluetoothAddress)).",
      "ranging/framework/java/android/ranging/ble/rssi/BleRssiRangingCapabilities.java:BleRssiRangingCapabilities(Parcel): sets mBluetoothAddress = DEFAULT_MAC_ADDRESS instead of in.readString(); getBluetoothAddress gains @NonNull and a javadoc stating it is for internal use and clients should use BluetoothAdapter#getAddress().",
      "ranging/framework/tests/src/android/ranging/RangingFrameworkTest.java: adds testBleCsRangingCapabilitiesParcel and testBleRssiRangingCapabilitiesParcel asserting the real address survives in-process and a parcel round trip yields 00:00:00:00:00:00."
     ],
     "completeness": [
      "Both BLE capability parcelables (CS and RSSI) get the same fix on the write side and the read side, so the address is no longer serialized through these classes. Write and read must both be the patched version to stay consistent.",
      "Access inside the service is kept on purpose: instances built with the Builder or constructor still hold and return the real address. The fix therefore relies on these objects reaching apps only through Parcel serialization.",
      "Only the Parcel path is changed. Whether other fields, classes or OOB messages in the ranging framework carry the Bluetooth address is not shown in the material.",
      "The parcel format of BleCsRangingCapabilities changes (one fewer string) and BleRssiRangingCapabilities writes nothing."
     ],
     "behavior_changes": [
      "Apps receiving BleCsRangingCapabilities or BleRssiRangingCapabilities over binder now see getBluetoothAddress() == \"00:00:00:00:00:00\" instead of the device's real Bluetooth address.",
      "BleCsRangingCapabilities parcel payload shrinks by one string; BleRssiRangingCapabilities parcel payload becomes empty.",
      "BleRssiRangingCapabilities.getBluetoothAddress is now annotated @NonNull."
     ],
     "preconditions": [
      "A client obtains ranging capabilities that include BLE CS or BLE RSSI technology capabilities. BleCsRangingCapabilities is a public API gated by Flags.FLAG_RANGING_CS_ENABLED. BleRssiRangingCapabilities is @hide but is still a Parcelable.",
      "The service must have populated the Bluetooth address into these capability objects; the exact service-side code that does so is not in the material."
     ],
     "per_major": {
      "14": "not affected per bulletin; code absent (ranging framework files do not exist at android-security-14.0.0_r29).",
      "15": "not affected per bulletin; code absent (ranging framework files do not exist at android-security-15.0.0_r17).",
      "16": "same fix: commit 4a9740318035 in android-security-16.0.0_r8. The non-test source hunks are identical to Android 17; the test file hunks differ only in base and offsets.",
      "17": "same fix: commit 5dbf65f08b16 in android-security-17.0.0_r1 (cherry-pick of c8aed37c3cd8, Change-Id I9160b5d9745670f3c8d084f89ca2d7e7c67bf0b4)."
     },
     "backport_notes": [
      "Single self-contained commit. The non-test source hunks are identical on 16 and 17. Keep the write and read sides in the same change so the parcel stays aligned."
     ],
     "confidence": "high",
     "unknowns": [
      "Which service-side code fills in the Bluetooth address and which binder call returns these capability objects to apps (not in the material).",
      "Whether other ranging framework types or OOB messages also carry the Bluetooth address.",
      "Whether BleRssiRangingCapabilities.toString() or other logging paths include the real address (toString body not shown).",
      "Whether the 16-qpr2 and 17-next branches listed by the bulletin received the identical commit (only 16.0.0_r8 and 17.0.0_r1 are shown)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28624": {
     "cve": "CVE-2026-28624",
     "summary": "Removes the EXTRA_DATA Intent extra that ConfirmDeviceCredentialActivity read from its launch intent and returned unchanged as its RESULT_OK result data. FingerprintSettings now keeps the gatekeeper password handle in its own state instead of reading it from that returned Intent.",
     "component": "platform/packages/apps/Settings, password confirmation / biometric (mandatory biometrics, fingerprint) settings",
     "weakness": "Untrusted caller-supplied Intent data reflected as an activity result and trusted by the consumer as authentication data",
     "root_cause": "ConfirmDeviceCredentialActivity read an Intent from its launch intent (intent.getParcelableExtra(EXTRA_DATA, Intent.class)) into mIntentData. When the biometric prompt reported success, it returned that Intent verbatim via setResult(Activity.RESULT_OK, mIntentData). Utils.getIntentForBiometricAuthentication put a data Intent into this extra: FingerprintSettings passed in the credential-confirmation result, which carries the gatekeeper password handle, so it could get that result back after the mandatory-biometrics prompt. On BIOMETRIC_AUTH_REQUEST with RESULT_OK, FingerprintSettings.onActivityResult then called handleAuthenticationSuccessful(data), which used BiometricUtils.getGatekeeperPasswordHandle(data) and GatekeeperPasswordProvider.requestGatekeeperHat(data, ...) on that Intent. The sensitive handle therefore travelled through an Intent extra set by whoever launched the activity, and the consumer trusted whatever Intent the result carried instead of state it had set up itself.",
     "fix_analysis": [
      "src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: the EXTRA_DATA constant and the mIntentData field are deleted. The extra is no longer parsed from the launch intent, and the success callback calls setResult(Activity.RESULT_OK) with no data Intent.",
      "src/com/android/settings/Utils.java: getIntentForBiometricAuthentication(Resources, int, boolean) loses its @Nullable Intent data parameter and the intent.putExtra(EXTRA_DATA, data) call. The overload launchBiometricPromptForMandatoryBiometrics(Fragment, int, int, boolean, Intent) is removed, and the remaining Fragment and Activity overloads call the three-argument builder. The static import of EXTRA_DATA is removed.",
      "src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java: adds a long mGkPwHandle field, saved to and restored from the saved instance state under KEY_GK_PW_HANDLE (\"gk_pw_handle\"). In the onActivityResult branch where BiometricUtils.containsGatekeeperPasswordHandle(data) is true, the handle is extracted into mGkPwHandle. The mandatory-biometrics prompt is then launched without data, and the NOT_ACTIVE branch calls handleAuthenticationSuccessful(mGkPwHandle).",
      "FingerprintSettings.onActivityResult for BIOMETRIC_AUTH_REQUEST with RESULT_OK no longer uses the returned Intent. If mGkPwHandle == 0L it logs \"Gatekeeper password not set.\" and does nothing; otherwise it calls handleAuthenticationSuccessful(mGkPwHandle).",
      "FingerprintSettings.handleAuthenticationSuccessful now takes long gkPwHandle. The calls addFirstFingerprint(gkPwHandle), provider.requestGatekeeperHat(gkPwHandle, challenge, mUserId) and provider.removeGatekeeperPasswordHandle(gkPwHandle) replace the Intent-based ones."
     ],
     "completeness": [
      "The EXTRA_DATA channel is removed at the source (the constant, the parsing and the setResult payload), so no launcher of ConfirmDeviceCredentialActivity or its InternalActivity subclass can have data reflected through it.",
      "FingerprintSettings is the only consumer of the round-tripped Intent shown in the material, and it now uses its own saved state. Per index search, getIntentForBiometricAuthentication has no other caller, but that search can be incomplete.",
      "The diff does not reset mGkPwHandle to 0L after removeGatekeeperPasswordHandle or addFirstFingerprint, so the old value stays in the fragment and its saved instance state. This does not bring back the external data path, but the new 0L guard only covers the case where no handle was ever obtained.",
      "The gatekeeper password handle is now written to the fragment's saved instance state Bundle instead of an Intent sent to another activity."
     ],
     "behavior_changes": [
      "ConfirmDeviceCredentialActivity no longer returns a data Intent on RESULT_OK from the biometric prompt callback. Callers that relied on EXTRA_DATA being echoed back now get null result data.",
      "Utils.launchBiometricPromptForMandatoryBiometrics(Fragment, int, int, boolean, Intent) no longer exists, so downstream code that calls it will fail to compile.",
      "If a BIOMETRIC_AUTH_REQUEST succeeds without a stored gatekeeper password handle, FingerprintSettings now logs an error and skips token generation and first-fingerprint enrollment.",
      "The fragment's saved state gains a long under the key gk_pw_handle."
     ],
     "preconditions": [
      "Mandatory biometrics (identity check) path in FingerprintSettings: after the lock credential is confirmed, Utils.requestBiometricAuthenticationForMandatoryBiometrics returns a status other than NOT_ACTIVE (Android 17) or returns OK (Android 16). ConfirmDeviceCredentialActivity.InternalActivity is then launched for a biometric prompt, and its result comes back with requestCode BIOMETRIC_AUTH_REQUEST.",
      "A launch of ConfirmDeviceCredentialActivity (or InternalActivity) that carries an EXTRA_DATA extra, which the activity returns as its result. The material does not show who can launch it or how such a result would reach a privileged consumer."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. Only 3/8 lines of the patched region are present at android-security-14.0.0_r29, which is consistent with the EXTRA_DATA / mandatory-biometrics code not existing there (not verified from a diff).",
      "15": "Not affected per the bulletin. Only 3/8 lines of the patched region are present at android-security-15.0.0_r17, which is consistent with the EXTRA_DATA / mandatory-biometrics code not existing there (not verified from a diff).",
      "16": "Adapted fix: commit 192ccfdcab3a in android-security-16.0.0_r8. Same three files and same logic; only the surrounding code differs. getIntentForBiometricAuthentication is gated by android.hardware.biometrics.Flags.mandatoryBiometrics(). The FingerprintSettings result handler has three branches: OK launches the prompt, any other non-NOT_ACTIVE status shows IdentityCheckBiometricErrorDialog, and NOT_ACTIVE calls handleAuthenticationSuccessful(mGkPwHandle).",
      "17": "Same fix: commit d800aed5a84f in android-security-17.0.0_r1 (cherry-pick of e99f4f51fe58, Change-Id I29eb7981). Two branches: a status other than NOT_ACTIVE launches the prompt; otherwise it calls handleAuthenticationSuccessful(mGkPwHandle)."
     },
     "backport_notes": [
      "Single commit. It depends on the long-based GatekeeperPasswordProvider.requestGatekeeperHat(long, long, int) and removeGatekeeperPasswordHandle(long); per index search, GatekeeperPasswordProviderTest exercises the first. Confirm both exist in the target branch. If a fork added other callers of the removed five-argument launchBiometricPromptForMandatoryBiometrics or of EXTRA_DATA, move them to caller-held state the same way FingerprintSettings was."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not show the concrete attack scenario: which component receives the reflected Intent, or how an attacker-controlled EXTRA_DATA reaches a privileged consumer. Only the removed data flow is visible.",
      "The material does not show whether ConfirmDeviceCredentialActivity (as opposed to InternalActivity) is exported, or with what protection.",
      "The material does not show whether any other Settings surface (for example face settings) consumed the EXTRA_DATA result; it only shows FingerprintSettings.",
      "The diff does not show the name of the FingerprintSettings inner fragment class or which request code(s) lead to the branch that extracts the handle.",
      "The Change-Id of the Android 16 commit is not shown in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28626": {
     "cve": "CVE-2026-28626",
     "summary": "Adds a launching-package check (getLaunchedFromPackage() must equal CommonConstants.BASE_PACKAGE) in SetupPassthroughActivity before it forwards the embedded TV input setup intent, finishing the activity otherwise.",
     "component": "platform/packages/apps/TV (LiveTv), SetupPassthroughActivity",
     "weakness": "Intent redirection: insufficient verification of the caller before forwarding an externally supplied Intent (missing/incomplete caller check)",
     "root_cause": "SetupPassthroughActivity prepares a setup Intent from its incoming extras (InputSetupActionUtils.removeSetupIntent(extras); setupIntent.putExtras(extras)) and then forwards it. Before the fix, the only caller validation visible in the context compared getCallingActivity()'s package name to CommonConstants.BASE_PACKAGE. The fix adds a stricter launching-package check, which implies the existing caller validation was not enough to stop forwarding on behalf of untrusted launchers (commit title: Intent redirection; bulletin: EoP). The branch taken when the getCallingActivity() check fails is not shown.",
     "fix_analysis": [
      "src/com/android/tv/SetupPassthroughActivity.java: inside the try block right after setupIntent.putExtras(extras), when Build.VERSION.SDK_INT >= 34, call getLaunchedFromPackage() and require it to be non-null and equal to CommonConstants.BASE_PACKAGE. Otherwise it logs a warning (\"Calling package ... is not trusted. Not forwarding intent.\"), calls finish() and returns before any forwarding happens.",
      "The pre-existing getCallingActivity()-based check is left in place and now runs only after the new launching-package gate passes.",
      "Adds import android.os.Build for the SDK_INT gate."
     ],
     "completeness": [
      "The new check covers the forwarding path in SetupPassthroughActivity shown in the diff; no other files are touched.",
      "The check is gated on Build.VERSION.SDK_INT >= 34 because getLaunchedFromPackage() is an API 34 method (general platform knowledge). On a device running an SDK below 34 the new gate is skipped and only the original getCallingActivity() logic applies. All affected branches (14 and later) run on SDK >= 34, so the gate is active there.",
      "Trust rests only on the package name matching CommonConstants.BASE_PACKAGE. The material shows no other check (for example on the setupIntent target component or flags), and the fix adds none."
     ],
     "behavior_changes": [
      "If SetupPassthroughActivity is launched by any package other than CommonConstants.BASE_PACKAGE (or the launching package cannot be determined), it now logs a warning and finishes without forwarding the setup intent.",
      "Launches whose launching package equals CommonConstants.BASE_PACKAGE pass the new gate and continue to the existing getCallingActivity() check."
     ],
     "preconditions": [
      "An app on the device able to start SetupPassthroughActivity with extras containing a setup Intent (the InputSetupActionUtils setup intent extra). The material does not show whether the activity is exported.",
      "Device running the LiveTv app from platform/packages/apps/TV on Android 14 or later (SDK >= 34) for the new check to be active."
     ],
     "per_major": {
      "14": "same fix: identical diff (file 2a4a556f..87a66a0e) applied as 738d2cc1f03e in android-security-14.0.0_r29, resolved by change_id, 8/8 patched lines present",
      "15": "same fix: identical diff applied as f23429d7cad6 in android-security-15.0.0_r17, resolved by change_id, 8/8 patched lines present",
      "16": "same fix: identical diff applied as 856a260c2504 in android-security-16.0.0_r8, resolved by change_id, 8/8 patched lines present",
      "17": "same fix: 25c98c910675 in android-security-17.0.0_r1, resolved by change_id, 8/8 patched lines present"
     },
     "backport_notes": [
      "Single-commit, self-contained change; the same diff (identical pre/post blob hashes) is applied on 14 through 17. It uses Activity.getLaunchedFromPackage() (API 34) and CommonConstants.BASE_PACKAGE, which is already imported in the file. At runtime the SDK_INT gate skips the check on SDK below 34, but building still needs a compile SDK of 34 or higher."
     ],
     "confidence": "high",
     "unknowns": [
      "What the pre-existing code did when getCallingActivity() was null or from a foreign package (the else/forward branch is not in the provided context).",
      "Whether SetupPassthroughActivity is exported and which intent filters it declares (manifest not provided).",
      "How the setupIntent is ultimately launched (the start call and its flags are outside the shown context).",
      "The value of CommonConstants.BASE_PACKAGE."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28627": {
     "cve": "CVE-2026-28627",
     "summary": "In btm_sec_encrypt_change, a BR/EDR Encryption Change event reporting HCI_ENCRYPT_MODE_ON (E0 encryption) is now checked with btm_sec_is_enc_algo_downgrade and, if the device previously used Secure Connections, the link is disconnected with HCI_ERR_AUTH_FAILURE instead of being accepted as encrypted.",
     "component": "platform/packages/modules/Bluetooth, BR/EDR security manager (system/stack/btm/btm_sec.cc)",
     "weakness": "Missing encryption algorithm downgrade check (acceptance of a legacy E0-encrypted link where Secure Connections / AES-CCM was previously established)",
     "root_cause": "When the controller reported an Encryption Change event with encr_enable == HCI_ENCRYPT_MODE_ON on the BR/EDR transport, btm_sec_encrypt_change simply set encryption_algo to E0 and proceeded to treat the link as encrypted (btm_sec_check_pending_enc_req had already run and bta_dm_on_encryption_change was notified). No check compared this against the device's stored security properties. Per the commit message, if both host and controller had previously supported Secure Connections, a peer could force legacy authentication (non-SC link key type) and the stack would accept the weaker link as encrypted, i.e. an implicit encryption downgrade.",
     "fix_analysis": [
      "system/stack/btm/btm_sec.cc:btm_sec_encrypt_change: inside the `if (!from_key_refresh)` block, in the `encr_enable == HCI_ENCRYPT_MODE_ON` branch, a new `if (transport == BT_TRANSPORT_BR_EDR)` sub-block calls btm_sec_is_enc_algo_downgrade(handle, 0, 0).",
      "If it returns true, the fix calls acl_set_disconnect_reason(HCI_ERR_HOST_REJECT_SECURITY), then btm_sec_send_hci_disconnect(p_device, HCI_ERR_AUTH_FAILURE, handle, \"attempted to downgrade from Secure Connections mode\"), and returns early, so bta_dm_on_encryption_change is not invoked for the downgraded link.",
      "The HCI_ENCRYPT_MODE_ON_BR_EDR_AES_CCM branch and the LE path are left unchanged; the check is only added where E0 is implied on BR/EDR.",
      "The commit message lists aconfig flag com.android.bluetooth.flags.btsec_check_implicit_encryption_downgrade, but the diff itself contains no flag check; whether gating happens inside btm_sec_is_enc_algo_downgrade is not shown."
     ],
     "completeness": [
      "Covers the BR/EDR Encryption Change path with encr_enable == HCI_ENCRYPT_MODE_ON when from_key_refresh is false; the decision is fully delegated to btm_sec_is_enc_algo_downgrade(handle, 0, 0), whose implementation is not in the material.",
      "The key-refresh path (from_key_refresh == true) skips the whole block and therefore is not checked by this fix.",
      "The check runs after btm_sec_check_pending_enc_req(p_device, transport, ...) has already been called with encryption reported as enabled and after sec_flags were updated; the fix relies on the subsequent disconnect rather than preventing those earlier updates.",
      "The AES-CCM branch and LE transport are untouched (not downgrade cases by this definition)."
     ],
     "behavior_changes": [
      "BR/EDR links that become E0-encrypted while the device record indicates Secure Connections was previously in use are now disconnected with HCI reason HCI_ERR_AUTH_FAILURE and ACL disconnect reason HCI_ERR_HOST_REJECT_SECURITY.",
      "In that case bta_dm_on_encryption_change is not called, so upper layers do not receive an encryption change notification for the downgraded link.",
      "New log string: \"attempted to downgrade from Secure Connections mode\".",
      "Possibly gated by aconfig flag btsec_check_implicit_encryption_downgrade (per commit message; not visible in the diff)."
     ],
     "preconditions": [
      "A BR/EDR (classic) connection with a known device record (p_device found for the handle).",
      "The controller delivers an Encryption Change event with encr_enable == HCI_ENCRYPT_MODE_ON (E0) and from_key_refresh is false.",
      "The device record indicates Secure Connections was previously supported by both host and controller (per commit message), so that btm_sec_is_enc_algo_downgrade returns true.",
      "Per the commit message, a remote peer can bring this about by forcing legacy authentication and later claiming SC support; no further trigger details are in the material."
     ],
     "per_major": {
      "14": "not affected per bulletin; at android-security-14.0.0_r29 only 1/6 patched-region lines present, fix not applied",
      "15": "not affected per bulletin; at android-security-15.0.0_r17 only 1/6 lines present, fix not applied",
      "16": "not affected per bulletin (Android 16 not listed); at android-security-16.0.0_r8 only 1/6 lines present, fix not applied",
      "17": "same fix: commit 5819c807e810 in android-security-17.0.0_r1 (cherry-pick of 13872784a01e), 6/6 lines present"
     },
     "backport_notes": [
      "The fix depends on btm_sec_is_enc_algo_downgrade (called as btm_sec_is_enc_algo_downgrade(handle, 0, 0)), acl_set_disconnect_reason and btm_sec_send_hci_disconnect(p_device, reason, handle, comment); on older branches confirm the helper exists and its semantics (and whether it checks the btsec_check_implicit_encryption_downgrade aconfig flag) before porting the 12-line hunk.",
      "Bluetooth is a mainline module, so the fix may ship via module update rather than the platform security tag (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "Implementation of btm_sec_is_enc_algo_downgrade and the meaning of the two zero arguments.",
      "Whether the new check is actually gated by the aconfig flag named in the commit message (no flag check appears in the diff).",
      "Whether the fix was applied to the 16-qpr2 branch, which the bulletin/OSV lists as affected.",
      "What information is disclosed (bulletin type ID) and the exact remote trigger sequence beyond the commit message's description."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28630": {
     "cve": "CVE-2026-28630",
     "summary": "ContactsPickerActivity now calls window.setHideOverlayWindows(true) in onCreate, and the HIDE_OVERLAY_WINDOWS permission is added to the manifest, so that other apps' non-system overlay windows cannot be drawn over the Contacts Picker UI.",
     "component": "platform/packages/apps/ContactsPicker, ContactsPickerActivity (system Contacts Picker UI)",
     "weakness": "Missing tapjacking/overlay protection on a privacy-sensitive system UI",
     "root_cause": "ContactsPickerActivity.onCreate created its window without asking for non-system overlay windows to be hidden. Per the commit message, an app holding SYSTEM_ALERT_WINDOW could therefore draw over the picker, hide the privacy banner or show misleading content, and so mislead the user into granting the caller contact access without full disclosure.",
     "fix_analysis": [
      "src/com/android/contactspicker/ContactsPickerActivity.kt:onCreate: right after super.onCreate(savedInstanceState), and before both the savedInstanceState != null (setupComposeUi) branch and routeIntent(intent), the fix adds window.setHideOverlayWindows(true). This sets SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS in the window's LayoutParams privateFlags (the new test asserts this), so non-system overlays are hidden while this window is shown.",
      "AndroidManifest.xml: adds <uses-permission android:name=\"android.permission.HIDE_OVERLAY_WINDOWS\" />, which setHideOverlayWindows requires.",
      "tests/componenttests/.../ContactsPickerActivityTest.kt: adds onCreate_setsHideOverlayWindows, which launches the activity and asserts that attrs.privateFlags has WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS set."
     ],
     "completeness": [
      "The flag is set unconditionally at the top of onCreate, so it covers both a cold start (the routeIntent path) and activity recreation (the savedInstanceState != null path). Because the activity is singleTop, onNewIntent reuses the same window, so the flag stays set.",
      "The fix protects only the ContactsPickerActivity window. The manifest in the material declares no other activity in this app.",
      "The call depends on the manifest declaring HIDE_OVERLAY_WINDOWS, which the fix adds.",
      "The fix does not change the privacy banner or any disclosure logic. It only stops other apps' non-system overlays from being drawn over the picker."
     ],
     "behavior_changes": [
      "While ContactsPickerActivity is shown, the window manager hides other apps' non-system overlay windows (for example SYSTEM_ALERT_WINDOW overlays).",
      "ContactsPicker now requests android.permission.HIDE_OVERLAY_WINDOWS."
     ],
     "preconditions": [
      "The android.content.flags.enable_system_contacts_picker feature flag must be on. The activity is declared with android:featureFlag, and the new test is gated by RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYSTEM_CONTACTS_PICKER).",
      "An app must launch the picker, either with ACTION_PICK and a contact/phone/email MIME type or with android.provider.action.PICK_CONTACTS. Per index search, UIIntentsImpl.launchContactCardPicker in packages/apps/Messaging sends ACTION_PICK with Contacts.CONTENT_TYPE.",
      "Per the commit message, the misleading overlay needs an installed app that holds SYSTEM_ALERT_WINDOW."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. The project packages/apps/ContactsPicker is not present at android-security-14.0.0_r29 (code absent).",
      "15": "Not affected per the bulletin. The project is not present at android-security-15.0.0_r17 (code absent).",
      "16": "Not affected per the bulletin. The project is not present at android-security-16.0.0_r8 (code absent).",
      "17": "Same fix: commit f44519d86b51 (cherry-pick of 9c389023fc9b) in android-security-17.0.0_r1. All 8/8 patched lines are present."
     },
     "backport_notes": [
      "No backport is needed for 14 to 16, because the ContactsPicker project does not exist in those security tags. On 17, apply both the manifest permission and the onCreate call together."
     ],
     "confidence": "high",
     "unknowns": [
      "The material describes what the privacy banner contains, and how hiding it leads to disclosure, only in the commit message, not in the diffed code."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28631": {
     "cve": "CVE-2026-28631",
     "summary": "On Android 14/15 the fix makes IntentForwarderActivity call getWindow().setHideOverlayWindows(true) on its mini resolver window. The commit linked to Android 16/17 is a companion: it refactors IntentForwarderActivity for testability (Injector gains getExecutorService() and isDeviceProvisioned()) and re-enables IntentForwarderActivityTest, which now needs the HIDE_OVERLAY_WINDOWS permission because the activity calls setHideOverlayWindows().",
     "component": "platform/frameworks/base, core/java/com/android/internal/app/IntentForwarderActivity (cross-profile intent forwarding mini resolver)",
     "weakness": "Missing overlay-window suppression on a security-relevant confirmation UI (the mini resolver window did not set hideOverlayWindows), per the 14/15 diffs",
     "root_cause": "IntentForwarderActivity shows the R.layout.miniresolver UI when forwarding intents across profiles. On Android 14 and 15 the code called setContentView(R.layout.miniresolver) without getWindow().setHideOverlayWindows(true). The fix implies that overlay windows from other apps could stay drawn on top of this dialog. The Android 16/17 commit shown does not change runtime security logic. It fixes IntentForwarderActivityTest, which:\n- was excluded from FrameworksCoreTests_internal_app;\n- waited 30 seconds per test for an executor shutdown it never requested;\n- lacked HIDE_OVERLAY_WINDOWS, so the activity crashed in the test when calling setHideOverlayWindows();\n- had backwards assertions (assertNull on an object dereferenced elsewhere in the same check).",
     "fix_analysis": [
      "Android 14 (cb471de52128) and Android 15 (8e66b48894f7), IntentForwarderActivity.java: add getWindow().setHideOverlayWindows(true) directly after setContentView(R.layout.miniresolver). On 14 the change is near line 240 (the enclosing method is not shown). On 15 it is near line 330, in a method whose parameters include (String resolverTitle, PackageManager pmForTargetUser).",
      "Android 16/17 (71d6dc109528 / 323c8b968a88), IntentForwarderActivity.java:onCreate: mExecutorService now comes from mInjector.getExecutorService() instead of a direct Executors.newSingleThreadExecutor() call.",
      "IntentForwarderActivity.java:isDeviceProvisioned: now delegates to mInjector.isDeviceProvisioned(). The default Injector keeps the original Settings.Global.DEVICE_PROVISIONED read and the original newSingleThreadExecutor, so production behavior is unchanged.",
      "IntentForwarderActivity.java:Injector interface: two new methods, ExecutorService getExecutorService() and boolean isDeviceProvisioned().",
      "core/tests/coretests/AndroidManifest.xml: adds <uses-permission android:name=\"android.permission.HIDE_OVERLAY_WINDOWS\" /> so the activity's setHideOverlayWindows() call does not crash under test.",
      "core/tests/coretests/Android.bp: removes com.android.internal.app.IntentForwarderActivityTest from the exclude_filters of FrameworksCoreTests_internal_app, which re-enables the test.",
      "IntentForwarderActivityTest.java: TestInjector supplies a synchronous AbstractExecutorService (execute runs the task inline) and an isDeviceProvisioned() that returns true. The commit also removes the 30 s awaitTermination in IntentForwarderWrapperActivity.onCreate, removes the Settings.Global writes and the @After restore, stubs sInjector.isDeviceProvisioned() to false in shouldSkipDisclosure_duringDeviceSetup, and replaces the backwards assertNull(capturedIntents.get(i)) with assertNotNull."
     ],
     "completeness": [
      "On 14/15 the diffs add setHideOverlayWindows(true) at one site, right after setContentView(R.layout.miniresolver). The material does not show whether other UI paths in the activity exist.",
      "The Android 16/17 commit has no runtime hardening of its own. It only enables regression testing of IntentForwarderActivity. The material does not show the commit that adds setHideOverlayWindows() on 16/17, although the commit message says the activity calls it.",
      "The test commit uses a synchronous executor in the tests. Per the commit message, removing the delay exposed race conditions in the tests that the delay had been hiding. The production executor is unchanged."
     ],
     "behavior_changes": [
      "Android 14/15: while the IntentForwarderActivity mini resolver is shown, its window asks for non-system overlay windows to be hidden (setHideOverlayWindows(true)). The 16/17 test commit shows that calling this requires the HIDE_OVERLAY_WINDOWS permission: without it, the activity crashed in the test app.",
      "Android 16/17: no observable change in production behavior. The Injector interface gains two methods, which affects any out-of-tree Injector implementations. IntentForwarderActivityTest is re-enabled in FrameworksCoreTests_internal_app and no longer waits 30 s per test."
     ],
     "preconditions": [
      "An intent is routed through IntentForwarderActivity with component class FORWARD_INTENT_TO_PARENT or FORWARD_INTENT_TO_MANAGED_PROFILE (a device with a managed/work profile), per onCreate in the code context.",
      "The disclosure mini resolver (R.layout.miniresolver) is actually shown, meaning the skip-disclosure conditions (which involve isDeviceProvisioned(), per the tests) do not apply.",
      "Another app on the device can draw overlay windows while the dialog is up (implied by the nature of the 14/15 fix)."
     ],
     "per_major": {
      "14": "Adapted fix: cb471de52128 'Set hide overlays on mini resolver' adds one line, getWindow().setHideOverlayWindows(true), after setContentView(R.layout.miniresolver) in IntentForwarderActivity.java. 4/4 lines present at android-security-14.0.0_r29.",
      "15": "Adapted fix: 8e66b48894f7 adds the same line in the method whose parameters include (resolverTitle, pmForTargetUser). 4/4 lines present at android-security-15.0.0_r17.",
      "16": "Same fix as 17 (71d6dc109528, linked by bug_id): the test-enablement commit with the Injector refactor. 8/8 lines present at android-security-16.0.0_r8. The material does not show the setHideOverlayWindows change itself for 16.",
      "17": "Same fix (323c8b968a88, linked by change_id Iade781e0758c477ca888bd1541e0c53e2dee3761): the test-enablement commit with the Injector refactor. 8/8 lines present at android-security-17.0.0_r1. The material does not show the setHideOverlayWindows change itself for 17."
     },
     "backport_notes": [
      "For branches without the hardening, the essential change is the single getWindow().setHideOverlayWindows(true) call directly after setContentView(R.layout.miniresolver) in IntentForwarderActivity.java, as in the 14/15 commits. The 16/17 test commit assumes that call exists: its manifest adds HIDE_OVERLAY_WINDOWS specifically because of it. Any custom Injector implementation must also add the two new Injector methods.",
      "No 'Fix in fork' section was provided, so no per-file fork verdict can be given."
     ],
     "confidence": "medium",
     "unknowns": [
      "The commit that adds setHideOverlayWindows() to IntentForwarderActivity on Android 16/17 is not in the material. Only the test commit that refers to it is shown.",
      "The material does not say whether the bulletin's EoP rating refers to the overlay issue fixed on 14/15 or to something else.",
      "The exact location and structure of the mini resolver code on 14 (enclosing method) and on 16/17 are not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28633": {
     "cve": "CVE-2026-28633",
     "summary": "In safe mode, VoiceInteractionManagerService stops parsing the XML recognition-service metadata of third-party (non-system) RecognitionService providers and of the currently configured recognizer, so an app with oversized metadata cannot crash system_server during a safe-mode boot.",
     "component": "platform/frameworks/base, services/voiceinteraction (VoiceInteractionManagerService / RecognitionServiceInfo)",
     "weakness": "Uncontrolled resource consumption (unbounded parsing of untrusted app-supplied XML metadata in system_server, including in safe mode)",
     "root_cause": "VoiceInteractionManagerService calls RecognitionServiceInfo.parseInfo on the currently set recognizer, and RecognitionServiceInfo.getAvailableServices calls parseInfo for every RecognitionService it resolves. Neither checks whether the package is a system app or whether the device is in safe mode. parseInfo reads the service's XML metadata in system_server. The commit message says a malicious app can ship a large recognition-service metadata XML that crashes the system, and the code comments say this can cause OOM and boot problems. Because the same parsing also ran in safe mode, safe mode did not avoid the problem.",
     "fix_analysis": [
      "RecognitionServiceInfo.java:getAvailableServices gains a boolean isSafeMode parameter. Inside the ResolveInfo loop, when isSafeMode is true and resolveInfo.serviceInfo.applicationInfo.flags lacks ApplicationInfo.FLAG_SYSTEM, the code skips parseInfo entirely, adds a synthetic RecognitionServiceInfo(serviceInfo, selectableAsDefault=true, parseError=\"\") and continues. System (FLAG_SYSTEM) services are still parsed in safe mode.",
      "VoiceInteractionManagerService.java (the branch that handles an existing curRecognizer, after pm.getServiceInfo(curRecognizer, ...)): the condition `if (recognizerInfo != null)` becomes `if (recognizerInfo != null && !mSafeMode)`. In safe mode the configured recognizer's metadata is therefore not parsed, and its selectableAsDefault is not checked, whether or not it is a system package.",
      "VoiceInteractionManagerService.java (the recognizer lookup at the hunk near line 1104 on 17): the call becomes RecognitionServiceInfo.getAvailableServices(mContext, userHandle, mSafeMode), passing the service's existing mSafeMode state."
     ],
     "completeness": [
      "The fix only applies in safe mode. In a normal boot, both parseInfo call sites still parse the metadata of every installed RecognitionService provider, third-party ones included. The commit does not limit or harden the XML parsing in RecognitionServiceInfo.parseInfo itself. It relies on safe mode as the recovery path, since third-party apps cannot be launched in safe mode.",
      "The two call sites are covered differently: getAvailableServices skips only non-FLAG_SYSTEM services in safe mode, while the curRecognizer branch in VoiceInteractionManagerService skips parsing for every recognizer in safe mode.",
      "The third-party branch sets selectableAsDefault=true without parsing. The code comment says this is acceptable because only preinstalled recognizers are auto-selected, but that rule is enforced outside the diff and is not visible here.",
      "getAvailableServices' signature changed; the diff shows only one caller updated (VoiceInteractionManagerService), consistent with the 2-file diffstat."
     ],
     "behavior_changes": [
      "In safe mode, third-party recognition services appear in the available list with selectableAsDefault=true and an empty parseError, without their metadata being checked.",
      "In safe mode, a previously configured default recognizer is kept as-is, without the parse-error warning or the non-selectableAsDefault reset check.",
      "No behavior change in normal (non-safe) mode."
     ],
     "preconditions": [
      "An installed app declares a service handling the RecognitionService intent with a large recognition-service XML metadata resource (per commit message).",
      "system_server reaches the VoiceInteractionManagerService code that resolves recognizers and calls RecognitionServiceInfo.parseInfo (the commit message and comments refer to boot issues).",
      "The mitigation applies only when the device booted in safe mode (mSafeMode true)."
     ],
     "per_major": {
      "14": "same fix: commit 20ecd72dd4c8 in android-security-14.0.0_r29, identical RecognitionServiceInfo.java change (same blob hashes 05d4b9305d30..376ba571f790); VoiceInteractionManagerService.java hunks at different line offsets (567, 831). 8/8 patched lines present.",
      "15": "same fix: commit df4d44965279 in android-security-15.0.0_r17, identical content, VIMS hunks at lines 648/912. 8/8 patched lines present.",
      "16": "same fix: commit a660d0c46482 in android-security-16.0.0_r8, identical content, VIMS hunks at lines 648/912. 8/8 patched lines present.",
      "17": "same fix: commit 4c5500a0d604 in android-security-17.0.0_r1 (cherry-pick of a81b51b6a6e0), VIMS hunks at lines 818/1101. 8/8 patched lines present."
     },
     "backport_notes": [
      "Single commit with no dependencies. The RecognitionServiceInfo.java change has the same blob hashes on 14 through 17; only the VoiceInteractionManagerService.java line offsets differ. A fork that added its own callers of RecognitionServiceInfo.getAvailableServices must update them for the new isSafeMode parameter."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact crash mechanism (OOM vs other exception) and the metadata size threshold are not shown; the commit message only says large XML metadata can crash the system.",
      "Whether parseInfo was later hardened in normal mode by a separate change is not shown in the material.",
      "Where the 'only preinstalled recognizers are auto selected' guarantee is enforced is not visible in the provided context.",
      "The names of the VoiceInteractionManagerService methods containing the two patched hunks are not shown in the diff.",
      "Whether other system_server metadata parsers (e.g. AutofillServiceInfo, returned by the index search as a semantic match only) have a similar unbounded-parsing exposure is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28634": {
     "cve": "CVE-2026-28634",
     "summary": "Adds a TelephonyPermissions.checkSubscriptionAssociatedWithUser check to PhoneInterfaceManager.handleUssdRequest, so a USSD request is rejected with SecurityException when the calling user is not associated with the target subscription.",
     "component": "platform/packages/services/Telephony, PhoneInterfaceManager (ITelephony binder service), USSD handling",
     "weakness": "missing authorization check (calling user vs. subscription association)",
     "root_cause": "PhoneInterfaceManager.handleUssdRequest accepted a subId supplied by the caller. After the existing permission/feature checks, it cleared the calling identity and processed the USSD request on that subscription. It never checked that the Binder calling user is associated with the subscription. A caller running as a user not associated with the subscription could therefore have USSD requests executed on it.",
     "fix_analysis": [
      "src/com/android/phone/PhoneInterfaceManager.java:handleUssdRequest: the new check sits after the existing enforceTelephonyFeatureWithException(..., FEATURE_TELEPHONY_RADIO_ACCESS, \"handleUssdRequest\") call (on 14: after enforceCallPermission()) and before Binder.clearCallingIdentity(). It calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mApp, subId, Binder.getCallingUserHandle()). If that returns false, it throws SecurityException(\"The subscription <subId> is not associated with the calling user\").",
      "The check runs while the caller's identity is still in effect, so Binder.getCallingUserHandle() reflects the real caller. The later isValidSubscriptionId check and USSD dispatch are unchanged.",
      "tests/src/com/android/phone/PhoneInterfaceManagerTest.java: setUp now installs the mocked SubscriptionManagerService as sInstance via replaceInstance and stubs getActiveSubIdList. Two tests are added. testHandleUssdRequest_SubscriptionNotAssociatedWithUser_ThrowsSecurityException sets isSubscriptionAssociatedWithUser to return false and expects SecurityException. testHandleUssdRequest_ValidSubscription_Success sets the association to true with FEATURE_TELEPHONY_RADIO_ACCESS present; it accepts a 'deadlock' RuntimeException (from calling on the main thread) as proof the check passed. On 17 the test also reorganizes mock fields: it removes the local mPhone and mDataNetworkController mocks and moves mAppOps."
     ],
     "completeness": [
      "Covers only handleUssdRequest in PhoneInterfaceManager. The single production hunk is 5 lines.",
      "The new check runs before the existing SubscriptionManager.isValidSubscriptionId(subId) check, so invalid or DEFAULT subIds also go through checkSubscriptionAssociatedWithUser. The material does not show how that helper treats such ids.",
      "This commit does not touch other PhoneInterfaceManager entry points that take a subId. The material does not show whether they already have an equivalent user-association check."
     ],
     "behavior_changes": [
      "handleUssdRequest now throws SecurityException (\"The subscription <subId> is not associated with the calling user\") when the caller's user is not associated with the subscription. Previously the request went ahead.",
      "The association check is added on top of the preceding check visible on each branch: enforceCallPermission() on 14, enforceTelephonyFeatureWithException(FEATURE_TELEPHONY_RADIO_ACCESS) on 15, 16 and 17."
     ],
     "preconditions": [
      "Caller reaches ITelephony.handleUssdRequest(subId, ussdRequest, callback) in the phone process and passes the earlier checks visible in the material: enforceCallPermission() on 14, and enforceTelephonyFeatureWithException for FEATURE_TELEPHONY_RADIO_ACCESS on 15, 16 and 17.",
      "Device has a subscription that is not associated with the caller's user (multi-user or profile configuration), and the caller supplies that subscription's subId."
     ],
     "per_major": {
      "14": "Same production check, different anchor: commit 2a177c06ec48 in android-security-14.0.0_r29. The identical 5-line check is inserted directly after enforceCallPermission(); handleUssdRequest has no enforceTelephonyFeatureWithException call on this branch. The test file gained extra mocks/imports to support the new tests (AppOpsManager, AudioManager, SatelliteController, SetFlagsRule, UiccController, PinStorage). Patched region: 8/8 lines present.",
      "15": "Same fix: commit 83d27ac3f32f in android-security-15.0.0_r17. Identical production hunk after enforceTelephonyFeatureWithException. Test setUp also stubs mPhone's HalVersion, context and DataNetworkController, plus getDefaultPhone. Patched region: 8/8 lines present.",
      "16": "Same fix: commit 03cc6ee76027 in android-security-16.0.0_r8. Identical production hunk. The test removes the local Phone mock and adds the two USSD tests. Patched region: 8/8 lines present.",
      "17": "Same fix: commit 5ef227b0a2f7 in android-security-17.0.0_r1, cherry-picked from b76670f3c5ba and 1336ed741dc4. Identical production hunk; test mock fields reorganized. Patched region: 8/8 lines present."
     },
     "backport_notes": [
      "The production change is self-contained (5 lines) and needs TelephonyPermissions.checkSubscriptionAssociatedWithUser to be available. On 14 the preceding line is enforceCallPermission() rather than enforceTelephonyFeatureWithException, so anchor on the Binder.clearCallingIdentity() line of handleUssdRequest.",
      "The new tests need replaceInstance(SubscriptionManagerService.class, \"sInstance\", ...), a mocked SubscriptionManagerService.isSubscriptionAssociatedWithUser, and a mockable SubscriptionManager from mContext. Forks with a different PhoneInterfaceManagerTest setup are most likely to need to adapt the test hunk."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the implementation of TelephonyPermissions.checkSubscriptionAssociatedWithUser (for example how it handles invalid subIds, or any system/shell exemptions).",
      "The material does not show whether other subId-based ITelephony methods already enforce user association.",
      "The material does not show whether handleUssdRequest calls enforceCallPermission on 15+ (it appears only in the 14 diff context), or what permission that check enforces."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28638": {
     "cve": "CVE-2026-28638",
     "summary": "MediaProvider now includes 'XMP_' boxes in IsoInterface's flattened box list, and XmpDataParser now redacts XML elements whose EXIF attributes (not only element names) carry sensitive tags such as GPSLatitude, closing a location redaction bypass for ISO media files.",
     "component": "platform/packages/providers/MediaProvider, media metadata redaction (IsoInterface / XmpDataParser)",
     "weakness": "Incomplete redaction of sensitive data: the parser skipped one container box type, and the redaction matcher checked only element names, not attributes",
     "root_cause": "There were two independent gaps. (1) In IsoInterface.parseNextBox, the switch that decides which boxes go into the flattened list (mFlattened) listed BOX_META, BOX_HDLR, BOX_XYZ, BOX_LOCI, BOX_GPS and BOX_GPS0, but not BOX_XMP ('XMP_', 0x584d505f). Lookups by box type search the flattened list, so XMP data stored in an 'XMP_' box was never found, and so was neither parsed nor redacted. (2) In XmpDataParser.createXmpInterface and XmpDataParser.getRedactionRanges, an element was redacted only when the element itself was in NS_EXIF and its name was in RedactionUtils.getsRedactedExifTags(). Sensitive EXIF fields written as attributes on an element were never matched, so location data stored that way survived redaction. Per the commit message, this let apps without ACCESS_MEDIA_LOCATION get around the privacy controls.",
     "fix_analysis": [
      "IsoInterface.java (parseNextBox box-flattening switch): adds `case BOX_XMP:`, so 'XMP_' boxes are appended to mFlattened alongside the other XMP/GPS-related boxes and lookups by BOX_XMP can now find them. The unused ArrayDeque/Queue imports are removed (cleanup only).",
      "XmpDataParser.java: a new private helper, isSensitiveTagOrAttribute(), returns true when the current START_TAG is in NS_EXIF with a name in RedactionUtils.getsRedactedExifTags(), or when any of its attributes (index i < getAttributeCount()) has namespace NS_EXIF and a name in that set.",
      "XmpDataParser.createXmpInterface(): the former `else if (NS_EXIF ... contains(name))` branch is moved out of the metadata else-if chain into a standalone `if (isSensitiveTagOrAttribute())`. Metadata extraction (format/documentId/instanceId/originalDocumentId) still runs, and then the element, from its start offset up to the next END_TAG with the same name, is overwritten with spaces in builder.mRedactedXmp.",
      "XmpDataParser.getRedactionRanges(): the element-name check is replaced with isSensitiveTagOrAttribute() in the same way, so elements that match on an attribute now count for redaction too.",
      "Tests: add tests/res/raw/gps_test_xmp.mp4 and XmpDataParserTest.testRedactXmp_RemovesExifGpsAttributes. The test asserts that the redacted XMP from XmpDataParser.createXmpInterface(IsoInterface.fromFile(...)) contains neither 'exif:GPSLatitude' nor 'exif:GPSLongitude'."
     ],
     "completeness": [
      "The fix covers both the ISO container path and the ExifInterface path, because getRedactionRanges(ExifInterface) also goes through XmpData.extractXmpData and the shared XmpDataParser logic.",
      "Attribute matching is limited to the NS_EXIF namespace and the names in RedactionUtils.getsRedactedExifTags(). Attributes in other namespaces are not checked, which is the same scope as the existing element-name check.",
      "When a sensitive attribute is found, the whole containing element is redacted, not just the attribute, so other metadata inside that element is blanked too. This errs on the side of redacting too much.",
      "On 15/16/17 the element-skipping loop stops at the first END_TAG whose name equals the start tag's name, and it does not track nesting depth. In the Android 14 variant (XmpInterface.process), the attribute case uses a depth counter instead.",
      "On 15/16/17, XmpData.extractXmpData still reads only the first XMP UUID box, falling back to the first 'XMP_' box. Further XMP boxes in the same file are not processed. The Android 14 fix instead adds getBoxBytesList/getBoxRangesList and processes every XMP box."
     ],
     "behavior_changes": [
      "'XMP_' boxes in ISO files are now present in the flattened list and get parsed. Files whose XMP lives only in an 'XMP_' box now produce XMP-derived metadata (format, documentId, instanceId, originalDocumentId) and redaction ranges.",
      "Any XML element that carries an EXIF attribute from the redacted tag set is now blanked in full (all its attributes and children) in the redacted XMP copy. Before the fix, such elements were left untouched.",
      "For the same file, getRedactionRanges() can now return more or larger ranges than before."
     ],
     "preconditions": [
      "An ISO base media file (e.g. MP4) is served through MediaProvider to a caller for whom location redaction applies. The commit message cites apps without ACCESS_MEDIA_LOCATION.",
      "The file's XMP is stored in an 'XMP_' box (previously ignored by IsoInterface), or the XMP holds EXIF GPS fields as XML attributes (previously not matched by XmpDataParser)."
     ],
     "per_major": {
      "14": "Adapted fix (15168c2e6f84, android-security-14.0.0_r29). Android 14 has no XmpDataParser; the attribute check (hasSensitiveAttribute, with depth-aware skipping) is added to XmpInterface.process(). The commit makes no flattening-switch change. Instead it adds getBoxRangesList/getBoxBytesList (by type and by UUID), and XmpInterface.fromContainer processes every XMP UUID box and every 'XMP_' box. The test is added to XmpInterfaceTest.",
      "15": "Adapted fix (cb04f3531ff2, android-security-15.0.0_r17). It bundles a refactor of IsoInterface to selective flattening (mRoots/children removed; MAX_XMP_SIZE_BYTES, allocateBuffer, XMP_UUID, getBoxBytesForXmpUuid/getBoxRangesForXmpUuid added), with BOX_XMP in the new switch. It also carries the same XmpDataParser isSensitiveTagOrAttribute change, with extractXmpData moved to the new getters. The resulting XmpDataParser.java blob (3e48d1d91) is identical to the one on 16/17.",
      "16": "Same fix (4bdbc2fa9f53, android-security-16.0.0_r8). The diff is identical to the 17 commit, with the same pre- and post-image blob hashes for IsoInterface.java and XmpDataParser.java.",
      "17": "The fix analysed here (11009f3800f9, android-security-17.0.0_r1). All 8 lines of the patched region are present."
     },
     "backport_notes": [
      "The one-line `case BOX_XMP:` change applies only to trees whose IsoInterface.parseNextBox already has the selective-flattening switch. The 15 commit shows the refactor that introduces the switch bundled with the fix. The 14 commit has no switch change and instead adds list-based box getters.",
      "There are two code shapes. On 14, XmpInterface.process uses depth-aware skipping and processes every XMP box. On 15+, XmpDataParser.isSensitiveTagOrAttribute uses END_TAG name matching and extracts only the first box. Use the shape that matches the parser class in the target tree."
     ],
     "confidence": "high",
     "unknowns": [
      "The contents of RedactionUtils.getsRedactedExifTags() are not shown, so the full set of attribute names now matched is unknown.",
      "The material does not show when, or in which change, BOX_XMP went missing from the flattening switch on 16/17. The 15 commit introduces the switch with BOX_XMP already included.",
      "How Android 14's IsoInterface builds its flattened list is not shown.",
      "Whether location data in XMP namespaces other than NS_EXIF is within the scope of this CVE is not shown.",
      "How the MAX_XMP_SIZE_BYTES limit applies to 'XMP_' boxes on 17 is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28650": {
     "cve": "CVE-2026-28650",
     "summary": "WindowState.setHiddenWhileSuspended now checks the window's base type (getBaseType(), which for sub-windows reflects the top parent window per the commit message) instead of mAttrs.type when deciding whether to hide a window while its owning package is suspended. Sub-windows attached to a system alert window are therefore hidden too.",
     "component": "platform/frameworks/base, WindowManager (services/core/java/com/android/server/wm/WindowState.java)",
     "weakness": "Incomplete check: the window-type check used the sub-window's own type instead of the base (top parent) type, so a security hide could be skipped (CWE-863-like)",
     "root_cause": "setHiddenWhileSuspended returns early when mOwnerCanAddInternalSystemWindow is true, or when the window is neither a system alert window type nor TYPE_TOAST. That type check used mAttrs.type. For a sub-window (for example TYPE_APPLICATION_PANEL) whose top parent is a TYPE_APPLICATION_OVERLAY window, isSystemAlertWindowType does not match mAttrs.type, so the method returned early. The sub-window was then neither hidden when the app was suspended nor tracked via mHiddenWhileSuspended. In the code context, the sibling method setForceHideNonSystemOverlayWindowIfNeeded already uses getBaseType() for a similar type check.",
     "fix_analysis": [
      "services/core/java/com/android/server/wm/WindowState.java:setHiddenWhileSuspended: computes `final int baseType = getBaseType();` and puts baseType in place of both uses of mAttrs.type in the early-return condition. `isSystemAlertWindowType(baseType)` and `baseType != TYPE_TOAST` are therefore evaluated against the base type. The rest of the method is unchanged: the mHiddenWhileSuspended dedupe check and the hide()/show() calls with doAnimation=true, requestAnim=true.",
      "services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java: adds testSubWindowHiddenWhenSuspended. It builds a TYPE_APPLICATION_OVERLAY parent with a TYPE_APPLICATION_PANEL sub-window and verifies that setHiddenWhileSuspended(true/false) calls hide(true, true) and then show(true, true) on the sub-window."
     ],
     "completeness": [
      "Covers sub-windows whose base type is a system alert window type or TYPE_TOAST. The test checks only a TYPE_APPLICATION_PANEL child of a TYPE_APPLICATION_OVERLAY parent.",
      "Only setHiddenWhileSuspended is changed. setForceHideNonSystemOverlayWindowIfNeeded already used getBaseType() and is not part of the diff.",
      "The adjacent setHiddenWhileLockedByAppLock (gated by the android.security.Flags.appLockCore() flag) still uses mAttrs.type in the same condition, and this commit does not touch it (see potential_variants).",
      "Only the gating condition is changed. The caller RootWindowContainer.updateHiddenWhileSuspendedState (per index search) and the hide/show mechanics are unchanged."
     ],
     "behavior_changes": [
      "Sub-windows whose base type is a system alert window type or toast, owned by a window with mOwnerCanAddInternalSystemWindow false, are now hidden when the package is suspended and shown again when it is unsuspended. Before the fix the hide/show was skipped.",
      "mHiddenWhileSuspended is now tracked for such sub-windows.",
      "No API, error code or flag changes. The change is unconditional (Flag: EXEMPT BUGFIX)."
     ],
     "preconditions": [
      "The window has mOwnerCanAddInternalSystemWindow false.",
      "The window is a sub-window attached to a parent whose type is a system alert window type (for example TYPE_APPLICATION_OVERLAY) or TYPE_TOAST.",
      "The owning package becomes suspended. RootWindowContainer.updateHiddenWhileSuspendedState then calls w.setHiddenWhileSuspended(suspended) for each window whose owning package is in the suspended set (per index search)."
     ],
     "per_major": {
      "14": "same fix: ce479a9a57bd in android-security-14.0.0_r29. The WindowState.java hunk is identical; the test is adapted to the createWindow(parent, type, name) helper.",
      "15": "same fix: 8130b6057817 in android-security-15.0.0_r17. The WindowState.java hunk is identical; the test is adapted to the createWindow helper.",
      "16": "same fix: b61562e1abe6 in android-security-16.0.0_r8. The WindowState.java hunk is identical; the test uses newWindowBuilder, as on 17.",
      "17": "same fix: 236258a3e304 in android-security-17.0.0_r1 (Change-Id/Merged-In Ia0c154fca56db7c0c89abc2ea09af600d4990323; Cherrypick-From 617b18b15e67 and 767984f9160c)"
     },
     "backport_notes": [
      "One commit with a two-line functional change in WindowState.setHiddenWhileSuspended. It uses getBaseType() and isSystemAlertWindowType, which the diffs for Android 14 through 17 also use. Only the test helper differs: createWindow on 14/15, newWindowBuilder on 16/17."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not state the concrete security impact beyond the commit message's 'security concerns' and the bulletin's High/EoP rating.",
      "The getBaseType() implementation is not shown, so its exact result for every window/sub-window combination is not confirmed.",
      "The material does not show whether a separate change addresses setHiddenWhileLockedByAppLock, which still uses mAttrs.type."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28652": {
     "cve": "CVE-2026-28652",
     "summary": "Adds enforceRangingPermission() checks to five Binder-exposed out-of-band (OOB) ranging methods in RangingServiceImpl (oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed, registerOobSendDataListener) that previously forwarded to RangingServiceManager without calling enforceRangingPermission().",
     "component": "platform/packages/modules/Uwb, ranging service (com.android.server.ranging.RangingServiceImpl, IRangingAdapter.Stub)",
     "weakness": "missing permission check on Binder-exposed service methods",
     "root_cause": "RangingServiceImpl extends IRangingAdapter.Stub and is the Binder entry point for the ranging service. Most of its methods (e.g. reconfigureInterval, addRawDevice, addOobDevice, removeDevice, stopRanging, as seen in the surrounding context) call enforceRangingPermission() before delegating to mRangingInjector.getRangingServiceManager(). The five OOB-related methods oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed and registerOobSendDataListener did not. So a Binder caller able to reach the service could, at least at this entry point, pass OOB data, signal OOB connection state changes for an OobHandle, or register an IOobSendDataListener without the ranging permission check. The bulletin classifies the result as information disclosure (ID), High.",
     "fix_analysis": [
      "ranging/service/java/com/android/server/ranging/RangingServiceImpl.java:oobDataReceived - adds enforceRangingPermission() as the first statement, before forwarding oobHandle and data to RangingServiceManager.oobDataReceived.",
      "RangingServiceImpl.java:deviceOobDisconnected - adds enforceRangingPermission() before RangingServiceManager.deviceOobDisconnected.",
      "RangingServiceImpl.java:deviceOobReconnected - adds enforceRangingPermission() before RangingServiceManager.deviceOobReconnected.",
      "RangingServiceImpl.java:deviceOobClosed - adds enforceRangingPermission() before RangingServiceManager.deviceOobClosed.",
      "RangingServiceImpl.java:registerOobSendDataListener - adds enforceRangingPermission() before RangingServiceManager.registerOobSendDataListener(oobSendDataListener), so a caller without the permission can no longer register the listener through this entry point.",
      "The fix brings these methods in line with the existing pattern used by the other IRangingAdapter methods in the same class; enforceRangingPermission() itself is not changed by this commit."
     ],
     "completeness": [
      "Covers all five OOB methods named in the commit message; after the fix every method visible in the provided context of RangingServiceImpl begins with enforceRangingPermission().",
      "The fix is purely at the Binder entry point; RangingServiceManager and its OOB handling are not changed, so no additional validation of oobHandle ownership or caller identity is introduced beyond the permission check.",
      "Whether any other IRangingAdapter methods outside the shown context (lines 85-144) lack the check is not visible in the material.",
      "Single commit, single file; the Android 16 commit is the same change at a different line offset."
     ],
     "behavior_changes": [
      "Callers of oobDataReceived, deviceOobDisconnected, deviceOobReconnected, deviceOobClosed and registerOobSendDataListener that do not pass enforceRangingPermission() now fail the check (as for the other RangingServiceImpl methods) instead of having the call forwarded to RangingServiceManager.",
      "Permitted callers see no change."
     ],
     "preconditions": [
      "Device runs the ranging service from the Uwb module (RangingServiceImpl present; absent in Android 14 and 15 security tags per per-major facts).",
      "Caller can obtain the IRangingAdapter Binder interface and invoke one of the five OOB methods; before the fix these methods did not call enforceRangingPermission()."
     ],
     "per_major": {
      "14": "not affected per bulletin; code absent (RangingServiceImpl.java does not exist at android-security-14.0.0_r29).",
      "15": "not affected per bulletin; code absent (RangingServiceImpl.java does not exist at android-security-15.0.0_r17).",
      "16": "same fix: commit 618d9adfcd65 in android-security-16.0.0_r8 adds the same five enforceRangingPermission() calls, hunk at line 102 instead of 113; 8/8 patched lines present.",
      "17": "same fix: commit 3be00374c294 in android-security-17.0.0_r1 (cherry-pick of two internal commits, Change-Id I9d2f34330258d00b4d19e57f330c58fdde021846); 8/8 patched lines present."
     },
     "backport_notes": [
      "Self-contained five-line addition relying only on the existing enforceRangingPermission() helper in RangingServiceImpl; on trees where the ranging service exists, apply by inserting the call at the top of each of the five OOB methods. Uwb is a mainline module, so updates may also ship via module update rather than platform tag (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "Which specific permission enforceRangingPermission() checks, how it fails (exception type), and its implementation are not shown in the material.",
      "What information is disclosed in practice (the bulletin only gives type ID); the material does not show how RangingServiceManager handles the OOB calls or what data the IOobSendDataListener receives.",
      "Whether RangingServiceManager performed any caller checks of its own before the fix.",
      "Whether IRangingAdapter exposes further methods beyond the shown context that may or may not carry the check."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28655": {
     "cve": "CVE-2026-28655",
     "summary": "RemoteViews parceling is reworked: one ApplicationInfoCache list is written once at the hierarchy root and nested RemoteViews refer to it by integer id. The Parcel.allowSquashing and forced-immediate-Bundle-unparcel mechanism in RemoteViews.writeToParcel and ReflectionAction is removed.",
     "component": "platform/frameworks/base, core/java/android/widget/RemoteViews.java (RemoteViews parcel serialization, ApplicationInfoCache, ReflectionAction)",
     "weakness": "Not fully evident from the diff. The fix targets inconsistent Parcel serialization and deserialization of RemoteViews: Parcel squashing of ApplicationInfo, a ReadWriteHelper trick that forces eager Bundle unparceling, and one full ApplicationInfo written per nested RemoteViews. The bulletin rates it EoP, High.",
     "root_cause": "Before the fix:\n- RemoteViews.writeToParcel turned on Parcel squashing (dest.allowSquashing() / restoreAllowSquashing).\n- Every MODE_NORMAL RemoteViews node wrote its own mApplication with dest.writeTypedObject(mApplication, flags) and read it back with parcel.readTypedObject(ApplicationInfo.CREATOR).\n- The removed code comment says squashing changes how Bundle contents are written. So ReflectionAction's BUNDLE case set a private Parcel.ReadWriteHelper (ALTERNATIVE_DEFAULT) to make Bundle.readFromParcel unparcel immediately instead of lazily.\n- The second commit message says squashing was added for ApplicationInfo but 'doesn't work', which is why mApplicationInfoCache (an in-memory Map keyed by package name and uid) was also added. Before commit 1 that cache was not written to the parcel.\n\nThe fix removes this combination of per-node ApplicationInfo serialization, squashing and the Bundle ReadWriteHelper workaround.",
     "fix_analysis": [
      "Commit 1 (56f83ee5f1f5, Revert^2 ApplicationInfoCache), changes to RemoteViews.java:ApplicationInfoCache: the cache changes from Map<Pair<String,Integer>,ApplicationInfo> to List<ApplicationInfo> mCachedApplicationInfos. getOrPut now returns an int id. A new findId(ApplicationInfo) does a linear match on packageName and uid. put is replaced by updateAndGetId, which replaces or appends an entry and returns its id. get(int id) bounds-checks and returns null when id < 0 or id >= size. The commit adds an ApplicationInfoCache(Parcel) constructor (parcel.createTypedArrayList(ApplicationInfo.CREATOR)) and a writeToParcel method (dest.writeTypedList). The static helper getPackageUserKey is deleted.",
      "Commit 1: a new field RemoteViews.mApplicationId (default -1) is set wherever mApplication is assigned. That covers: the RemoteViews(ApplicationInfo, int) constructor; the landscape/portrait constructor; the sized-RemoteViews constructor; the constructor that copies from a source RemoteViews 'src'; the parcel constructor (sized and landscape/portrait branches); the code that refreshes mApplication from the root cache before propagating to descendants (getOrPut then get); the root reconfiguration path (new ApplicationInfoCache(), then getOrPut/get, then configureDescendantsAsChildren()); and the proto restore path (rv.mApplicationId = rv.mApplicationInfoCache.updateAndGetId(rv.mApplication)).",
      "Commit 1, write side, RemoteViews.java:writeToParcel(Parcel, int, SparseArray<Intent>): at the root, the cache is now written once with mApplicationInfoCache.writeToParcel(dest, flags). This happens in the MODE_NORMAL, MODE_HAS_SIZED_REMOTEVIEWS and landscape/portrait branches, right after mBitmapCache.writeBitmapsToParcel. In MODE_NORMAL, dest.writeInt(mApplicationId) replaces dest.writeTypedObject(mApplication, flags).",
      "Commit 1, read side: the parcel constructor reads mApplicationInfoCache = new ApplicationInfoCache(parcel) at the root, after mClassCookies and before RemoteCollectionCache. In MODE_NORMAL it reads mApplicationId = parcel.readInt(), then mApplication = mApplicationInfoCache.get(mApplicationId).",
      "Commit 1: RemoteViews.java:updateAppInfo now finds the existing entry with mApplicationInfoCache.get(mApplicationInfoCache.findId(info)) and stores the new one with updateAndGetId(info) before calling configureDescendantsAsChildren().",
      "Commit 2 (b78e05338801, applied on top of commit 1, base index baaaee43c352): RemoteViews.java:writeToParcel drops 'boolean prevSquashingAllowed = dest.allowSquashing();' and 'dest.restoreAllowSquashing(prevSquashingAllowed);'. RemoteViews no longer turns on Parcel squashing while serializing.",
      "Commit 2: the BUNDLE case in ReflectionAction's parcel reading is reduced to 'this.mValue = in.readBundle();'. The hasReadWriteHelper / setReadWriteHelper(ALTERNATIVE_DEFAULT) / setReadWriteHelper(null) sequence is deleted, and so is the static field ALTERNATIVE_DEFAULT (Parcel.ReadWriteHelper)."
     ],
     "completeness": [
      "Commit 1 updates every mApplication assignment visible in the diff, plus updateAppInfo and the write and read paths of all three parcel branches. Root and nested nodes therefore share one serialized ApplicationInfoCache.",
      "ApplicationInfoCache.get(int) bounds-checks the id and returns null instead of throwing. A nested node whose id in the parcel is invalid therefore gets mApplication == null, and the diff does not validate this further.",
      "findId is a linear scan over the list that matches packageName (Objects.equals) and uid. updateAndGetId and getOrPut prevent duplicate keys, but a cache list read from a parcel is not deduplicated or checked beyond typed-list parsing.",
      "Commit 2 removes squashing only from RemoteViews.writeToParcel, and the Bundle eager-unparcel workaround only from ReflectionAction's BUNDLE case. Other Parcel.allowSquashing users in the platform are not part of this fix, and the material shows none.",
      "For Android 14 and 16 the material shows only the allowSquashing-removal commit, and for Android 15 only the ApplicationInfoCache commit. Whether the sibling commit also landed on those branches is not shown."
     ],
     "behavior_changes": [
      "Where commit 1 is present (shown for 17 and 15), the RemoteViews parcel wire format changes. The root writes a typed list of ApplicationInfo after the bitmap cache and before the RemoteCollectionCache, and each MODE_NORMAL node writes an int id instead of a full ApplicationInfo. Patched and unpatched frameworks therefore cannot read each other's RemoteViews parcels.",
      "Parcel squashing is no longer turned on during RemoteViews serialization. Where commit 1 is present, ApplicationInfo deduplication relies only on the ApplicationInfoCache list. If a branch has commit 2 without commit 1, each MODE_NORMAL node writes its own ApplicationInfo without squashing.",
      "Bundles carried by ReflectionAction are now read with plain in.readBundle(). Per the removed comment, they are no longer forced to unparcel immediately when read and are handled like ordinary Bundles.",
      "With commit 1, a nested RemoteViews whose stored id is outside the cache range gets mApplication = null instead of its own deserialized ApplicationInfo."
     ],
     "preconditions": [
      "A RemoteViews object must be serialized and deserialized across a process boundary (RemoteViews is parcelable per RemoteViews.aidl). Per index search, cross-process paths include RemoteViewsAdapter.updateRemoteViews, which receives RemoteViews from IRemoteViewsFactory.getViewAt, and app-widget hosts such as Launcher3's LauncherAppWidgetHostView.updateAppWidget.",
      "The affected code paths are nested RemoteViews hierarchies (children, landscape/portrait, sized RemoteViews) where ApplicationInfo was written per node, and ReflectionAction entries of type BUNDLE.",
      "The material does not state the exact trigger conditions of the EoP."
     ],
     "per_major": {
      "14": "adapted fix. Only the 'Remove usage of Parcel.allowSquashing' commit is shown (ae484e3d7a20, android-security-14.0.0_r29). It is adapted to the older code: public writeToParcel(Parcel, int) without intentsToIgnore, a ReflectionAction field named 'value' instead of 'mValue', and no mHasDrawInstructions write. The ApplicationInfoCache serialization commit is not shown for this branch. 4/8 lines of the patched region are present at r29.",
      "15": "adapted fix. Only the Revert^2 'Writing ApplicationInfoCache instead of multiple ApplicationInfos' commit is shown (04faeb30ca94, android-security-15.0.0_r17). Its content matches the 17 commit except that the shown diff has no proto-restore hunk. The allowSquashing-removal commit is not shown for this branch. 6/8 lines are present at r17.",
      "16": "adapted fix. Only the 'Remove usage of Parcel.allowSquashing' commit is shown (6fe346abe6fb, android-security-16.0.0_r8). It is nearly identical to 17 but has no mOriginalDensity line. The ApplicationInfoCache serialization commit is not shown for this branch. 4/8 lines are present at r8.",
      "17": "same fix. Both commits are in android-security-17.0.0_r1 as cherry-picks: 56f83ee5f1f5 (ApplicationInfoCache serialization, Revert^2), then b78e05338801 (allowSquashing removal)."
     },
     "backport_notes": [
      "Apply the ApplicationInfoCache commit first and the allowSquashing-removal commit second: the second commit's base index, baaaee43c352, is the result of the first. The second commit is small and mechanical. The first touches every mApplication assignment and the parcel format. When porting it, check that each constructor path (sized, landscape/portrait, copy, parcel) sets mApplicationId, and that writeToParcel and the parcel constructor write and read the cache at the same position in all three branches.",
      "On 14 the file differs (public writeToParcel(Parcel, int), ReflectionAction.value, no draw instructions or original density), so the hunks must be placed by hand."
     ],
     "confidence": "medium",
     "unknowns": [
      "The material does not describe the exact vulnerability mechanism, that is, how squashing, per-node ApplicationInfo and eager Bundle unparceling together lead to EoP.",
      "Whether the second commit (allowSquashing removal) is on Android 15, and whether the first commit (ApplicationInfoCache serialization) is on Android 14 and 16. Only one commit per branch is shown.",
      "The content of the reverted commit 532a0a90b30a, and what the 'original bug' fixed by the Revert^2 was.",
      "Whether consumers of Parcel.allowSquashing or of ApplicationInfoCache outside RemoteViews.java needed changes.",
      "Whether the proto-restore code path exists on Android 15."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28657": {
     "cve": "CVE-2026-28657",
     "summary": "AppWidgetConfigActivityProxy.onActivityResult now forwards only the extras of the configuration activity's result Intent (via a fresh Intent().putExtras(data)) instead of the original Intent. URI permission grant flags and the data URI from the widget provider are therefore no longer relayed through the system-privileged proxy's setResult().",
     "component": "platform/frameworks/base, AppWidget (core/java/android/appwidget/AppWidgetConfigActivityProxy.java)",
     "weakness": "Confused deputy / improper privilege management: a system-privileged proxy activity relayed an untrusted result Intent, including FLAG_GRANT_*_URI_PERMISSION flags, through its own setResult(), causing URI permissions to be granted on the system's authority",
     "root_cause": "AppWidgetConfigActivityProxy is a @hide activity. Per the commit message, it is system-privileged and its PendingIntent is created by the system. It starts the widget provider's configuration activity with startActivityForResultSafely(target, CONFIG_ACTIVITY_REQUEST_CODE). In onActivityResult it called setResult(resultCode, data) with the exact Intent returned by that configuration activity. Per the commit message, URI permission grant flags in that Intent caused the system to grant those permissions, so a malicious widget provider could obtain URI permissions it was not entitled to.",
     "fix_analysis": [
      "core/java/android/appwidget/AppWidgetConfigActivityProxy.java:onActivityResult: replaces setResult(resultCode, data) with `Intent result = data != null ? new Intent().putExtras(data) : null; setResult(resultCode, result);`. The new Intent carries only the extras Bundle. Flags, data URI, MIME type and the other non-extras fields of the untrusted Intent are dropped. The widgetId lookup and the AppWidgetManager.setConfigActivityComplete(widgetId) call are unchanged.",
      "Same file: removes the imports android.view.View, ViewGroup, WindowManager and WindowManagerWrapper (no functional effect).",
      "core/tests/coretests/src/android/appwidget/AppWidgetConfigActivityProxyTest.kt (new): FakeConfigWithFlagsActivity returns a result with content:// data and all four FLAG_GRANT_* flags. Test onActivityResult_stripsUriPermissions asserts resultData.flags == 0 and resultData.data == null. Test onActivityResult_preservesAppWidgetIdExtra asserts that the EXTRA_APPWIDGET_ID extra survives.",
      "core/tests/coretests/AndroidManifest.xml: registers FakeConfigActivity, FakeConfigWithFlagsActivity and AppWidgetConfigActivityProxy as exported activities in the test APK. core/tests/coretests/Android.bp: adds test_module_config FrameworksCoreTests_android_appwidget (filter android.appwidget.). core/tests/coretests/src/android/appwidget/TEST_MAPPING: adds it to presubmit."
     ],
     "completeness": [
      "The fix changes the result-forwarding path in onActivityResult. Everything except the extras is dropped, so it does not depend on a flag mask. The visible code shows only onCreate and onActivityResult; the rest of the file is not in the material.",
      "The extras Bundle is copied verbatim, with no content filtering. The material does not show whether objects inside the extras could affect URI grants.",
      "The onCreate path (setResult(RESULT_CANCELED) with no Intent, then startActivityForResultSafely on the EXTRA_INTENT target) is untouched. The material does not show startActivityForResultSafely or how the proxy's launching PendingIntent is built.",
      "Test coverage: the tests assert that flags and data are stripped. Stripping of other fields follows from building a new Intent but is not asserted."
     ],
     "behavior_changes": [
      "The host now receives a result Intent that contains only extras. Action, data URI, MIME type, flags, component and the other non-extras fields set by the configuration activity no longer reach the host through the proxy.",
      "If the configuration activity returned a non-null Intent with no extras, the host now gets an empty non-null Intent. A null result Intent is still forwarded as null.",
      "No new error codes or exceptions. The result code is passed through unchanged."
     ],
     "preconditions": [
      "A widget provider's configuration activity is launched through AppWidgetConfigActivityProxy (per the commit message, via a PendingIntent created by the system).",
      "The attacker controls the widget provider app whose configuration activity returns a result Intent with URI permission grant flags.",
      "The build must contain AppWidgetConfigActivityProxy (the file carries a 2025 copyright; the bulletin lists only 16-qpr2, 17 and 17-next as affected)."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. The resolver reports 7/8 lines of the patched region at android-security-14.0.0_r29. That figure does not discriminate, because the fixed 17 tag also shows 7/8. The material does not confirm whether the file exists in 14.",
      "15": "Not affected per the bulletin. The resolver reports 7/8 lines at android-security-15.0.0_r17. Same caveat as 14.",
      "16": "Not affected per the bulletin for base 16 (android-security-16.0.0_r8 shows 7/8 lines, same caveat). The bulletin lists 16-qpr2 as affected, but no 16-qpr2 fix commit is in the material.",
      "17": "Same fix: commit 7d46aaa9199b in android-security-17.0.0_r1 (cherry-pick of internal e2c75d70260a / 80e84dab91f6, Change-Id I770b26d0e28e1090abae452e3bc00af51bf9b30e), linked by bug 498916138. The resolver shows 7/8 lines at the fixed tag."
     },
     "backport_notes": [
      "Single self-contained commit. The functional change is the replacement in AppWidgetConfigActivityProxy.onActivityResult and applies wherever that file exists (16-qpr2 and later per the bulletin). Drop the android.view imports only if nothing else in the target file uses them.",
      "The test additions use Kotlin and androidx ActivityScenario in FrameworksCoreTests and register the @hide proxy activity in the test APK manifest. They can be adapted or left out if the target branch lacks that setup."
     ],
     "confidence": "high",
     "unknowns": [
      "How, and by which component, the system-created PendingIntent that launches AppWidgetConfigActivityProxy is built, and which hosts use this proxy flow.",
      "How startActivityForResultSafely is implemented, and whether the proxy has any other result path.",
      "Whether content in the forwarded extras can influence URI grants when the result is delivered.",
      "Whether AppWidgetConfigActivityProxy.java exists in the 14/15/16 security tags.",
      "The 16-qpr2 fix commit is not in the material."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28658": {
     "cve": "CVE-2026-28658",
     "summary": "AccountsDb.findMetaAuthUid now derives the account type from a meta_key by stripping the fixed \"auth_uid_for_type:\" prefix instead of splitting the key on \":\" and taking the second token, so account types containing a colon are no longer truncated when building the type-to-authenticator-UID map.",
     "component": "platform/frameworks/base, services/core accounts (AccountsDb)",
     "weakness": "Improper parsing of a delimited key (CWE-20 style input handling): the delimiter used to split the key can also appear inside the account type value, producing a wrong type string.",
     "root_cause": "In services/core/java/com/android/server/accounts/AccountsDb.java, findMetaAuthUid reads meta rows whose key has the form \"auth_uid_for_type:<accountType>\" and recovered the type with TextUtils.split(key, META_KEY_DELIMITER)[1], where META_KEY_DELIMITER is \":\". If the account type itself contains \":\", the split truncates the type at its first colon. The returned Map<String,Integer> then ties the authenticator UID to a shortened type string instead of the one actually stored.",
     "fix_analysis": [
      "AccountsDb.java: the META_KEY_DELIMITER constant is replaced with META_KEY_PREFIX_LEN = META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX.length(), so parsing is tied to the actual prefix.",
      "AccountsDb.java:findMetaAuthUid: the full key is read into typeWithPrefix and type = typeWithPrefix.substring(META_KEY_PREFIX_LEN) is computed. Everything after the fixed prefix, including any embedded ':', is kept as the account type. The existing TextUtils.isEmpty(type)/isEmpty(uidStr) check still skips malformed rows."
     ],
     "completeness": [
      "Covers the only read-side parse of auth_uid_for_type meta keys shown in the material (findMetaAuthUid). Per the commit message, entries are stored with the \"auth_uid_for_type:\" prefix, and the parse now strips exactly that prefix.",
      "The new substring call assumes every row returned by SELECTION_META_BY_AUTHENTICATOR_TYPE (META_KEY LIKE ?) is at least as long as the prefix. A shorter key would throw StringIndexOutOfBoundsException. The query argument is not shown in the material.",
      "No change to how types are validated when written to the meta table. The fix only makes the read-side parse faithful."
     ],
     "behavior_changes": [
      "Account types containing ':' are now returned intact from findMetaAuthUid, so the keys of the type-to-UID map match the stored authenticator types exactly."
     ],
     "preconditions": [
      "An authenticator meta row must exist in the accounts database meta table whose account type contains ':' (rows are stored with the \"auth_uid_for_type:\" prefix, per the commit message and constant).",
      "findMetaAuthUid must be invoked by the accounts service. Its callers are not shown in the provided material."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 051431afaec5 in android-security-14.0.0_r29, identical diff (hunk at line 1055).",
      "15": "same fix: platform/frameworks/base 38dbed092b9d in android-security-15.0.0_r17, identical diff (hunk at line 1055).",
      "16": "same fix: platform/frameworks/base 8c2ce8c567a2 in android-security-16.0.0_r8, identical diff (hunk at line 1055).",
      "17": "same fix: platform/frameworks/base 31e7981c9d23 in android-security-17.0.0_r1, identical diff (hunk at line 1094), cherry-pick of 6a2d8b6bc382."
     },
     "backport_notes": [
      "Single self-contained commit that only touches AccountsDb.java. The same diff was applied on 14 through 17 with only line-offset differences, so it is expected to port easily to trees that still have META_KEY_DELIMITER and the TextUtils.split call in findMetaAuthUid."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show how AccountManagerService consumes the result of findMetaAuthUid, nor the concrete EoP consequence of a truncated type mapping.",
      "The LIKE selection argument passed for SELECTION_META_BY_AUTHENTICATOR_TYPE is not shown, so it is unknown whether rows shorter than the prefix can be returned.",
      "The write path that inserts auth_uid_for_type rows, and any validation of account type strings there, is not shown.",
      "Which database (DE or CE) holds the meta table queried here is not shown.",
      "How the old TextUtils.split code handled malformed keys is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28660": {
     "cve": "CVE-2026-28660",
     "summary": "Adds a PackageInstallerService.getAllSessions(userId, filterCallingUid) overload and makes LauncherAppsService.getAllSessions pass the real caller UID. SessionInfo generation and filtering then use the launcher client's UID, not system_server's UID (identity is cleared at that point), so originatingUri/referrerUri are scrubbed as intended.",
     "component": "platform/frameworks/base, package manager (LauncherAppsService / PackageInstallerService)",
     "weakness": "Information disclosure via wrong caller identity: privileged (cleared) Binder identity used for per-caller data scrubbing",
     "root_cause": "LauncherAppsService.LauncherAppsImpl.getAllSessions(String callingPackage) calls Binder.clearCallingIdentity() and then calls PackageInstallerService.getAllSessions(int userId). That method took the UID for session.generateInfoForCaller(false, callingUid) and shouldFilterSession(snapshot, callingUid, info) from Binder.getCallingUid(). After identity clearing, that is system_server's UID. SessionInfo objects were therefore generated as for a system caller, without the per-caller scrubbing of originatingUri and referrerUri. LauncherAppsService then re-filtered the list with its own shouldFilterSession(callingUid, info) using the real UID. That filter removes a session only when the caller is not the installer and cannot query the session's app package. The sensitive URI fields in the sessions that remained were not scrubbed for the actual caller.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/PackageInstallerService.java: getAllSessions(int userId) now only reads Binder.getCallingUid() and delegates to a new package-private overload, getAllSessions(int userId, int filterCallingUid).",
      "PackageInstallerService.getAllSessions(int, int): still uses the real Binder.getCallingUid() for snapshot.enforceCrossUserPermission(...). It passes filterCallingUid to session.generateInfoForCaller(false /* includeIcon */, filterCallingUid) and to shouldFilterSession(snapshot, filterCallingUid, info), so scrubbing and visibility filtering are done for the supplied UID.",
      "services/core/java/com/android/server/pm/LauncherAppsService.java: LauncherAppsImpl.getAllSessions(String) now calls getPackageInstallerService().getAllSessions(userId, callingUid). It passes the launcher client's UID, captured before Binder.clearCallingIdentity().",
      "services/tests/mockingservicestests/.../PackageInstallerServiceTest.java: the new testGetAllSessions_filtersSessionsCorrectly checks that getAllSessions(userId) returns no sessions when canQueryPackage is false for the caller and one session when it is true. The 15 and 16 diffs shown contain no test change."
     ],
     "completeness": [
      "Covers the LauncherAppsService path: both SessionInfo generation (generateInfoForCaller) and in-service filtering (shouldFilterSession) now use the launcher client's UID.",
      "The direct Binder entry point PackageInstallerService.getAllSessions(int) behaves as before: it still uses Binder.getCallingUid(), which is correct when identity is not cleared.",
      "PackageInstallerService.getStagedSessions() still takes its UID from Binder.getCallingUid() and gets no overload. The material does not show whether any internal caller reaches it with cleared identity.",
      "The added test checks session filtering by package visibility only. It does not check that originatingUri/referrerUri are scrubbed. The scrubbing logic is in PackageInstallerSession.generateInfoForCaller, which the material does not show.",
      "The cross-user check is still tied to the real Binder caller (system_server after identity clearing). The fix changes only which UID is used for scrubbing and filtering, not the cross-user permission enforcement."
     ],
     "behavior_changes": [
      "LauncherApps.getAllPackageInstallerSessions() now returns SessionInfo objects generated for the calling app's UID. The fields that generateInfoForCaller scrubs for that UID (per the commit message, originatingUri and referrerUri) are no longer exposed.",
      "Inside PackageInstallerService, sessions are now filtered against the launcher client's UID rather than system_server's UID. LauncherAppsService still applies its own filter afterwards.",
      "New package-private method PackageInstallerService.getAllSessions(int, int); no public API change."
     ],
     "preconditions": [
      "The caller invokes LauncherApps.getAllPackageInstallerSessions(), which reaches LauncherAppsImpl.getAllSessions(String) in LauncherAppsService. Per index search, this is the only in-base caller of PackageInstallerService.getAllSessions found.",
      "An install session exists for one of the caller's user profiles. The caller must own it or be able to query the session's app package (package visibility); otherwise LauncherAppsService.shouldFilterSession removes it.",
      "The session carries originatingUri/referrerUri values set by the installer."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (14 is not listed). The resolver reports 8/8 lines of the patched region present at android-security-14.0.0_r29; the material does not explain this.",
      "15": "Same fix: 498791aa27ab in android-security-15.0.0_r17. Same LauncherAppsService and PackageInstallerService changes at different line offsets; no test change in the diff shown.",
      "16": "Same fix: 3d614f9e98a3 in android-security-16.0.0_r8. Same LauncherAppsService and PackageInstallerService changes; no test change in the diff shown.",
      "17": "Same fix: 6acdd7f1a72d in android-security-17.0.0_r1 (cherry-pick of bbeee4f37c25), including the new PackageInstallerServiceTest test."
     },
     "backport_notes": [
      "Single self-contained commit. It uses the existing PackageInstallerSession.generateInfoForCaller(boolean, int) and shouldFilterSession(Computer, int, SessionInfo); the 15/16/17 diff context shows both. The fix depends on generateInfoForCaller doing the per-UID scrubbing, which is not shown.",
      "The 15 and 16 backports carry only the two service file changes; the test was added only in the 17 commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact scrubbing logic in PackageInstallerSession.generateInfoForCaller (which UIDs count as privileged and which fields are cleared) is not shown in the material.",
      "The logic of PackageInstallerService.shouldFilterSession(Computer, int, SessionInfo) is not shown.",
      "Whether getStagedSessions or other session-info paths are reached with cleared identity is not shown.",
      "The material does not explain why the resolver reports the patched region as present on Android 14 (android-security-14.0.0_r29) while the bulletin does not list 14.",
      "The material does not say why the 15 and 16 backports omit the test."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28663": {
     "cve": "CVE-2026-28663",
     "summary": "LauncherAppsService.buildIntentSenderForUser now creates its PendingIntent with ActivityOptions whose PendingIntentCreatorBackgroundActivityStartMode is MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE instead of null options, so the app-market IntentSender (getAppMarketActivityIntent) no longer gives an unrestricted background activity launch on behalf of the system server.",
     "component": "platform/frameworks/base, services/core PackageManager LauncherAppsService (LauncherApps system service)",
     "weakness": "Missing restriction on a privileged capability token (PendingIntent created as UID 1000 without background activity launch restrictions), leading to a background activity launch (BAL) restriction bypass",
     "root_cause": "Per the commit message, LauncherAppsService.getAppMarketActivityIntent created a PendingIntent on behalf of the system server (UID 1000) because identity was cleared. The code context shows buildAppMarketIntentSenderForUser building an app-market Intent (ACTION_MAIN + CATEGORY_APP_MARKET, NEW_TASK|CLEAR_TASK) and passing it to buildIntentSenderForUser, which called PendingIntent.getActivityAsUser with null options, so no creator BAL restriction was set. Per the commit message, an unprivileged app could obtain this IntentSender and use it (e.g. as a notification deleteIntent) to launch activities from the background, bypassing BAL restrictions.",
     "fix_analysis": [
      "services/core/java/com/android/server/pm/LauncherAppsService.java:buildIntentSenderForUser: builds a Bundle from ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE).toBundle() and passes it as the options argument of PendingIntent.getActivityAsUser (previously null). Flags (FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT), request code 0, intent and target user are unchanged.",
      "The creator-side BAL mode is carried by the PendingIntent, so per the commit message background activity launches are prevented unless the app is visible. The system server still creates the PendingIntent, which the commit message says preserves cross-profile functionality.",
      "Single-commit, single-file fix in the material; the Cherrypick-From lines reference two internal commits whose content is not shown."
     ],
     "completeness": [
      "Changes only buildIntentSenderForUser. The context shows buildAppMarketIntentSenderForUser as its caller, and per index search no other callers were found (index may be incomplete).",
      "The surrounding context (lines 1838-1855, the private-space settings path using psSettingsIntent) shows another PendingIntent.getActivityAsUser call in the same class with null options, under a cleared identity (Binder.restoreCallingIdentity in its finally block), that this commit does not modify. Whether that sender is reachable by unprivileged callers or restricted elsewhere is not shown.",
      "No change to the receiving side (BAL enforcement) or to the LauncherApps API surface; the fix relies on the existing PendingIntent creator BAL mode mechanism."
     ],
     "behavior_changes": [
      "IntentSenders built by buildIntentSenderForUser (the getAppMarketActivityIntent path) now carry a creator background activity start mode of ALLOW_IF_VISIBLE; per the commit message they no longer allow background activity launches unless the app is visible.",
      "Per the commit message, cross-profile behavior is intended to be preserved; the PendingIntent is still created by the system server."
     ],
     "preconditions": [
      "An app calls getAppMarketActivityIntent, which per the commit message and code context leads to buildAppMarketIntentSenderForUser and buildIntentSenderForUser; the commit message states an unprivileged app could request this IntentSender.",
      "The returned IntentSender is then sent from a background context (the commit message gives a notification deleteIntent as the example)."
     ],
     "per_major": {
      "14": "not affected per the bulletin; at android-security-14.0.0_r29 6/8 lines of the patched region are present. No fix shown in the material.",
      "15": "not affected per the bulletin; at android-security-15.0.0_r17 the resolver reports 8/8 lines of the patched region present. No fix commit for 15 is shown, so whether this reflects fixed or unfixed code cannot be determined from the material.",
      "16": "same fix: platform/frameworks/base 05c5903d27b8 in android-security-16.0.0_r8, identical diff to buildIntentSenderForUser at a different line offset (1824/1831 vs 1867/1874). 16-qpr2 is listed as affected by the bulletin but no separate commit is shown.",
      "17": "same fix: platform/frameworks/base b8e217c4e6cd in android-security-17.0.0_r1 (Change-Id Ida112cd1f289089c4dba4c81d5cd43496daeeb62)."
     },
     "backport_notes": [
      "Self-contained one-file change: it needs ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode and MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE in the target branch. The diff adds no imports, so Bundle and ActivityOptions are already imported in LauncherAppsService.java on the branches where it applies.",
      "Verify with atest CtsPackageManagerTestCases:android.content.pm.cts.LauncherAppsForHiddenProfilesTest as named in the commit."
     ],
     "confidence": "high",
     "unknowns": [
      "The content of the two Cherrypick-From internal commits (0c7caef3..., 927fb5e3...) is not shown; the material only contains the one-file change.",
      "Whether the private-space settings PendingIntent path visible in the context (psSettingsIntent, null options) is exposed to unprivileged callers or otherwise protected is not shown.",
      "Whether FLAG_UPDATE_CURRENT causes already existing PendingIntent records for this intent to pick up the new options is not shown.",
      "Why Android 15 shows 8/8 patched-region lines present while the bulletin lists it as not affected is not explained by the material.",
      "How getAppMarketActivityIntent reaches buildAppMarketIntentSenderForUser is not shown in the code context; it comes from the commit message."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28664": {
     "cve": "CVE-2026-28664",
     "summary": "In RuntimeImage::WriteImageToDisk, ART now calls fchmod() to set the runtime-generated app image (.art) file to mode 0444 right after the file is created, and fails the write if fchmod fails.",
     "component": "platform/art, runtime (runtime-generated app image, runtime/runtime_image.cc)",
     "weakness": "Insecure file permissions on a runtime-generated artifact (the file was left writable, which allows tampering)",
     "root_cause": "RuntimeImage::WriteImageToDisk created the app image file and wrote its contents without restricting the file mode. The generated .art image therefore kept whatever default mode it was created with, during generation and afterwards. The commit message says the read-only change is meant to prevent tampering. The material does not show the file's directory, owner or default mode, so it does not show who could write to it.",
     "fix_analysis": [
      "runtime/runtime_image.cc:RuntimeImage::WriteImageToDisk: the fix adds #include <sys/stat.h>. It also adds a call to fchmod(image_file->Fd(), S_IRUSR | S_IRGRP | S_IROTH), which sets mode 0444 on the file. The call comes right after the existing early return on file-creation failure and before image->FillData() fills the buffer that gets written.",
      "If fchmod fails, the fix sets *error_msg to \"Failed to make runtime image read-only: <strerror(errno)>\" and the function returns false.",
      "The code comment notes that writes through the already-open file descriptor still succeed after fchmod. The image data is therefore written to a file that is already read-only on the filesystem. The diff does not change the later move to the final path."
     ],
     "completeness": [
      "The diff covers only the runtime-generated app image written by RuntimeImage::WriteImageToDisk. The single hunk sets the mode once on the fd right after creation.",
      "The commit message describes making the image read-only after it is written and moved, and deleting it if that fails. The diff in the material contains only the fchmod-at-creation hunk and no deletion. Whether a file is cleaned up on the new failure path depends on code that is not shown.",
      "The fix only hardens the file mode. It does not add any integrity check of the image contents at load time, and the material shows no such check. Permissions on the parent directory are not changed."
     ],
     "behavior_changes": [
      "Runtime-generated app image files are now set to mode 0444 (read-only for owner, group and other) right after creation, instead of keeping their previous default mode.",
      "There is a new failure path: if fchmod fails, WriteImageToDisk returns false with the error message \"Failed to make runtime image read-only: ...\"."
     ],
     "preconditions": [
      "The runtime must be generating an app image at runtime, so that RuntimeImage::WriteImageToDisk is reached after the image file has been created successfully.",
      "For the pre-fix weakness to matter, some party must be able to write to the generated .art file. The material does not show the file location, ownership or default mode, so the set of possible writers is unknown."
     ],
     "per_major": {
      "14": "Same fix: platform/art f8f793610644 in android-security-14.0.0_r29. The hunk is identical (fchmod to 0444 after creation, return false on failure). Only the include context differs (#include <sstream> is present), and the hunk is at line ~1875. Patched region: 6/6 lines present.",
      "15": "Same fix: platform/art 19665de3fb4d in android-security-15.0.0_r17. Identical hunk at line ~1902. Patched region: 6/6 lines present.",
      "16": "Same fix: platform/art a57f914df7ee in android-security-16.0.0_r8. Identical hunk at line ~1903. Patched region: 6/6 lines present.",
      "17": "Same fix: platform/art 70be2043e6db in android-security-17.0.0_r1. Hunk at line ~1914. Patched region: 6/6 lines present."
     },
     "backport_notes": [
      "This is a self-contained change to one file. Add #include <sys/stat.h>, and put the fchmod block in RuntimeImage::WriteImageToDisk right after the early return on image-file creation failure and before image->FillData(). Google used the same hunk on 14 through 17, with only line offsets differing. ART is a mainline (APEX) module, so the fix may also arrive through an ART module update rather than the platform build (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The directory, ownership and pre-fix default mode of the generated app image file, and therefore which process or UID could tamper with it.",
      "How the image is consumed later and what validation, if any, happens on load. The material does not show the loading path.",
      "Whether the image file is deleted on the new fchmod failure path. The commit message says the image is discarded, but the diff shown contains no deletion.",
      "What each of the two upstream cherrypicks (a18d72596e17, 4cf977edf749) contains, beyond the single hunk shown.",
      "Whether any other code or tooling expects to reopen an existing runtime app image for writing."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28668": {
     "cve": "CVE-2026-28668",
     "summary": "Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that a realloc rejected by the android allocation limit returns nullptr without freeing the caller's still-valid original pointer.",
     "component": "platform/bionic, libc malloc allocation-limit hooks (libc/bionic/malloc_limit.cpp)",
     "weakness": "Invalid free on an error path (realloc contract violation leading to double free / use-after-free of the original pointer by callers)",
     "root_cause": "In libc/bionic/malloc_limit.cpp, LimitRealloc checks whether growing an allocation would cross the per-process limit (bytes > old_usable_size && !CheckLimit(bytes - old_usable_size)). On that failure path the old code called LimitFree(old_mem) before returning nullptr. realloc semantics require that on failure the original block remains valid and owned by the caller, so any caller that correctly handles the nullptr return (by continuing to use the old block or freeing it) operates on memory that bionic already freed. The commit message calls this an invalid free and says the original pointer should not be changed.",
     "fix_analysis": [
      "libc/bionic/malloc_limit.cpp:LimitRealloc: on the limit-exceeded path, the LimitFree(old_mem) call is deleted and replaced by the comment 'Do not free the old pointer, it's still valid.'; the function still logs the warning and returns nullptr, and this path no longer touches old_mem or gAllocated.",
      "tests/malloc_test.cpp:TEST(android_mallopt, set_allocation_limit_realloc_increase): the over-limit realloc result is stored in a separate new_memory variable, asserted to be nullptr, and the original memory pointer is then passed to free() to check that it is still valid; per the commit message this test fails with the old behavior."
     ],
     "completeness": [
      "Covers the only place in the shown code that freed the caller's block on a limit failure: the CheckLimit rejection branch of LimitRealloc.",
      "The other failure branch in LimitRealloc (underlying realloc returns nullptr) does not free old_mem either; it only subtracts old_usable_size from gAllocated with the comment 'This acts as if the pointer was freed'. That is an accounting assumption, not a free, and the fix does not change it.",
      "The other limit hooks shown (LimitPosixMemalign, LimitAlignedAlloc) have no caller-owned pointer on their failure paths, so the same pattern does not apply to them. The material does not show whether a reallocarray-style limit hook exists."
     ],
     "behavior_changes": [
      "When the allocation limit rejects a growing realloc, the original block stays allocated and counted in gAllocated, and callers must free it themselves (standard realloc semantics). Previously LimitFree(old_mem) was called on it. Its exact accounting effect is not shown.",
      "The warning_log message and the nullptr return are unchanged."
     ],
     "preconditions": [
      "The process must have enabled the bionic allocation limit, e.g. via android_mallopt(M_SET_ALLOCATION_LIMIT_BYTES, ...) as done in tests/malloc_test.cpp.",
      "A realloc must grow the block past its current usable size by more than the remaining headroom under gAllocLimit (bytes > old_usable_size and CheckLimit fails).",
      "The caller must then act on the original pointer after the nullptr return (as correct realloc error handling does). The material does not show which production processes enable the limit."
     ],
     "per_major": {
      "14": "same fix: platform/bionic cd1be6e8bfb8 in android-security-14.0.0_r29, identical two-file change (malloc_limit.cpp hunk at line 199, test hunk at line 1279).",
      "15": "same fix: platform/bionic 10a1e2ce6e59 in android-security-15.0.0_r17, identical change (malloc_limit.cpp hunk at line 200, test hunk at line 1289).",
      "16": "same fix: platform/bionic 50cbdffd6f7e in android-security-16.0.0_r8, identical change (malloc_limit.cpp hunk at line 200, test hunk at line 1291).",
      "17": "same fix: platform/bionic 48a7e3362ff9 in android-security-17.0.0_r1 (cherry-pick of d00918bfbbe0), malloc_limit.cpp hunk at line 201, test hunk at line 818."
     },
     "backport_notes": [
      "Single self-contained commit. The code hunk is identical on all four branches and only line offsets differ, so it should apply to any tree whose LimitRealloc still contains the 'Free the old pointer. LimitFree(old_mem);' lines. The resolver reports 6/8 lines of the patched region present on each tag. The material does not explain that figure, but the code at android-security-17.0.0_r1 already contains the fixed lines."
     ],
     "confidence": "high",
     "unknowns": [
      "Which production processes enable M_SET_ALLOCATION_LIMIT_BYTES and are therefore exposed (not in the material).",
      "The bodies of LimitFree and LimitUsableSize are not shown, so the exact accounting effect of the old invalid free is inferred only from the comment and commit message.",
      "Whether a separate reallocarray limit hook exists and how it behaves is not shown.",
      "How the resolver's '6/8 lines present' figure is computed."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 2,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-28671": {
     "cve": "CVE-2026-28671",
     "summary": "MediaProvider.update's file-movement path now checks for an existing database row at the computed destination path before Os.rename. A row with no file on disk is deleted; a row that has a file requires write permission. If the follow-up DB update then fails on the UNIQUE(_data) constraint and the conflicting path is the active rename target, the conflicting row is deleted and the update is retried, so a squatter row cannot keep the path a pending file is being published to.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider.update / updateAllowingReplace (MediaStore pending-file publishing and file movement)",
     "weakness": "Race condition / improper conflict resolution between the MediaStore database and the filesystem: a pre-existing row with the same _data path (namespace squatting) was not detected or resolved when a pending file was renamed to that path.",
     "root_cause": "In MediaProvider.update, when placement columns change (for example IS_PENDING) and movement is allowed, the provider computes afterPath, calls Os.rename(beforePath, afterPath), then saves the new values through updateAllowingReplace. If a row whose _data equals afterPath already existed (in the tests, a row with no backing file inserted with the same DISPLAY_NAME and RELATIVE_PATH), qb.update would hit a SQLiteConstraintException on the UNIQUE _data column. The old handler threw for callers with targetSdk >= R, and for legacy callers it only tried a replace when a file existed at the path. Nothing checked for or removed the pre-existing row before the rename, and nothing linked the conflict to the rename in progress.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java:update: a new local renameAfterPath is set to afterPath right before Os.rename in the movement block. It is passed to updateAllowingReplace so the conflict handler knows the destination of the active rename.",
      "src/com/android/providers/media/MediaProvider.java:update: before the rename, the new checkIfPathAlreadyExists(helper, beforeVolume, afterPath) is called. With the local calling identity cleared, it uses queryForSingleItem to look up the _ID in Files where DATA = afterPath. If a row exists but new File(afterPath).exists() is false, deleteEntryForPath removes the stale row. If both the row and the file exist, the calling identity is restored and enforceCallingPermission(Files content URI for that _ID, Bundle.EMPTY, forWrite=true) is called, so the caller needs write access to the existing row. A null cursor or a FileNotFoundException from the query makes it return silently.",
      "src/com/android/providers/media/MediaProvider.java:updateAllowingReplace: the signature gains @Nullable String renameAfterPath. In the SQLiteConstraintException handler, the path is read from values (FileColumns.DATA) and handleNonExistingEntryFile is called first. If it returns anything other than -1, that count is returned. Otherwise the previous logic runs (throw for targetSdk >= R, replace path for legacy callers). The old guard 'path == null || !new File(path).exists() -> throw' was removed; only the null check remains, inside handleNonExistingEntryFile.",
      "src/com/android/providers/media/MediaProvider.java:handleNonExistingEntryFile (new): rethrows the original exception if path is null. If Objects.equals(path, renameAfterPath) and deleteEntryForPath(helper, path) deletes at least one row, it re-runs qb.update(helper, values, userWhere, userWhereArgs) inside the enclosing runWithTransaction lambda and returns that count. Otherwise it returns -1.",
      "src/com/android/providers/media/MediaProvider.java:deleteEntryForPath (new): clears the local calling identity and runs db.delete(Files.TABLE, \"_data=?\", {path}) inside helper.runWithTransaction. On any exception it logs and returns 0.",
      "tests/src/com/android/providers/media/MediaProviderTest.java: adds testPendingUpdate_SquatterRowDeleted (a squatter row without a file is removed when the pending row is published) and testPendingUpdate_SquatterRowFileExists (a squatter row with a real file is kept, and the published row gets a different display name)."
     ],
     "completeness": [
      "Only the movement path of MediaProvider.update is covered. renameAfterPath is non-null only when placement columns change, DATA is not supplied, the item is not a thumbnail and movement is allowed. Constraint conflicts from other kinds of update (for example caller-supplied DATA) still get the old handling (throw for targetSdk >= R).",
      "The pre-rename check and the Os.rename run outside the DB transaction. A row inserted between checkIfPathAlreadyExists and qb.update is handled only by handleNonExistingEntryFile inside the transaction. Despite its name, that function deletes any row at renameAfterPath without re-checking whether the file exists or who owns the row.",
      "deleteEntryForPath removes the row with a raw db.delete on Files.TABLE, bypassing the provider's normal delete path. The material does not show which side effects (notifications, related tables) are skipped.",
      "The insert path that lets a non-pending row be created for a path with no backing file is unchanged. The fix acts at publish time rather than preventing the squatter row.",
      "Because the 'file exists' guard was removed, the legacy (targetSdk < R) replace fallback now also runs when no file exists at the conflicting path. The diff does not show whether the replace code below handles that case.",
      "checkIfPathAlreadyExists queries only the Files collection of beforeVolume. Cross-volume moves depend on the earlier afterPath adjustment (replaceFirst(afterVolumePath, beforeVolumePath)) that is visible in the 14/15/16 hunks."
     ],
     "behavior_changes": [
      "Publishing or moving a file to a destination path already claimed by a DB row with no file on disk now deletes that row (in the test, the squatter URI returns 0 rows afterwards).",
      "If a DB row and a real file already exist at the destination, the caller needs write permission on that row (enforceCallingPermission with forWrite=true) or the update fails. When the caller has that permission, the test expects the moved file to end up with a different display name.",
      "On a UNIQUE constraint failure during the movement update where the conflicting path is the rename target and the conflicting row is deleted, the update is retried in the same transaction. In that case callers with targetSdk >= R no longer get the SQLiteConstraintException.",
      "New log lines: 'Deleted stale entry' (verbose) and 'Failure in deleting stale entry' (error).",
      "The legacy-app replace fallback no longer requires the conflicting file to exist on disk."
     ],
     "preconditions": [
      "The caller updates a MediaStore row it can write to, changing placement columns (the tests clear IS_PENDING), with movement allowed (QUERY_ARG_ALLOW_MOVEMENT defaults to !isCallingPackageSelf()), without supplying DATA, for a URI in a well-defined collection (AUDIO_MEDIA_ID, AUDIO_PLAYLISTS_ID, VIDEO_MEDIA_ID, IMAGES_MEDIA_ID, DOWNLOADS_ID, FILES_ID).",
      "Another row already exists in the Files table whose _data equals the computed destination path. The tests create it with an ordinary insert into Images using the same DISPLAY_NAME and RELATIVE_PATH (Pictures) and IS_PENDING=0."
     ],
     "per_major": {
      "14": "same fix, adapted to local context: commit e27ea39d7184 in android-security-14.0.0_r29, identical logic. The checkIfPathAlreadyExists call sits after the afterPath volume adjustment and before Log.d(\"Moving ...\"); surrounding code uses invalidateFuseDentry; line offsets differ. 8/8 lines of the patched region present.",
      "15": "same fix, adapted to local context: commit 9e42f05c7c32 in android-security-15.0.0_r17, same logic and placement as on 14. 8/8 lines of the patched region present.",
      "16": "same fix, adapted to local context: commit 94374dc41151 in android-security-16.0.0_r8, same logic and placement as on 14/15. 8/8 lines of the patched region present.",
      "17": "reference fix: commit 87beb3978b6a in android-security-17.0.0_r1. Same logic; surrounding code uses Logging.logIfLoggable and markPathAsDeletedAndInvalidateFuseDentry. 8/8 lines of the patched region present."
     },
     "backport_notes": [
      "Single self-contained commit that touches only MediaProvider.java and its test. It relies on existing helpers (queryForSingleItem, enforceCallingPermission, clearLocalCallingIdentity/restoreLocalCallingIdentity, DatabaseHelper.runWithTransaction) and adds no imports. Place the checkIfPathAlreadyExists call after the final afterPath adjustment and right before the 'Moving' log in the movement block. MediaProvider ships as a Mainline module, so devices may also receive it through a module update (general platform knowledge)."
     ],
     "confidence": "medium",
     "unknowns": [
      "How afterPath is made unique when a file already exists at the destination (the SquatterRowFileExists test expects a different display name, but that code is not in the diff or context).",
      "Whether nested DatabaseHelper.runWithTransaction calls (deleteEntryForPath inside updateAllowingReplace's transaction) behave as a single transaction, as the commit message states.",
      "The exact state in the unpatched code after the constraint failure (renamed file vs. surviving squatter row), and what the squatter could then access beyond the bulletin's ID rating; neither is shown in the material.",
      "Whether the legacy replace path copes with a non-existent file now that the exists() guard is gone.",
      "Whether any caller outside MediaProvider (for example DownloadProvider.updateMediaProvider, per index search) reaches the movement path; that caller supplies DATA, so it probably does not."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45517": {
     "cve": "CVE-2026-45517",
     "summary": "Adds structural (scheme, authority, picker path pattern) and user-ID validation of photopicker pre-selection URIs before the existing permission check, in both the PickerUriResolver and the v2 MediaQueryForPreSelection filtering loops.",
     "component": "platform/packages/providers/MediaProvider, PhotoPicker pre-selection URI handling (PickerUriResolver, photopicker/v2/model/MediaQueryForPreSelection)",
     "weakness": "Improper input validation of caller-supplied content URIs (missing user-ID consistency check and missing scheme/authority/path validation), leading to information disclosure per the bulletin type",
     "root_cause": "The photopicker accepts a list of pre-selection URI strings from the launching app (EXTRA_PICKER_PRE_SELECTION_URIS). These reach MediaProvider as the \"pre_selection_uris\" / QUERY_ID_SELECTION query arg. Both filtering loops (PickerUriResolver.processUrisForSelection and the loop in MediaQueryForPreSelection) only did Uri.parse followed by checkUriPermission for the calling UID, then added the URI to accessibleUris. Nothing checked that the URI was a content URI with MediaStore.AUTHORITY and a valid picker path. Nothing compared the user ID encoded in path segment 1 (content://media/picker/<user-id>/...) against the current user. As a result, a URI with a mismatched user ID or an unexpected structure could pass filtering, and its media ID could be used in the picker database query for the active profile. The new test testQueryMediaForPreSelection_ignoresCrossUserUris asserts that a URI for user+1 with the same media ID must now return 0 rows.",
     "fix_analysis": [
      "src/com/android/providers/media/PickerUriResolver.java adds a static LocalUriMatcher sStaticUriMatcher = new LocalUriMatcher(MediaStore.AUTHORITY) and a new public static isValidPickerUri(Uri). The helper returns false for null, requires scheme ContentResolver.SCHEME_CONTENT, and requires sStaticUriMatcher.matchUri(uri, allowHidden=false) to return PICKER_ID, PICKER_GET_CONTENT_ID or PICKER_TRANSCODED_ID. The fake-authority test shows this also rejects URIs with a non-media authority.",
      "src/com/android/providers/media/PickerUriResolver.java:processUrisForSelection: inside the per-URI try block and before checkUriPermission, a URI is skipped (continue, with Log.d) if !isValidPickerUri(uriForSelection). It is also skipped if getUserId(uriForSelection) != UserHandle.myUserId(). getUserId(Uri) is made public static so the v2 path can reuse it.",
      "src/com/android/providers/media/photopicker/v2/model/MediaQueryForPreSelection.java: the equivalent per-URI loop gets the same two gates, PickerUriResolver.isValidPickerUri(...) and PickerUriResolver.getUserId(...) != UserHandle.myUserId(). Both run ahead of PickerUriResolver.checkUriPermission(appContext, uriForSelection, -1, callingPackageUid). An import of android.os.UserHandle is added.",
      "Tests: PickerUriResolverTest switches the test picker URIs from TEST_USER to UserHandle.myUserId(). PickerDataLayerV2Test stubs mMockContext.getUser() and uses the real user ID in URI placeholders. It also adds tests for cross-user, malformed, fake-authority and invalid-segment URIs via PickerDataLayerV2.queryMediaForPreSelection."
     ],
     "completeness": [
      "On 16 and 17 the fix covers both pre-selection filtering sites visible in the material: the v1 PickerUriResolver.processUrisForSelection loop and the v2 MediaQueryForPreSelection loop.",
      "Validation runs in this order: structural check, then user-ID check, then the pre-existing checkUriPermission. The permission check itself is unchanged.",
      "Accepted URI shapes are limited to the matcher codes PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID with allowHidden=false. testQueryMediaForPreSelection_ignoresInvalidPickerSegments confirms that picker_internal, an unknown first segment, and a wrong fourth segment are rejected.",
      "The user-ID check compares against UserHandle.myUserId() of the MediaProvider process, not against a caller-supplied or context user.",
      "The Android 15 commit only changes PickerUriResolver.java (plus its test), and its isValidPickerUri accepts only PICKER_ID and PICKER_GET_CONTENT_ID. It does not touch MediaQueryForPreSelection. The material does not show whether that file exists on 15."
     ],
     "behavior_changes": [
      "Pre-selection URIs are now silently dropped with a Log.d message instead of being processed if they are not content:// URIs, do not have MediaStore.AUTHORITY, or do not match a picker / picker_get_content / picker_transcoded pattern. picker_transcoded is accepted only on 16 and 17.",
      "Pre-selection URIs whose encoded user ID differs from the MediaProvider process user are silently dropped.",
      "Two API visibility changes: there is a new public helper PickerUriResolver.isValidPickerUri(Uri), and on 16 and 17 PickerUriResolver.getUserId(Uri) becomes public."
     ],
     "preconditions": [
      "An app launches the photopicker with pre-selection URI strings (EXTRA_PICKER_PRE_SELECTION_URIS per the commit message). These reach MediaProvider as the pre_selection_uris / PickerDataLayer.QUERY_ID_SELECTION query argument, together with the calling UID (EXTRA_CALLING_PACKAGE_UID / Intent.EXTRA_UID).",
      "The code is reached via PickerUriResolver.processUrisForSelection (v1 path, with QUERY_SHOULD_SCREEN_SELECTION_URIS set as in PickerUriResolverTest) or via PickerDataLayerV2.queryMediaForPreSelection (v2 path, which uses MediaQueryForPreSelection).",
      "The caller must hold URI permission on the supplied URI for the pre-existing checkUriPermission to pass. The material does not show how that check interacts with a mismatched user-ID segment."
     ],
     "per_major": {
      "14": "Not affected: the bulletin does not list Android 14. android-security-14.0.0_r29 has 2/8 lines of the patched region.",
      "15": "Adapted fix (1b032be59978 in android-security-15.0.0_r17). The same checks are added only to PickerUriResolver.processUrisForSelection. isValidPickerUri accepts PICKER_ID and PICKER_GET_CONTENT_ID only (no PICKER_TRANSCODED_ID), and a Nullable import is added. MediaQueryForPreSelection and PickerDataLayerV2Test are not modified, and getUserId is not made public.",
      "16": "Same fix (32217e55e510 in android-security-16.0.0_r8). The source changes to PickerUriResolver and MediaQueryForPreSelection are functionally the same as the 17 commit; only whitespace on the sStaticUriMatcher declaration differs. Test hunk offsets and setUp context differ.",
      "17": "Fix commit 50d220c204df in android-security-17.0.0_r1 (RESTRICT AUTOMERGE cherry-pick, Change-Id I7bf6d97a7fac3ecc708df96a93363cbb3e512216)."
     },
     "backport_notes": [
      "Single commit with no dependencies beyond LocalUriMatcher and its PICKER_ID / PICKER_GET_CONTENT_ID / PICKER_TRANSCODED_ID codes. On trees without PICKER_TRANSCODED_ID or without the v2 MediaQueryForPreSelection class, follow the Android 15 variant (PickerUriResolver only, two match codes).",
      "Existing tests that build picker URIs with a fixed user ID (0 or TEST_USER) can fail the new gate when that ID differs from UserHandle.myUserId(). Update them as the commit does."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show what data was exposed or how a mismatched user-ID segment interacted with checkUriPermission before the fix. The bulletin only classifies the issue as High / ID.",
      "The diff hunk does not show the name of the MediaQueryForPreSelection method that contains the patched loop.",
      "The material does not show whether MediaQueryForPreSelection exists on the Android 15 branch.",
      "The material does not include the path patterns LocalUriMatcher uses for PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45518": {
     "cve": "CVE-2026-45518",
     "summary": "Adds an OPTION_CHECK_BRACKETS mode to android.database.sqlite.SQLiteTokenizer that rejects SQL with unbalanced or prematurely closed parentheses, and makes ContactsProvider's SelectionBuilder.addClause run every clause through it so a caller-supplied selection cannot break out of the \"(clause) AND (clause)\" wrapping.",
     "component": "platform/frameworks/base (core SQLite helpers, SQLiteTokenizer) and platform/packages/providers/ContactsProvider (util/SelectionBuilder)",
     "weakness": "SQL injection: improper neutralization of parentheses in a caller-supplied selection string that is concatenated into a WHERE clause (CWE-89 class)",
     "root_cause": "SelectionBuilder (ContactsProvider) builds a WHERE clause by wrapping each added clause in parentheses and joining the clauses with AND (tests show \"(A) AND (B=bar) AND (C='1')\"). The constructor's baseSelection is documented as \"typically the user supplied selection arg\". Before the fix, addClause() stored a clause after only a TextUtils.isEmpty check. A clause with extra closing parentheses (for example the test string \"1=1)) OR ((1=1\") could close the builder's enclosing bracket and change the logical structure of the combined selection. SQLiteTokenizer already rejected semicolons and unterminated quotes but did not check bracket balance, so there was nothing available to detect this. Separately, SQLiteTokenizer.tokenize used \"options &= OPTION_TOKEN_ONLY\" (a compound assignment that modifies options) in two places. Each time one of those tests ran, every option bit other than OPTION_TOKEN_ONLY was cleared.",
     "fix_analysis": [
      "core/java/android/database/sqlite/SQLiteTokenizer.java: adds a new constant, OPTION_CHECK_BRACKETS = 1 << 1.",
      "SQLiteTokenizer.tokenize(String, int, Consumer): adds an int level counter. The bracket handling comes after the comment, quoted-literal and semicolon handling, and those branches 'continue' earlier in the loop, so brackets inside comments or quotes are not counted (testCheckBracketsInComments confirms this for comments). '(' increments level and ')' decrements it. If level drops below 0 and OPTION_CHECK_BRACKETS is set, tokenize throws IllegalArgumentException(\"Unbalanced brackets in '...'\"). After the loop, a non-zero level with the option set also throws. The counting always happens; only the throw depends on the option.",
      "SQLiteTokenizer.tokenize: the two 'options &= OPTION_TOKEN_ONLY' tests become 'options & OPTION_TOKEN_ONLY'. Without this correction the OPTION_CHECK_BRACKETS bit would be cleared the first time one of those tests ran, so the change is required for the new option to work.",
      "SQLiteTokenizer.tokenize: the checker Consumer is now @Nullable and every checker.accept(token) call is guarded by a null check, so the method can be called purely for validation.",
      "src/com/android/providers/contacts/util/SelectionBuilder.java: before adding a non-empty clause, addClause(String) now calls SQLiteTokenizer.tokenize(clause, SQLiteTokenizer.OPTION_CHECK_BRACKETS, null). The constructor's baseSelection goes through addClause, so it is validated too. A clause with unbalanced brackets now throws IllegalArgumentException instead of being stored.",
      "Tests: SQLiteTokenizerTest.testCheckBrackets, SQLiteTokenizerTest.testCheckBracketsInComments and SelectionBuilderTest.testInjection cover balanced input, a missing close, an extra close, the break-out pattern and brackets inside comments."
     ],
     "completeness": [
      "The bracket check is opt-in: only callers that pass OPTION_CHECK_BRACKETS get it. In the material the only adopter is ContactsProvider's SelectionBuilder.addClause.",
      "The new check covers parentheses only. The tokenizer already rejected semicolons and unterminated quotes. Brackets inside comments, string literals and quoted identifiers are skipped by the earlier branches of the loop.",
      "Within ContactsProvider the check sits in SelectionBuilder, which every clause added through it passes through (including the constructor's baseSelection). The material does not show whether ContactsProvider has other selection-concatenation paths that bypass SelectionBuilder.",
      "Any clause with unbalanced brackets is rejected, not only one that closes an outer bracket. A clause with extra '(' is also refused."
     ],
     "behavior_changes": [
      "The SelectionBuilder constructor and addClause now throw IllegalArgumentException for clauses with unbalanced parentheses.",
      "Because the whole tokenizer now runs on each clause, clauses that it rejects for other reasons (a semicolon outside quotes or comments, an unterminated quote) now also throw IllegalArgumentException at addClause time.",
      "SQLiteTokenizer.tokenize accepts a null Consumer and exposes OPTION_CHECK_BRACKETS. With '&=' changed to '&', options is no longer modified during tokenization."
     ],
     "preconditions": [
      "A caller must be able to pass a selection string that reaches ContactsProvider code using SelectionBuilder (the constructor's baseSelection is documented as the user supplied selection arg). The material does not show the specific provider entry points.",
      "The selection must contain parentheses, outside quotes or comments, that close the builder's wrapping bracket."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 9183bfe6574b in android-security-14.0.0_r29 is identical to the Android 17 SQLiteTokenizer change (only the test file index differs). The material does not show the companion ContactsProvider commit for 14. The resolver reports 7/8 lines of the patched region present.",
      "15": "same fix: platform/packages/providers/ContactsProvider b8e00892122c in android-security-15.0.0_r17 has the identical SelectionBuilder diff, with 8/8 lines present. The matching frameworks/base SQLiteTokenizer change for 15 is not shown, but the ContactsProvider change depends on OPTION_CHECK_BRACKETS, which that change defines.",
      "16": "same fix: platform/packages/providers/ContactsProvider 3c11261b2a6b in android-security-16.0.0_r8 has the identical SelectionBuilder diff, with 8/8 lines present. The material does not show the matching frameworks/base commit.",
      "17": "same fix: platform/frameworks/base 5a73367d5b3a plus platform/packages/providers/ContactsProvider 5ec4850fbe73 in android-security-17.0.0_r1. The resolver reports 7/8 lines of the patched region present."
     },
     "backport_notes": [
      "Apply the frameworks/base SQLiteTokenizer change first, because it defines OPTION_CHECK_BRACKETS and allows a null checker. Then apply the ContactsProvider SelectionBuilder change, which uses both. Keep the '&=' to '&' correction: without it the new option bit is cleared at the first of the corrected tests, and the check is silently ineffective."
     ],
     "confidence": "high",
     "unknowns": [
      "Which ContactsProvider query/update/delete entry points pass caller-controlled selections into SelectionBuilder, and which provider-added restrictions could be affected by a break-out.",
      "Whether ContactsProvider has other selection-building paths that do not go through SelectionBuilder.",
      "The material does not include the ContactsProvider commit for Android 14 or the frameworks/base commits for Android 15 and 16.",
      "What the resolver's '7/8 lines present' on 14 and 17 refers to.",
      "Whether the semicolon check in SQLiteTokenizer is fully unconditional (its full condition is not shown)."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45519": {
     "cve": "CVE-2026-45519",
     "summary": "Before photopicker pre-selection URIs (EXTRA_PICKER_PRE_SELECTION_URIS) are permission-checked and used, the fix now checks their structure (scheme, authority, picker path pattern) and their user ID. It does this in both PickerUriResolver.processUrisForSelection and MediaQueryForPreSelection.",
     "component": "platform/packages/providers/MediaProvider, photopicker pre-selection URI handling (PickerUriResolver, photopicker v2 MediaQueryForPreSelection)",
     "weakness": "Improper input validation of caller-supplied URIs (no checks on scheme, authority, path or user-ID consistency before the URIs were used in queries)",
     "root_cause": "Both pre-selection filtering loops (PickerUriResolver.processUrisForSelection and the loop in MediaQueryForPreSelection) worked the same way. For each caller-supplied string they called Uri.parse(), ran only checkUriPermission(uri, -1, callingUid) and, if that passed, added the URI to accessibleUris for the later picker database ID selection. Nothing checked the content scheme, the MediaStore.AUTHORITY authority, the picker path layout, or whether the user ID in path segment 1 matched the user the MediaProvider process runs as. The commit message says the added user check keeps pre-selected media consistent with the active profile and prevents data mismatch during database queries.",
     "fix_analysis": [
      "src/com/android/providers/media/PickerUriResolver.java: adds a static LocalUriMatcher sStaticUriMatcher = new LocalUriMatcher(MediaStore.AUTHORITY) and a new public static isValidPickerUri(@Nullable Uri). It returns false for null, false for any scheme other than ContentResolver.SCHEME_CONTENT, and false unless sStaticUriMatcher.matchUri(uri, /* allowHidden */ false) returns PICKER_ID, PICKER_GET_CONTENT_ID or PICKER_TRANSCODED_ID. The new tests show that fake authorities, picker_internal and invalid_segment first segments, and album or invalid fourth segments are rejected.",
      "src/com/android/providers/media/PickerUriResolver.java:processUrisForSelection: inside the per-URI try block and before checkUriPermission, it skips (Log.d, then continue) any URI that fails isValidPickerUri. It then skips any URI where getUserId(uri) != UserHandle.myUserId(). getUserId(Uri) changes from package-private to public static so the v2 code can reuse it. The structural check runs before getUserId parses segment 1.",
      "src/com/android/providers/media/photopicker/v2/model/MediaQueryForPreSelection.java: inserts the same two pre-checks (PickerUriResolver.isValidPickerUri, then PickerUriResolver.getUserId(uri) != UserHandle.myUserId()) before PickerUriResolver.checkUriPermission(appContext, uri, -1, callingPackageUid) in the loop that builds accessibleUris, and imports android.os.UserHandle.",
      "tests: PickerUriResolverTest switches pre-selection URIs from TEST_USER to UserHandle.myUserId(). PickerDataLayerV2Test mocks mMockContext.getUser() and adds four tests: testQueryMediaForPreSelection_ignoresCrossUserUris, _ignoresMalformedUris, _ignoresFakeAuthorityUris and _ignoresInvalidPickerSegments. The cross-user test expects 0 results. The other three expect only the valid current-user content://media/picker/<user>/<provider>/media/<id> URI to be returned."
     ],
     "completeness": [
      "The fix covers the two pre-selection filtering loops it touches: the legacy PickerUriResolver.processUrisForSelection path (PickerDataLayer.QUERY_ID_SELECTION) and the photopicker v2 MediaQueryForPreSelection path (pre_selection_uris, reached via PickerDataLayerV2.queryMediaForPreSelection per the tests).",
      "The user check compares the URI against UserHandle.myUserId() of the MediaProvider process, not against an explicit caller user.",
      "Validation filters each URI on its own (continue): invalid entries are silently dropped, the remaining valid URIs are still processed, and the request is not rejected.",
      "No explicit check is added for path segment 2 (the provider authority). Whether the LocalUriMatcher patterns constrain it is not shown. The existing permission check and the downstream ID selection logic are unchanged.",
      "On 16/17, isValidPickerUri accepts PICKER_ID, PICKER_GET_CONTENT_ID and PICKER_TRANSCODED_ID. The 15 variant accepts only PICKER_ID and PICKER_GET_CONTENT_ID."
     ],
     "behavior_changes": [
      "Pre-selection URIs are now ignored (logged at debug level) instead of being permission-checked and used if they are not content:// URIs or if they do not match the picker URI patterns of the media-authority matcher.",
      "Pre-selection URIs whose encoded user ID differs from the MediaProvider process user are now ignored. A cross-user pre-selection URI no longer pre-selects any item (testQueryMediaForPreSelection_ignoresCrossUserUris expects 0 results).",
      "PickerUriResolver.getUserId(Uri) becomes public, and PickerUriResolver gains a new public static isValidPickerUri(Uri)."
     ],
     "preconditions": [
      "A caller supplies pre-selection URIs to the photopicker through EXTRA_PICKER_PRE_SELECTION_URIS (commit message). In the tests these arrive as the pre_selection_uris or PickerDataLayer.QUERY_ID_SELECTION query argument together with the calling package UID (Intent.EXTRA_UID or EXTRA_CALLING_PACKAGE_UID).",
      "The URIs are processed by PickerUriResolver.processUrisForSelection or by MediaQueryForPreSelection (v2 path, via PickerDataLayerV2.queryMediaForPreSelection per the tests). Before the fix, only checkUriPermission gated them."
     ],
     "per_major": {
      "14": "Not affected per the bulletin (not listed). The per-major facts show only 2/8 lines of the patched region at android-security-14.0.0_r29.",
      "15": "Adapted fix (1b032be59978, android-security-15.0.0_r17). Only PickerUriResolver.java is patched: sStaticUriMatcher, isValidPickerUri, both pre-checks in processUrisForSelection, and an added androidx.annotation.Nullable import. isValidPickerUri accepts only PICKER_ID and PICKER_GET_CONTENT_ID (no PICKER_TRANSCODED_ID). The diff does not touch MediaQueryForPreSelection or PickerDataLayerV2Test and does not change getUserId's visibility. 8/8 lines of the patched region present.",
      "16": "Same fix (32217e55e510, android-security-16.0.0_r8). The source changes in PickerUriResolver.java and MediaQueryForPreSelection.java are functionally identical to 17 (only the indentation of the sStaticUriMatcher declaration differs). The same tests are added to PickerDataLayerV2Test at different line offsets. 7/8 lines present.",
      "17": "This fix (50d220c204df, android-security-17.0.0_r1), a RESTRICT AUTOMERGE cherry-pick with Change-Id I7bf6d97a7fac3ecc708df96a93363cbb3e512216. 7/8 lines present."
     },
     "backport_notes": [
      "On 16/17 the PickerUriResolver.java hunk adds no imports, so LocalUriMatcher, ContentResolver, UserHandle, Nullable and the PICKER_ID / PICKER_GET_CONTENT_ID / PICKER_TRANSCODED_ID constants must already be visible there. The 15 variant adds the Nullable import and omits PICKER_TRANSCODED_ID. MediaQueryForPreSelection.java adds an android.os.UserHandle import. On trees without photopicker v2 (MediaQueryForPreSelection), only the PickerUriResolver hunk applies.",
      "When backporting the MediaQueryForPreSelection hunk, the hunk depends on the getUserId visibility change (package-private to public static) in PickerUriResolver. Tests that use TEST_USER for pre-selection URIs must switch to UserHandle.myUserId()."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show exactly what information is disclosed or how the issue is triggered. The bulletin only rates it High / ID, and the commit message describes a profile/data mismatch in database queries.",
      "The material does not show which exceptions the surrounding try/catch in each loop handles, so it is unclear how a NumberFormatException from getUserId would behave. The structural check runs first, and the ignoresMalformedUris test suggests the matcher rejects malformed user segments before that point.",
      "The LocalUriMatcher patterns for the accepted match codes are not shown, so it is unclear whether the provider-authority path segment is constrained, or whether downstream code checks it against the active local/cloud provider.",
      "The material does not show whether other pre-selection ingestion paths exist beyond the two loops patched here."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45520": {
     "cve": "CVE-2026-45520",
     "summary": "BiometricsSettingsBase now accepts the EXTRA_USER_ID and gatekeeper password handle intent extras only when the hosting SettingsActivity was launched by the Settings package itself; external callers get the current user and no gatekeeper handle, so credential confirmation is enforced.",
     "component": "platform/packages/apps/Settings, biometrics/combination (BiometricsSettingsBase, hosted by CombinedBiometricSettingsActivity)",
     "weakness": "Trust of caller-controlled intent extras without origin check (improper authorization / missing caller validation)",
     "root_cause": "In src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java, onAttach() read Intent.EXTRA_USER_ID from the host activity intent unconditionally into mUserId, and onCreate() read the gatekeeper password handle (ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE via BiometricUtils.containsGatekeeperPasswordHandle/getGatekeeperPasswordHandle) into mGkPwHandle whenever it was present. Nothing distinguished an internal Settings launch from an external one, so per the commit message an unprivileged app launching CombinedBiometricSettingsActivity could supply a target user ID and a password handle, skipping the device credential confirmation step and acting on biometric settings for another user.",
     "fix_analysis": [
      "BiometricsSettingsBase.onAttach: mUserId is initialised to UserHandle.myUserId() and a new field mAllowInternalExtras is set to false. Only if getActivity() is a SettingsActivity and activity.getInitialCallingPackage() equals activity.getPackageName() (TextUtils.equals) is mAllowInternalExtras set true and mUserId overridden from Intent.EXTRA_USER_ID.",
      "BiometricsSettingsBase.onCreate: the gatekeeper handle is now read only when mAllowInternalExtras is true, i.e. `if (mAllowInternalExtras && BiometricUtils.containsGatekeeperPasswordHandle(getIntent()))`; otherwise mGkPwHandle stays 0 and the existing flow proceeds to launchChooseOrConfirmLock() (verified by the new test testExternalCaller_ignoresSensitiveExtras).",
      "Tests (CombinedBiometricProfileSettingsTest): the host activity is now built as SettingsActivity instead of FragmentActivity, ShadowPasswordUtils is added and setCallingAppPackageName set to the Settings package in setUp; two new tests cover an internal caller (extras honoured: user 10, handle 12345) and an external caller identified via SettingsActivity.EXTRA_INITIAL_CALLING_PACKAGE = \"com.poc.app\" (user falls back to myUserId, handle 0, launchChooseOrConfirmLock invoked)."
     ],
     "completeness": [
      "The gate is in the abstract base class, so every BiometricsSettingsBase subclass (e.g. the combined biometric profile settings fragment exercised by the test) inherits it.",
      "Fail-closed design for the two changed reads: if the host activity is not a SettingsActivity, or the calling package does not match, both extras are ignored.",
      "Both sensitive extras read in the shown hunks (EXTRA_USER_ID in onAttach, EXTRA_KEY_GK_PW_HANDLE in onCreate) are covered; the check compares only the package name string, so the strength of the fix depends on how SettingsActivity.getInitialCallingPackage() is derived, which is not shown in the material.",
      "The commit lists two Cherrypick-From sources but only one diff is provided; whether the second upstream change touched other files is not visible."
     ],
     "behavior_changes": [
      "External launches of the combined biometric settings now always operate on the current user (UserHandle.myUserId()) regardless of EXTRA_USER_ID.",
      "External launches never receive a pre-supplied gatekeeper password handle, so the device credential confirmation (launchChooseOrConfirmLock) is always required for them.",
      "Internal launches from the Settings package keep the previous behaviour (extras honoured).",
      "If the fragment is hosted by an activity that is not a SettingsActivity, the extras are now ignored even for internal use."
     ],
     "preconditions": [
      "A local app able to launch CombinedBiometricSettingsActivity with crafted intent extras (the commit message describes external callers launching it).",
      "Pre-fix: the fragment reads EXTRA_USER_ID and EXTRA_KEY_GK_PW_HANDLE from the launching intent without checking the caller."
     ],
     "per_major": {
      "14": "same fix (adapted trivially): commit a88ca4587208 in android-security-14.0.0_r29 adds the same onAttach/onCreate gating plus an extra `import android.app.Activity;` missing on that branch; the test class is @Ignore on 14 but received the same new tests. Patched region 8/8 lines present.",
      "15": "same fix: commit 783b7806995f in android-security-15.0.0_r17, identical source change; test diff also adds a SetFlagsRule import. 8/8 lines present.",
      "16": "same fix: commit 23e5e4077bc9 in android-security-16.0.0_r8, identical source change. 8/8 lines present.",
      "17": "same fix: commit 759e75cec1c1 in android-security-17.0.0_r1 (the analysed commit). 8/8 lines present."
     },
     "backport_notes": [
      "The source change is self-contained in BiometricsSettingsBase.java but depends on SettingsActivity.getInitialCallingPackage() existing on the target branch; on Android 14 the android.app.Activity import must also be added, as Google did. The test changes additionally use SettingsActivity.EXTRA_INITIAL_CALLING_PACKAGE and the ShadowPasswordUtils test shadow with setCallingAppPackageName/reset."
     ],
     "confidence": "high",
     "unknowns": [
      "How SettingsActivity.getInitialCallingPackage() obtains the calling package (the test sets it via both ShadowPasswordUtils.setCallingAppPackageName and the EXTRA_INITIAL_CALLING_PACKAGE extra, but the implementation is not in the material).",
      "Whether other parts of BiometricsSettingsBase.java (not shown in the material) read further caller-supplied intent extras.",
      "Contents of the second Cherrypick-From commit (c50b2a121cf3...) referenced in the commit message; only one diff is provided.",
      "Which concrete settings (beyond the Watch Unlock example in the commit message) could be manipulated once credential confirmation was skipped."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45521": {
     "cve": "CVE-2026-45521",
     "summary": "StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid() and AppFuseBridge.openFile rejects the request (FuseUnavailableMountException, surfaced as a null return) when the caller UID does not match the uid recorded in the AppFuse MountScope for the requested mountId.",
     "component": "platform/frameworks/base, system_server StorageManagerService / AppFuse bridge (services/core)",
     "weakness": "missing authorization check (caller identity not compared against resource owner)",
     "root_cause": "AppFuse mounts are created in StorageManagerService (the mount-creation code just above openProxyFileDescriptor), which builds an AppFuseMountScope(uid, name) holding the caller's uid. AppFuseBridge.addBridge registers that scope in mScopes, keyed by mountId. The Binder entry point StorageManagerService.openProxyFileDescriptor(mountId, fileId, mode) looked up the scope by mountId alone and, if the scope existed, returned scope.openFile(mountId, fileId, flags) to any caller. Nothing compared the calling UID with the scope's owning uid, so a caller supplying another app's mountId and fileId could get a ParcelFileDescriptor backed by that app's AppFuse mount.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java:openProxyFileDescriptor: reads `final int uid = Binder.getCallingUid()` at entry (before mode masking) and passes it as a new first argument to mAppFuseBridge.openFile(uid, mountId, fileId, mode).",
      "services/core/java/com/android/server/storage/AppFuseBridge.java:openFile: the signature becomes openFile(int uid, int mountId, int fileId, int mode). Inside the existing `synchronized (this)` block, the condition `scope == null` becomes `scope == null || scope.uid != uid`. An owner mismatch therefore throws FuseUnavailableMountException(mountId), the same as a non-existent mount, before scope.waitForMount() and scope.openFile() are reached.",
      "The caller already catches FuseUnavailableMountException and returns null with a Slog.v message, so the mismatch path reuses the existing failure handling. No new exception type is introduced."
     ],
     "completeness": [
      "The check runs under the AppFuseBridge monitor on the scope fetched from mScopes. It uses the uid stored when the scope was created (AppFuseMountScope(uid, name)), so the ownership comparison matches how the mount was registered.",
      "Only the openFile path changes. The diff and context do not show any other externally reachable AppFuseBridge operation that takes a mountId from a caller. Whether other such paths exist is not shown in the material.",
      "The fix does not add a permission check to the openProxyFileDescriptor Binder method itself. It relies on per-mount UID ownership instead. A mismatch is reported the same way as an unknown mountId (null return), so the result does not reveal whether the mountId exists.",
      "The Android 17 commit lists two internal Cherrypick-From changes. Only the resulting change is visible, so nothing can be said about the individual internal changes beyond what the diff shows."
     ],
     "behavior_changes": [
      "openProxyFileDescriptor now returns null, with a verbose 'The mount point has already been invalid' log, when the calling UID differs from the mount owner's uid. Previously it returned a ParcelFileDescriptor in that case.",
      "Any legitimate caller that opened proxy file descriptors on a mount created under a different UID is now refused. The material does not show whether such a caller exists.",
      "AppFuseBridge.openFile has a new signature (an extra leading int uid parameter). Any other in-tree caller of openFile must be updated or it will fail to compile."
     ],
     "preconditions": [
      "The caller can invoke IStorageManager.openProxyFileDescriptor on the StorageManagerService Binder interface.",
      "mAppFuseBridge has been created and an AppFuse mount owned by a different UID is registered in mScopes (i.e. another app has used the proxy file descriptor bridge).",
      "The caller supplies a mountId and fileId belonging to that other mount. The material does not show how these identifiers are allocated, beyond mNextAppFuseName++ for the mount name."
     ],
     "per_major": {
      "14": "same fix: commit 0d2ee8e16afc in android-security-14.0.0_r29. The diff to both files is identical; only line offsets differ in StorageManagerService.java.",
      "15": "same fix: commit bca099fcb293 in android-security-15.0.0_r17, identical diff.",
      "16": "same fix: commit 63f908ebd9ee in android-security-16.0.0_r8, identical diff.",
      "17": "same fix: commit 03988cf95e56 in android-security-17.0.0_r1 (lists two internal Cherrypick-From changes, Change-Id I83e8105de792c1a805cf029e7cbf57a611786995)."
     },
     "backport_notes": [
      "Single self-contained commit. The AppFuseBridge.java hunk is byte-identical on 14 through 17 (same blob ids 292314840efb..d839b30ddf44), so the StorageManagerService.java hunk is the only part that may need offset adjustment. The fix requires MountScope to already expose a `uid` field populated by AppFuseMountScope(uid, name); verify this on any fork before applying.",
      "Update every caller of AppFuseBridge.openFile to the new (uid, mountId, fileId, mode) signature. In AOSP the only caller shown is openProxyFileDescriptor."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether openProxyFileDescriptor had any permission or caller-type gating before this fix (not shown in the context).",
      "The definition of MountScope and how its uid field is declared (inferred from the scope.uid usage and the AppFuseMountScope(uid, name) construction).",
      "The contents of the two internal Cherrypick-From changes individually; only the resulting commit is visible.",
      "The name of the StorageManagerService method that creates AppFuse mounts (the context does not show it).",
      "Why the resolver reports 7/8 patched-region lines present on each branch even though the commits show identical diffs."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45525": {
     "cve": "CVE-2026-45525",
     "summary": "MediaProvider's ISO/MP4 XMP handling is changed so that an XMP box larger than the allocation limit no longer aborts box parsing and is instead redacted in its entirety, closing a location-metadata redaction bypass.",
     "component": "platform/packages/providers/MediaProvider, media metadata redaction (IsoInterface / XmpDataParser)",
     "weakness": "Improper handling of an exceptional (oversized input) condition leading to missing redaction of sensitive data (information disclosure)",
     "root_cause": "Two defects worked together. (1) In the IsoInterface box-parsing code (the method name is not shown in the material, around line 190), when an XMP box (a UUID box with XMP_UUID, or a BOX_XMP box) went over the allocateBuffer memory limit, allocateBuffer returned null and the code ran `return false`. That aborted parsing, so no box after the oversized XMP box was recorded (per the commit message). (2) In XmpDataParser.XmpData.extractXmpData(IsoInterface), when getBoxBytesForXmpUuid returned null the code fell back to BOX_XMP. If the buffer was still null, it replaced the buffer with an empty array and also reset xmpOffsets to `new long[0]`. getRedactionRanges(XmpData) then treated this as an empty document and returned no redaction ranges for the oversized XMP data.",
     "fix_analysis": [
      "src/com/android/providers/media/util/IsoInterface.java (box parsing, UUID/XMP_UUID branch and BOX_XMP branch): `if (box.data == null) return false; Os.read(...)` becomes `if (box.data != null) { Os.read(...) }`. An oversized XMP box now leaves box.data null, but the Box (with its range) is still created and parsing goes on to the following boxes.",
      "src/com/android/providers/media/util/XmpDataParser.java: XmpData.extractXmpData(IsoInterface): the BOX_XMP fallback is now taken only when `buf == null && xmpOffsets.length == 0`, not when the UUID XMP box exists but was too large to load. The `xmpOffsets = new long[0];` reset is removed, so the offsets of the oversized box stay in the XmpData.",
      "src/com/android/providers/media/util/XmpDataParser.java: getRedactionRanges(XmpData): new early return. If `xmpData.mRawXmp.length == 0 && xmpData.mXmpOffsets.length > 0`, it returns `LongArray.fromArray(xmpData.mXmpOffsets, xmpData.mXmpOffsets.length)`, so the whole XMP box range is flagged for redaction instead of an empty document being parsed.",
      "tests: adds tests/res/raw/gps_test_xmp_big.mp4 and XmpDataParserTest#testContainer_IsoRedactionRanges_OversizedXmp. The test asserts that the oversized box is still listed, that exactly one [start,end] range is returned, and that createXmpInterface gives an empty redacted XMP."
     ],
     "completeness": [
      "Covers both ISO XMP carriers, UUID boxes with XMP_UUID and BOX_XMP boxes, in the parser (no abort) and in the range computation (whole-range redaction).",
      "On 15/16/17 only the IsoInterface path of XmpData.extractXmpData is changed. The ExifInterface path (extractXmpData(ExifInterface)) is not touched in this diff, and how it handles oversized or absent XMP is not shown.",
      "The new early return fires for any XmpData with an empty buffer and non-empty offsets. The material does not show whether this also happens for a legitimately zero-length XMP box, or what range would be redacted then.",
      "When a UUID XMP box is present (loaded or oversized), extractXmpData never looks at a BOX_XMP box in the same file. This single-source behaviour existed before the fix and is unchanged.",
      "The fix relies on the box range stored by IsoInterface being usable directly as a redaction [start,end] pair. The material does not show getBoxRangesForXmpUuid/getBoxRanges, so the pair semantics are not confirmed."
     ],
     "behavior_changes": [
      "ISO/MP4 files whose XMP box exceeds the allocateBuffer limit are now parsed to the end, and boxes after the XMP box are recorded.",
      "For these files, the entire XMP box range is returned as a redaction range.",
      "If the UUID XMP box exists but is oversized, no fallback to a BOX_XMP box is attempted. Before the fix, a null buffer always triggered the fallback.",
      "createXmpInterface on such a file returns an interface with empty redacted XMP."
     ],
     "preconditions": [
      "An ISO-BMFF/MP4 file containing an XMP box (UUID XMP_UUID or BOX_XMP) whose payload exceeds the allocateBuffer size limit (1 MiB per the commit message).",
      "The file is read through a path where MediaProvider computes redaction ranges. Per index search, MediaProvider.getRedactionRangesForFuse calls RedactionUtils.getRedactionRanges(file). The link from there to XmpDataParser.getRedactionRanges(IsoInterface) is not shown in the material.",
      "Redaction applies to the reader (forceRedaction or the redaction policy in getRedactionRangesForFuse). The exact policy is not shown."
     ],
     "per_major": {
      "14": "adapted fix (93cf0194cff2, android-security-14.0.0_r29): the patch has no allocateBuffer call and changes XmpInterface rather than XmpDataParser. It adds MAX_XMP_SIZE_BYTES (1 MiB) and a public XMP_UUID constant to IsoInterface and reads UUID box data only for XMP_UUID boxes. When an XMP or UUID-XMP box exceeds the limit or hits OOM, it now logs and continues instead of returning null. XmpInterface.process now accepts a null rawXmp and adds [xmpOffsets[0], xmpOffsets[1]] to mRedactedRanges. It also converts Exif TAG_XMP ranges from [start,length] to [start,end] (enclosing method not named in the hunk). The test is added to XmpInterfaceTest.",
      "15": "same fix (ec0834680bfe, android-security-15.0.0_r17): same content as the 17 commit. Only the IsoInterface.java hunk sits one line earlier (@@ -188). The XmpDataParser.java hunks are identical.",
      "16": "same fix (cedfc5086b8d, android-security-16.0.0_r8): identical diff to the 17 commit.",
      "17": "same fix (21270fd56118, android-security-17.0.0_r1): the reference commit described above."
     },
     "backport_notes": [
      "Branches that have allocateBuffer in IsoInterface, getBoxBytesForXmpUuid/getBoxRangesForXmpUuid and XmpDataParser (15/16/17) take the reference diff unchanged. Branches on the older XmpInterface-based code (14) need the adapted variant, which adds the size limit itself and handles a null rawXmp in XmpInterface.process.",
      "No 'Fix in fork' section was provided, so no per-file fork verdict can be reported."
     ],
     "confidence": "high",
     "unknowns": [
      "The exact IsoInterface method name containing the patched box-parsing code is not shown in the material.",
      "The semantics of the long[] pairs returned by getBoxRangesForXmpUuid/getBoxRanges ([start,end] vs [start,length]) are not shown; the fix returns them directly as redaction ranges.",
      "Whether the ExifInterface path of XmpData.extractXmpData on 15/16/17 needs, or already has, equivalent handling. The 14 patch adjusted the Exif offsets; the 17 patch does not touch that path.",
      "How IsoInterface moves past a box whose data was not read (the seek logic after the patched region is not shown).",
      "Whether RedactionUtils.getRedactionRanges reaches XmpDataParser.getRedactionRanges(IsoInterface) for MP4 files, and what decides that redaction applies to a reader.",
      "How a zero-length XMP box is handled by the new early return."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45527": {
     "cve": "CVE-2026-45527",
     "summary": "In HeifCleanAperture.cpp:convertCleanApertureToRect(), the fix performs the crop-rect bounds check in int64_t arithmetic before computing *right and *bottom, so crafted CleanAperture values can no longer trigger a signed int32 overflow (IntSan abort) in media.extractor.",
     "component": "platform/frameworks/av, media/module/extractors/mp4 (HEIF clean aperture handling in the MP4 extractor)",
     "weakness": "Signed integer overflow due to a bounds check performed after the overflowing addition (and an ineffective overflow pre-check)",
     "root_cause": "convertCleanApertureToRect() computed `*right = *left + clapW` and `*bottom = *top + clapH` as int32_t additions immediately after obtaining *left/*top from centerX.getInt32()/centerY.getInt32(), and only afterwards checked the result against the image bounds. The pre-existing guard `*left > (UINT32_MAX - clapW)` is evaluated in unsigned arithmetic and cannot catch an int32 overflow (for non-negative *left and positive clapW the right-hand side is always at least INT32_MAX), so with a horizOff/vertOff that pushes *left or *top close to INT32_MAX (e.g. the new test case horizOff = 0x7FFFFFFE with imageW = 2, clapW = 2) the addition overflows int32_t. Under IntSan this aborts the media.extractor process (per the commit message); the bulletin rates this High / DoS.",
     "fix_analysis": [
      "media/module/extractors/mp4/HeifCleanAperture.cpp:convertCleanApertureToRect(): the two additions `*right = *left + clapW;` and `*bottom = *top + clapH;` are moved below the bounds check, so they are only executed once the result is known to fit.",
      "Same function: the bounds check is rewritten as `(int64_t)*left + clapW > (int64_t)imageW || (int64_t)*top + clapH > (int64_t)imageH`, i.e. the sum is formed in int64_t (no overflow possible for two int32 operands) and compared against the image dimension. Because the function already rejects imageW/imageH > INT32_MAX, *left/*top >= 0 (centerX.n < 0 / centerY.n < 0 checks) and clapW/clapH > 0, passing this check guarantees the subsequent int32 additions cannot overflow.",
      "The ineffective unsigned sub-conditions `*left > (UINT32_MAX - clapW)` / `*top > (UINT32_MAX - clapH)` and the post-hoc `*right > imageW` / `*bottom > imageH` comparisons are removed, since the int64 check subsumes them.",
      "media/module/extractors/mp4/tests/HeifCleanApertureUnitTest.cpp: two entries added to kInvalidClapPropertyTestParams (imageW/H = 2, clap 2x2, horizOff or vertOff = 0x7FFFFFFE) that exercise the horizontal and vertical overflow; InvalidClapPropertyTest expects convertCleanApertureToRect() to return false."
     ],
     "completeness": [
      "Both overflow sites in the function (right = left + clapW and bottom = top + clapH) are covered by the single int64 check.",
      "The remaining arithmetic in the function (centerX/centerY add/subtract with halfW/halfH and horizOff/vertOff) goes through Fraction::add/subtract, whose bool return is checked; the index snippet shows an overflowsInt32() helper in HeifCleanAperture.cpp, but the bodies of add/subtract are not shown. The diff does not touch them.",
      "The fix is limited to convertCleanApertureToRect(); the material does not show the caller that parses the clap box, so whether callers do further arithmetic on the returned rect is not visible."
     ],
     "behavior_changes": [
      "convertCleanApertureToRect() now returns false (instead of overflowing/aborting) when left + clapW exceeds imageW or top + clapH exceeds imageH, evaluated in int64.",
      "*right and *bottom are now only written when the function succeeds; previously they were written before the bounds check even on the failure path.",
      "No change for valid clean apertures: any rect that passed the old check also satisfies the new one."
     ],
     "preconditions": [
      "A HEIF/MP4 file containing a CleanAperture (clap) property reaches convertCleanApertureToRect() in the MP4 extractor (media.extractor process per the commit message); the exact parsing caller is not shown in the material.",
      "The clap values must pass the earlier validation: positive denominators, non-negative integer width/height, non-zero clapW/clapH, image dimensions <= INT32_MAX, and centerX/centerY integer and non-negative with Fraction add/subtract succeeding.",
      "horizOff or vertOff large enough that *left + clapW or *top + clapH exceeds INT32_MAX (as in the added test vectors)."
     ],
     "per_major": {
      "14": "not affected per bulletin; media/module/extractors/mp4/HeifCleanAperture.cpp and its unit test are absent at android-security-14.0.0_r29 (code absent).",
      "15": "same fix: platform/frameworks/av c92a92203957 in android-security-15.0.0_r17, identical diff to the Android 17 commit (same file blobs f0a086703e -> 7d78f0e869).",
      "16": "same fix: platform/frameworks/av 9456667afa01 in android-security-16.0.0_r8, identical diff.",
      "17": "same fix: platform/frameworks/av 1e00537f8f43 in android-security-17.0.0_r1 (Change-Id I2b282fc9e69a6510e9414fd2bbfe3beddf81001e)."
     },
     "backport_notes": [
      "Single self-contained commit; the pre-image blob is the same (f0a086703e) on 15/16/17, so the patch should apply cleanly wherever that version of HeifCleanAperture.cpp exists. The file is absent at android-security-14.0.0_r29."
     ],
     "confidence": "high",
     "unknowns": [
      "The caller that parses the clap box and invokes convertCleanApertureToRect() is not shown in the material (index search returned the header, a HeifCleanAperture.cpp helper snippet and the unit test, but no parsing caller).",
      "The implementation of Fraction::add/subtract is not shown, so their overflow handling is not verified from the material.",
      "Whether the IntSan abort is the only consequence, or whether a non-sanitized build would misuse the wrapped rect, is not shown; the bulletin classifies it as DoS only."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45528": {
     "cve": "CVE-2026-45528",
     "summary": "StorageManagerService.getManageSpaceActivityIntent now builds the returned PendingIntent through ActivityManagerInternal.getPendingIntentActivityAsApp(..., packageName, appInfo.uid). Before, it called PendingIntent.getActivity on a package Context created by system_server with the caller identity cleared. Per the commit message, this stops callers from obtaining a PendingIntent registered under the target package identity with system privileges.",
     "component": "platform/frameworks/base, StorageManagerService (services/core/java/com/android/server/StorageManagerService.java)",
     "weakness": "Improper privilege management: a PendingIntent created with system_server identity on behalf of a third-party package (confused deputy)",
     "root_cause": "In StorageManagerService.getManageSpaceActivityIntent, after checking that the caller has external storage access, the code cleared the Binder calling identity and created a Context for the target package with mContext.createPackageContextAsUser (createPackageContext on 14/15). It then called PendingIntent.getActivity on that Context. That Context came from system_server's own Context. Per the commit message, this let callers (apps that pass hasExternalStorageAccess, i.e. file-manager style apps) obtain a PendingIntent registered under the target package identity with system privileges.",
     "fix_analysis": [
      "services/core/java/com/android/server/StorageManagerService.java:getManageSpaceActivityIntent: removes the Binder.clearCallingIdentity/restoreCallingIdentity block. It also removes the mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(UserHandle.getUserId(originalUid))) call that produced the system-backed target Context.",
      "Same function: the Intent (ACTION_DEFAULT, setClassName(packageName, appInfo.manageSpaceActivityName), FLAG_ACTIVITY_NEW_TASK) and the ActivityOptions (MODE_BACKGROUND_ACTIVITY_START_DENIED for the creator) are unchanged.",
      "Same function: the PendingIntent now comes from LocalServices.getService(ActivityManagerInternal.class).getPendingIntentActivityAsApp(requestCode, intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, options.toBundle(), packageName, appInfo.uid). The target package and its uid are passed as the app the PendingIntent is created for. appInfo.uid is resolved earlier with getApplicationInfo for the caller's user.",
      "Same function: the catch of PackageManager.NameNotFoundException (which threw IllegalArgumentException(\"packageName not found\")) is removed. The earlier appInfo == null check already rejects unknown packages with IllegalArgumentException(\"Invalid packageName\")."
     ],
     "completeness": [
      "The fix covers the single PendingIntent construction site in getManageSpaceActivityIntent. The diff touches no other function and no other file.",
      "The permission gate (mStorageManagerInternal.hasExternalStorageAccess on packagesFromUid[0]) and the package lookup (mIPackageManager.getApplicationInfo for the caller's user) are unchanged. The fix only changes how the PendingIntent is created and whose identity it carries.",
      "The PendingIntent flags (FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE) and the background-start-denied creator option are kept, so the change is purely about the creator identity.",
      "The material does not show the implementation of ActivityManagerInternal.getPendingIntentActivityAsApp, so it is not visible whether it does any extra check that packageName and uid match."
     ],
     "behavior_changes": [
      "The PendingIntent is now created through ActivityManagerInternal.getPendingIntentActivityAsApp with packageName and appInfo.uid. It is no longer created through a system_server-derived package Context.",
      "The IllegalArgumentException(\"packageName not found\") path for PackageManager.NameNotFoundException is removed. Unknown packages are still rejected earlier with IllegalArgumentException(\"Invalid packageName\").",
      "The method no longer clears the Binder identity. The call to ActivityManagerInternal is an in-process LocalServices call."
     ],
     "preconditions": [
      "The caller reaches IStorageManager.getManageSpaceActivityIntent over Binder and passes mStorageManagerInternal.hasExternalStorageAccess for its uid and first package (MANAGE_EXTERNAL_STORAGE style file-manager access).",
      "The target packageName must resolve through mIPackageManager.getApplicationInfo in the caller's user and must declare a manageSpaceActivityName (otherwise the method returns null)."
     ],
     "per_major": {
      "14": "same fix (commit 51b647175642 in android-security-14.0.0_r29, RESTRICT AUTOMERGE). The removed code used mContext.createPackageContext(packageName, 0) (not the AsUser variant). The added block is identical to the 17 version.",
      "15": "same fix (commit 8c8709a5ad8c in android-security-15.0.0_r17, RESTRICT AUTOMERGE). The removed code used createPackageContext(packageName, 0). The added block is identical to 17.",
      "16": "same fix (commit 84d85dbe121f in android-security-16.0.0_r8, RESTRICT AUTOMERGE). The removed code used createPackageContextAsUser with UserHandle.of(UserHandle.getUserId(originalUid)). The added block is identical to 17.",
      "17": "same fix (commit 43ef4a09e448 in android-security-17.0.0_r1, RESTRICT AUTOMERGE, Cherrypick-From commit 50d6b5520df2ffe73f2bd5f5ee7265c52a0cee9e, Change-Id I107885fe1d3203c11868c2cabbb68ed164811bbd)."
     },
     "backport_notes": [
      "The replacement block is identical across 14 through 17. Only the removed lines differ (createPackageContext vs createPackageContextAsUser). Per its diffstat, the 17 commit adds no imports. A backport to another tree should confirm that LocalServices and ActivityManagerInternal are imported, and that ActivityManagerInternal exposes getPendingIntentActivityAsApp(int, Intent, int, Bundle, String, int). The 14 fix uses this method, so it exists on the 14 security branch."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the body of ActivityManagerInternal.getPendingIntentActivityAsApp, so the exact checks it applies to the packageName/uid pair, and how a later send of the PendingIntent is attributed, are not visible.",
      "Beyond the commit message and the bulletin's EoP/High rating, the material does not describe what privilege a caller gained from the system-backed PendingIntent or how it was used.",
      "No test is referenced in the commit (the Test: field is empty).",
      "The 14, 15 and 16 diffs are shown only as the function hunk, so whether they also change imports is not visible."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-45529": {
     "cve": "CVE-2026-45529",
     "summary": "MmsSmsProvider.query now limits the simple-conversations, conversation recipients/subject, search and searchSuggest URIs to messages whose sub_id belongs to a subscription associated with the calling user. It returns an empty cursor when the user has no associated subscriptions.",
     "component": "platform/packages/providers/TelephonyProvider, MmsSmsProvider (content://mms-sms)",
     "weakness": "Missing authorization filter (per-user subscription scoping not applied to several query paths)",
     "root_cause": "MmsSmsProvider.query() already had a per-user subscription filter string, selectionBySubIds. The test mocks suggest it is built from SubscriptionManager.getSubscriptionInfoListAssociatedWithUser. Several query branches did not use it: URI_CONVERSATIONS with simple=true (getSimpleConversations), URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT (getConversationById), URI_SEARCH_SUGGEST (fixed SEARCH_QUERY on the words table) and URI_SEARCH (getTextSearchQuery over the sms/pdu tables). As a result these branches could return conversation, thread and full-text search results for messages of subscriptions not associated with the calling user. The bulletin rates the issue as a High EoP.",
     "fix_analysis": [
      "src/com/android/providers/telephony/MmsSmsProvider.java: the SEARCH_QUERY constant drops its trailing ' ORDER BY snippet LIMIT 50;' so a WHERE condition can be appended. The URI_SEARCH_SUGGEST case now builds searchQuery = SEARCH_QUERY + \" AND \" + selectionBySubIds.replace(\"'\", \"\") + \" ORDER BY snippet LIMIT 50;\" and passes it to db.rawQuery.",
      "MmsSmsProvider.getTextSearchQuery gets a new selectionBySubIds parameter. It builds smsSubIdClause (\" AND \" + smsTable + \".\" + selectionBySubIds with quotes stripped) for the sms sub-select WHERE, and mmsSubIdClause (the same with pduTable) for the pdu/part sub-select WHERE. Both are added only when selectionBySubIds is non-empty. The URI_SEARCH case passes selectionBySubIds through.",
      "MmsSmsProvider.query, in the URI_CONVERSATIONS simple=true branch, URI_CONVERSATIONS_RECIPIENTS and URI_CONVERSATIONS_SUBJECT: if selectionBySubIds == null the method logs and returns emptyCursor. Otherwise it sets selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds) before calling getSimpleConversations / getConversationById.",
      "MmsSmsProvider.query: URI_SEARCH_SUGGEST and URI_SEARCH also return emptyCursor early when selectionBySubIds == null. In 17 the suggest check comes after the existing otpFilter early return. In URI_SEARCH it comes after an existing check whose exception message ends in 'with this query'.",
      "tests/src/com/android/providers/telephony/MmsSmsProviderTest.java: adds two tests. testQuery_subIdEmpty_returnsEmptyCursor mocks getSubscriptionInfoListAssociatedWithUser to return an empty list and asserts a count of 0 for the five URIs. testQuery_subIdValid_doesNotReturnEmptyCursor asserts a non-null cursor with the default sub_id mocks."
     ],
     "completeness": [
      "Covers exactly five query branches: URI_CONVERSATIONS (simple=true only), URI_CONVERSATIONS_RECIPIENTS, URI_CONVERSATIONS_SUBJECT, URI_SEARCH_SUGGEST and URI_SEARCH. The diff does not show the non-simple URI_CONVERSATIONS branch or the other cases in the switch (e.g. URI_MESSAGE_ID_TO_THREAD), so the material cannot show whether they already apply selectionBySubIds.",
      "The null check in query() and the clause logic in getTextSearchQuery do not match. query() returns emptyCursor only when selectionBySubIds is null, while getTextSearchQuery skips the clause when the string is empty (TextUtils.isEmpty). An empty non-null string would pass the null checks and produce an unfiltered sms/pdu search. In URI_SEARCH_SUGGEST it would produce malformed SQL (' AND  ORDER BY'). The material does not show whether selectionBySubIds can be an empty string.",
      "For URI_SEARCH_SUGGEST the filter is appended, without a table prefix, to a query on the words table. For RECIPIENTS/SUBJECT it is joined into the selection passed to getConversationById. The material does not show the schema of words or which table getConversationById queries, so the diff cannot confirm that those column references are correct.",
      "The changes are limited to query(), the private helper getTextSearchQuery and the SEARCH_QUERY constant. The insert/update/delete paths of MmsSmsProvider are not touched by this commit."
     ],
     "behavior_changes": [
      "Queries on content://mms-sms/conversations?simple=true, conversations/<id>/recipients, conversations/<id>/subject, search and searchSuggest return an empty cursor (not null) when the calling user has no associated subscriptions. Each case writes a Log.d line; the suggest path logs it as 'URI_CONVERSATIONS_SUGGEST'.",
      "When subscriptions exist, results for those URIs are limited to rows whose sub_id matches the user's subscription filter. Rows of other subscriptions are no longer returned.",
      "The SEARCH_QUERY constant no longer contains ORDER BY/LIMIT; the full statement is built on each call. getTextSearchQuery has a new trailing String parameter. It is private static, so there is no external API impact.",
      "The search and suggest SQL strings now include the sub_id clause, with single quotes removed by String.replace."
     ],
     "preconditions": [
      "The caller can query the mms-sms content provider on one of the five affected URIs. The provider's existing permission checks are not shown in the material.",
      "The device has messages whose sub_id belongs to a subscription not associated with the calling user (multi-user / per-user subscription association), because the fix depends on getSubscriptionInfoListAssociatedWithUser.",
      "Before the fix, selectionBySubIds was not applied on these branches, so reaching them needed nothing beyond a normal query."
     ],
     "per_major": {
      "14": "adapted fix: commit 475853ddf6b3 in android-security-14.0.0_r29. Same five branches and the same SEARCH_QUERY change. Differences from 17: getTextSearchQuery(smsTable, pduTable, selectionBySubIds) has no canReadRestrictedMessages/otpFilter parameters; getSimpleConversations/getConversationById take sortOrder instead of the restricted/otp arguments; URI_SEARCH_SUGGEST has no otpFilter pre-check; the tests lack @DisableFlags and the commit adds the assertEquals import. Resolver: 6/8 patched-region lines present.",
      "15": "adapted fix: commit 4fab244beaa3 in android-security-15.0.0_r17. The code changes are the same as the 14 backport, applied at different offsets to a different base blob. Resolver: 6/8 lines present.",
      "16": "adapted fix: commit 190c0a3b8e44 in android-security-16.0.0_r8. Same diff as the 15 commit (same blob indexes). Resolver: 6/8 lines present.",
      "17": "same fix: commit 1fab99e951d5 in android-security-17.0.0_r1 (cherry-pick of internal 23db352dbb9c). It sits on top of the restricted-RCS/otpFilter code, and the tests carry @DisableFlags(FLAG_SECURE_ACCESS_TO_RESTRICTED_RCS_MESSAGES). Resolver: 7/8 lines present."
     },
     "backport_notes": [
      "The material shows one commit per branch, linked by bug_id. The commit relies on selectionBySubIds already being computed in query(), and the tests rely on the getSubscriptionInfoListAssociatedWithUser mock in setUp. On branches without the otpFilter/canReadRestrictedMessages code, use the 14-16 variant of getTextSearchQuery and drop the @DisableFlags annotations."
     ],
     "confidence": "high",
     "unknowns": [
      "How selectionBySubIds is built is not in the material: the exact SQL text, whether it can be an empty string, and how rows with an invalid or default sub_id are treated.",
      "Whether the words table, and the table queried by getConversationById, have a sub_id column that the unprefixed clause can match.",
      "Whether the query() branches not touched by the diff already apply the subscription filter.",
      "The provider-level permission/AppOps checks on these URIs, and so the exact caller privilege required.",
      "Whether any other commit is needed together with this fix on any branch."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49881": {
     "cve": "CVE-2026-49881",
     "summary": "Removes InCallController.serviceClassExists and the 'android.telecom.CLASS_EXISTENCE_CHECK' metadata check from Telecom. That code loaded a third-party package's code inside Telecom during InCallService enumeration (CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY, then getClassLoader and Class.forName). The fix also drops the now-unused metadata from ConnectivityCallListenerService in the framework manifest.",
     "component": "platform/packages/services/Telecomm (InCallController) and platform/frameworks/base (core/res/AndroidManifest.xml), Telecom InCallService binding",
     "weakness": "Untrusted code loading in a privileged process: an arbitrary app's package code was loaded via CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY, triggered by manifest metadata the app controls.",
     "root_cause": "InCallController.getInCallServiceComponents() enumerated InCallService components with queryIntentServices(GET_META_DATA | MATCH_DISABLED_COMPONENTS). For any entry whose ServiceInfo.metaData had 'android.telecom.CLASS_EXISTENCE_CHECK' = true, it called serviceClassExists(). That method called mContext.createPackageContextAsUser(serviceInfo.packageName, CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY, userHandle), then packageContext.getClassLoader() and Class.forName(serviceInfo.name, false, classLoader). The app that declared the InCallService controlled both the metadata flag and the package, so Telecom loaded that app's code into its own process. Per the commit message, this allowed arbitrary code execution in system_server through a custom AppComponentFactory. In the diff, the check is the first statement inside the 'serviceInfo != null' block of the loop.",
     "fix_analysis": [
      "packages/services/Telecomm 668eb07260ec, src/com/android/server/telecom/InCallController.java: deletes the private method serviceClassExists(ServiceInfo, UserHandle) entirely. This removes the createPackageContextAsUser(..., CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY, ...), getClassLoader(), Class.forName() sequence.",
      "Same commit, InCallController.getInCallServiceComponents(): removes the isMetaFlag computation that read 'android.telecom.CLASS_EXISTENCE_CHECK' from serviceInfo.metaData, and the 'if (isMetaFlag && !serviceClassExists(...)) continue;' early skip. The loop now goes straight to the existing METADATA_INCLUDE_EXTERNAL_CALLS handling. Per the commit message, Telecom now relies on standard binding mechanisms, which do not execute third-party code during component resolution.",
      "Same commit, tests/src/com/android/server/telecom/tests/InCallControllerTests.java: removes testBindToServices_classCheckFlagOn_classFound, testBindToServices_classCheckFlagOn_classNotFound, the getOptionalResolveinfo helper, and the imports that are no longer used.",
      "frameworks/base cbb9f1f1ab9c, core/res/AndroidManifest.xml: removes the <meta-data android:name=\"android.telecom.CLASS_EXISTENCE_CHECK\"> element and its comment from the ConnectivityCallListenerService declaration. This is the only declarer of the metadata shown in the material. The change is cleanup: once the Telecomm change is applied, the metadata is ignored."
     ],
     "completeness": [
      "The dangerous code path is removed outright rather than gated. No branch remains in InCallController that creates a package context with CONTEXT_INCLUDE_CODE for an arbitrary InCallService package.",
      "The material shows only this one use of createPackageContextAsUser with CONTEXT_INCLUDE_CODE in Telecomm. It does not show whether other Telecom code paths load app code.",
      "The framework manifest change only removes the metadata from ConnectivityCallListenerService. The original reason for the check was builds where that class is absent, and these diffs do not replace the check with any other handling for that case."
     ],
     "behavior_changes": [
      "Telecom no longer checks that an InCallService class exists before binding. A declared service whose class is missing is no longer skipped during enumeration.",
      "The 'android.telecom.CLASS_EXISTENCE_CHECK' metadata no longer has any effect.",
      "The log lines 'serviceClassExists check' and 'Skipping InCallService: class not found' are no longer emitted.",
      "ConnectivityCallListenerService in the framework manifest no longer declares the metadata, so the existence-check skip no longer applies to it."
     ],
     "preconditions": [
      "An installed app for the target user declares a service with the android.telecom.InCallService intent-filter and <meta-data android:name=\"android.telecom.CLASS_EXISTENCE_CHECK\" android:value=\"true\">. The query uses MATCH_DISABLED_COMPONENTS, so disabled components are also returned.",
      "InCallController.getInCallServiceComponents() must enumerate that package, meaning it is called with a null packageName/componentName or with one that matches the app.",
      "The material does not show which Telecom call flows invoke getInCallServiceComponents with an unrestricted package filter."
     ],
     "per_major": {
      "14": "Not affected per the bulletin. At android-security-14.0.0_r29, 4/8 lines of the patched region are present, the same count as the fixed 17 tag. This is consistent with the vulnerable check being absent (code absent).",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17, 4/8 lines are present, consistent with the vulnerable check being absent (code absent).",
      "16": "Not affected per the bulletin. At android-security-16.0.0_r8, 4/8 lines are present, consistent with the vulnerable check being absent (code absent).",
      "17": "Same fix: Telecomm 668eb07260ec and frameworks/base cbb9f1f1ab9c are in android-security-17.0.0_r1, linked by bug 501155244. The code at that tag shows the removed lines gone and the context lines retained."
     },
     "backport_notes": [
      "The fix spans two repositories. The security-relevant change is the Telecomm removal of serviceClassExists and the metadata check. The frameworks/base manifest change is cleanup and can be applied after it. The fix is only needed on trees that carry the CLASS_EXISTENCE_CHECK feature: Android 17 per the material, while 14 to 16 are not affected per the bulletin."
     ],
     "confidence": "high",
     "unknowns": [
      "Which Telecom flows call getInCallServiceComponents without a package/component restriction, and therefore the exact trigger conditions.",
      "Whether the ignoreDisabled filtering happens before or after the removed check.",
      "How Telecom now handles a bound InCallService whose class is missing (bind-failure path).",
      "The commit message says system_server. The material does not show which process Telecom runs in on this build.",
      "The commit that introduced serviceClassExists / CLASS_EXISTENCE_CHECK, and whether any other branch or vendor fork carries it."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49887": {
     "cve": "CVE-2026-49887",
     "summary": "Fixes an inverted boolean in PackageInstaller v2's InstallRepository.maybeRemoveInvalidInstallerPackageName so that a mismatching EXTRA_INSTALLER_PACKAGE_NAME is stripped when the caller lacks INSTALL_PACKAGES, rather than when it holds it.",
     "component": "platform/frameworks/base, packages/PackageInstaller (v2 install flow, InstallRepository)",
     "weakness": "Inverted permission check (logic error leading to a missing authorization check)",
     "root_cause": "In packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt, maybeRemoveInvalidInstallerPackageName() is meant to drop a caller-supplied Intent.EXTRA_INSTALLER_PACKAGE_NAME that differs from the calling package unless the caller holds android.permission.INSTALL_PACKAGES. The condition was written as `&& isPermissionGranted(packageManager, INSTALL_PACKAGES, callerInfo.packageName)`, the opposite of the intended `!isPermissionGranted(...)`. As a result, a caller without INSTALL_PACKAGES kept its arbitrary installer package name in the intent (the extra was never removed), while callers that did hold the permission had their installer name removed. The commit message states the inversion was introduced while porting the V1 PackageInstaller logic to V2.",
     "fix_analysis": [
      "InstallRepository.kt:maybeRemoveInvalidInstallerPackageName: the third conjunct of the if-condition is changed from `isPermissionGranted(packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName)` to `!isPermissionGranted(...)`. With the fix, the extra is handled only when three things hold: the intent's EXTRA_INSTALLER_PACKAGE_NAME differs from callerInfo.packageName, callerInfo.packageName is non-null, and the caller does NOT hold INSTALL_PACKAGES. In that case the code logs an error, writes EventLog tag 0x534e4554 with id 236687884 and the caller uid, and calls intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME).",
      "No other logic is changed. This is a one-line, one-character fix."
     ],
     "completeness": [
      "The fix restores the intended check at the single site in InstallRepository.maybeRemoveInvalidInstallerPackageName. The rest of the condition (name mismatch and non-null caller package) is unchanged.",
      "The check only runs when callerInfo.packageName is non-null. If the caller package cannot be determined, the extra is left in place by both the old and the new code, so this fix does not change that case.",
      "The material does not show the V1 PackageInstaller counterpart of this check, so it cannot confirm whether V1 is correct. The commit message implies V1 had the correct form."
     ],
     "behavior_changes": [
      "Callers without INSTALL_PACKAGES that supply an EXTRA_INSTALLER_PACKAGE_NAME different from their own package now have that extra removed, with a Log.e and an EventLog 0x534e4554/236687884 entry.",
      "Callers that hold INSTALL_PACKAGES and supply a different installer package name now keep that extra. Before the fix it was stripped."
     ],
     "preconditions": [
      "The device uses the PackageInstaller v2 flow (InstallRepository.kt under packages/PackageInstaller/.../v2/).",
      "An app invokes the PackageInstaller install intent with Intent.EXTRA_INSTALLER_PACKAGE_NAME set to a value different from its own package name, and the calling package can be identified (callerInfo.packageName non-null).",
      "Before the fix, the extra was kept exactly when the caller did not hold android.permission.INSTALL_PACKAGES, so no special permission was needed to reach the flawed branch."
     ],
     "per_major": {
      "14": "not affected: not listed by the bulletin; packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt is absent at android-security-14.0.0_r29.",
      "15": "not affected per the bulletin; at android-security-15.0.0_r17 the resolver reports 4/4 lines of the patched region present. The material does not show whether the condition there is negated.",
      "16": "not affected per the bulletin for base 16 (only 16-qpr2 is listed); at android-security-16.0.0_r8 the resolver reports 4/4 lines of the patched region present. The material does not show whether the condition there is negated.",
      "17": "same fix: commit 110da4cfe2a8 (cherry-pick of 9dbc12bb1ebb, Change-Id I6c336109c5422170d993adba901d5a025217baa1) in android-security-17.0.0_r1, resolved by bug_id. The resolver reports 3/4 lines of the patched region present at that tag, and the code context shows `!isPermissionGranted` at line 357."
     },
     "backport_notes": [
      "Single one-line change with no dependencies. On any branch that has the v2 InstallRepository.kt, check the sign of the isPermissionGranted(...) conjunct in maybeRemoveInvalidInstallerPackageName and add the negation if it is missing. Branches without that file (e.g. 14 at 14.0.0_r29) have nothing to patch for this CVE."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not explain why the bulletin lists 16-qpr2 as affected while the 15 and 16 security tags contain the patched region. It does not include the 16-qpr2 state of the file or the commit that introduced the inversion.",
      "Whether the condition at 15.0.0_r17 and 16.0.0_r8 carries the negation is not shown; the line-presence metric does not distinguish the two forms.",
      "The material does not explain why the Android 17 resolver reports only 3/4 lines of the patched region present when the code context contains the fixed condition.",
      "The material does not show the downstream consequence of a retained spoofed EXTRA_INSTALLER_PACKAGE_NAME, that is, how later code consumes the extra."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49895": {
     "cve": "CVE-2026-49895",
     "summary": "Raises the EHT Operation element minimum-length constant in src/common/ieee802_11_defs.h from 1 to 5. The constant is EHT_OPERATION_ELEM_MIN_LEN on Android 17 and EHT_OPERATION_IE_MIN_LEN on Android 14/15/16. With the new value, the length check that guards access to the EHT Operation Information field counts the 1-byte parameters field plus the 4-byte basic_eht_mcs_nss_set field.",
     "component": "platform/external/wpa_supplicant_8, IEEE 802.11be (EHT) element parsing in src/common",
     "weakness": "Incorrect bounds check constant leading to heap out-of-bounds read (missing/insufficient length validation)",
     "root_cause": "Per the commit message, EHT_OPERATION_ELEM_MIN_LEN was defined as 1, a value that predates the addition of basic_eht_mcs_nss_set[4] to struct ieee80211_eht_operation. The bounds check in get_eht_operation_channel_width() relies on this constant. When the EHT_OPER_INFO_PRESENT bit is set, the function reads the operation information field, which starts at offset 5 of the element. So for an element 1 to 4 bytes long, the check passed but the read went past the end of the element data, causing a heap out-of-bounds read.",
     "fix_analysis": [
      "src/common/ieee802_11_defs.h: the single definition `#define EHT_OPERATION_ELEM_MIN_LEN 1` is changed to `5`. This requires the fixed-size prefix (1-byte EHT Operation Parameters + 4-byte Basic EHT-MCS And Nss Set) to be present before the operation information at offset 5 is accessed.",
      "No code in get_eht_operation_channel_width() is changed. The fix works entirely through the existing bounds check that uses the macro. The commit touches only this one file (1 insertion, 1 deletion)."
     ],
     "completeness": [
      "The fix covers every length check that uses the EHT_OPERATION_ELEM_MIN_LEN macro. The material only names get_eht_operation_channel_width() as the affected consumer.",
      "The fix adds no check inside get_eht_operation_channel_width() itself. It only works if that function, and any other parser of the EHT Operation element, compares the element length against this macro before reading the operation information. The material shows neither the function body nor other users of the macro, so this cannot be confirmed from the diff.",
      "The separate EHT_OPER_INFO_MIN_LEN (3) constant for the operation information field is unchanged. The material does not show whether the later oper_info/disabled_chan_bitmap reads have their own length checks."
     ],
     "behavior_changes": [
      "EHT Operation elements of 1 to 4 octets are now rejected by any check that uses the minimum-length macro. Before the fix they were accepted. This is stricter validation of malformed elements that are shorter than the standard's fixed fields.",
      "On Android 14/15/16 the comment next to the macro changes from the P802.11be/D2.3 draft reference to IEEE Std 802.11be-2024, 9.4.2.321. This has no functional effect."
     ],
     "preconditions": [
      "wpa_supplicant must parse an EHT Operation element that reaches get_eht_operation_channel_width() (per commit message).",
      "The element must be 1 to 4 bytes long and have the EHT_OPER_INFO_PRESENT bit (BIT(0)) set in the parameters byte (per commit message).",
      "The material does not show which frames or code paths deliver the element to this function."
     ],
     "per_major": {
      "14": "adapted fix: commit 0ce102474997 in android-security-14.0.0_r29 changes EHT_OPERATION_IE_MIN_LEN (older macro name) from 1 to 5 in src/common/ieee802_11_defs.h and updates the comment. The functional change is the same.",
      "15": "adapted fix: commit 234d284145fc in android-security-15.0.0_r17 changes EHT_OPERATION_IE_MIN_LEN from 1 to 5 and updates the comment. The functional change is the same.",
      "16": "adapted fix: commit 6579923889d6 in android-security-16.0.0_r8 changes EHT_OPERATION_IE_MIN_LEN from 1 to 5 and updates the comment. The functional change is the same.",
      "17": "same fix: commit 14398e6a6663 in android-security-17.0.0_r1 changes EHT_OPERATION_ELEM_MIN_LEN from 1 to 5."
     },
     "backport_notes": [
      "This is a one-line constant change. On Android 14/15/16 the macro is named EHT_OPERATION_IE_MIN_LEN instead of EHT_OPERATION_ELEM_MIN_LEN, so change the value under whichever name the target tree uses.",
      "Before backporting, confirm that the target tree's EHT Operation parser (get_eht_operation_channel_width() or its equivalent) really gates oper_info access on this macro. If a fork uses a hardcoded length check instead, changing the constant will not fix it."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the body of get_eht_operation_channel_width() or the exact comparison it makes against the macro.",
      "The material does not show other users of EHT_OPERATION_ELEM_MIN_LEN / EHT_OPERATION_IE_MIN_LEN, or whether any other parser reads struct ieee80211_eht_operation without a length check.",
      "The material does not show which frame types and call paths bring the element to the parser."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-49913": {
     "cve": "CVE-2026-49913",
     "summary": "In the default AIDL CAS HAL, DescramblerImpl::descramble now compares the mmap() result against MAP_FAILED instead of NULL, and unmaps the source buffer through an android::base ScopeGuard on every exit path.",
     "component": "platform/hardware/interfaces, cas/aidl/default (android.hardware.cas default AIDL Descrambler HAL implementation)",
     "weakness": "Incorrect check of the mmap() return value (NULL instead of MAP_FAILED), which leads to use of an invalid pointer. There was also a missing munmap() (memory leak).",
     "root_cause": "In cas/aidl/default/DescramblerImpl.cpp, DescramblerImpl::descramble maps the source buffer into srcPtr and then tested `if (srcPtr == NULL)` to detect failure. mmap() reports failure with MAP_FAILED, not NULL. So when mapping failed, the check did not trigger and the function continued with srcPtr == MAP_FAILED. Per the commit message, this led to an arbitrary-address memory access. Also per the commit message, the mapping was not cleaned up with munmap(), which leaked memory.",
     "fix_analysis": [
      "cas/aidl/default/DescramblerImpl.cpp:DescramblerImpl::descramble: the failure test changes from `srcPtr == NULL` to `srcPtr == MAP_FAILED`. On failure the function still logs \"Failed to map src buffer.\" and returns toStatus(BAD_VALUE), but this path is now actually taken when mmap() fails.",
      "cas/aidl/default/DescramblerImpl.cpp:DescramblerImpl::descramble: right after the failure check, `::android::base::make_scope_guard` is created. It captures srcPtr and srcBuffer.heapBase.size, so munmap(srcPtr, size) runs on every later return, including the validateRangeForSize failure return. The guard is created only after the MAP_FAILED check, so munmap is not called on MAP_FAILED.",
      "cas/aidl/default/DescramblerImpl.cpp: adds `#include <android-base/scopeguard.h>`. <sys/mman.h> was already included."
     ],
     "completeness": [
      "The fix covers the mmap() of the source buffer in DescramblerImpl::descramble. The following validateRangeForSize(srcBuffer.offset, srcBuffer.size, srcBuffer.heapBase.size) check is unchanged.",
      "The munmap size comes from srcBuffer.heapBase.size. The mmap call itself is outside the shown diff, so the material does not show whether munmap uses the same size that was mapped, or whether the size and fd are validated before mapping.",
      "The material does not show the rest of this file or the HIDL CAS default implementation, so it is unknown whether sibling code has the same NULL-vs-MAP_FAILED pattern."
     ],
     "behavior_changes": [
      "descramble() now returns BAD_VALUE (with an ALOGE) when mmap() of the source buffer fails. Previously it kept executing.",
      "The source mapping is now unmapped when descramble() returns after a successful map, which addresses the memory leak named in the commit message."
     ],
     "preconditions": [
      "A client must be able to call descramble on the default AIDL CAS HAL with a source buffer that mmap() fails to map, so that mmap() returns MAP_FAILED.",
      "Possible callers per index search: MediaDescrambler.descramble calls mIDescrambler.descramble, and HidlDescrambler.descramble calls native_descramble (frameworks/base/media/jni/android_media_MediaDescrambler.cpp). ATSParser::Stream::flushScrambled in frameworks/av also uses a descrambler. These index results are HIDL-oriented, so the exact AIDL client path is not confirmed."
     ],
     "per_major": {
      "14": "same fix: platform/hardware/interfaces 8858a5521ba4 in android-security-14.0.0_r29. Content is identical to 17. The hunk is at @@ -100 (one line earlier than on 17), and the file mode is 100755.",
      "15": "same fix: platform/hardware/interfaces 9bdc5c54c702 in android-security-15.0.0_r17. Content is identical to 17, and the hunk is at @@ -100.",
      "16": "same fix: platform/hardware/interfaces 0b6f21543e54 in android-security-16.0.0_r8. Content is identical to 17, and the hunk is at @@ -100.",
      "17": "same fix: platform/hardware/interfaces d9f6b94ba0f0 in android-security-17.0.0_r1, cherry-picked from internal commit 5fc6bab9c9a2 (Change-Id I4805ee90ae1705070b0c43fb9152f666d90963d7)."
     },
     "backport_notes": [
      "This is a single, self-contained commit. Its only new dependency is libbase's android-base/scopeguard.h, so the HAL default implementation must link libbase (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The mmap() call site (its arguments, including the size and fd taken from srcBuffer.heapBase) is not shown.",
      "The code that uses srcPtr after the check is not shown.",
      "Whether descramble() also maps a destination buffer, and how that is checked, is not shown.",
      "Whether the HIDL CAS default implementation contains the same NULL check is not shown.",
      "The resolver reports 7/8 lines of the patched region present on every branch. The material does not explain the missing line.",
      "Which process or SELinux domain hosts the default AIDL CAS HAL is not shown, beyond the bulletin's High/EoP rating."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55290": {
     "cve": "CVE-2026-55290",
     "summary": "Adds early validation of stringsStart and stylesStart against the chunk size in ResStringPool::setTo and replaces the style-offset-array checks so that styleCount * sizeof(uint32_t) is verified (overflow-safe) to fit both within the data size and before stringsStart, closing an OOB heap read reachable via ResStringPool::styleAt when stringCount is zero.",
     "component": "platform/frameworks/base, libs/androidfw (ResourceTypes.cpp, ResStringPool parsing)",
     "weakness": "Missing/insufficient bounds check on attacker-controlled header fields (out-of-bounds heap read)",
     "root_cause": "In libs/androidfw/ResourceTypes.cpp ResStringPool::setTo, stringsStart validation was skipped when stringCount was zero (per commit message). In that case a pool with a large styleCount and a huge stringsStart passed the style-count checks. The first check only verified that the start of the style offset array (mEntryStyles) was within size, not that styleCount entries fit. The second check compared styleCount * 4 against (stringsStart - styleOffsetsStart). An oversized stringsStart satisfies that check trivially, and the subtraction could underflow when styleOffsetsStart > stringsStart. mEntryStyles was therefore set up with an unvalidated extent, and later indexing in ResStringPool::styleAt could read past the chunk (per commit message).",
     "fix_analysis": [
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, right after mSize = mHeader->header.size: new unconditional checks reject the pool with BAD_TYPE if mHeader->stringsStart > mSize or mHeader->stylesStart > mSize, so these fields are validated regardless of stringCount.",
      "libs/androidfw/ResourceTypes.cpp:ResStringPool::setTo, inside the styleCount > 0 block: styleOffsetsStart is computed up front. styleCount is checked against SIZE_MAX / sizeof(uint32_t) before computing styleOffsetsSize = styleCount * sizeof(uint32_t). The old 'start within size' check is replaced by styleOffsetsSize > size || styleOffsetsStart > (size - styleOffsetsSize), which requires the whole offset array to fit in the buffer without arithmetic overflow.",
      "Same block: the old subtraction check is replaced by styleOffsetsStart > mHeader->stringsStart || styleOffsetsSize > (mHeader->stringsStart - styleOffsetsStart). This guards the unsigned subtraction and ensures the offset array does not overlap string data. The two older checks (the numeric_limits<decltype(styleCount)> overflow check and the unguarded subtraction) are removed as superseded.",
      "libs/androidfw/tests/ResourceTypes_test.cpp: adds ResStringPool_StyleCountUnboundedWhenStringCountIsZero (stringCount 0, styleCount 100, stringsStart 0xFFFFFFFF) and ResStringPool_StyleCountCalculationOverflow (styleCount 0x40000000). Both expect BAD_TYPE."
     ],
     "completeness": [
      "Covers the extent of the style offset array (mEntryStyles): it is bounded by the passed size and by stringsStart, with overflow-safe arithmetic, independent of stringCount.",
      "stringsStart and stylesStart are now bounded by header.size for all pools, including those with stringCount == 0 and styleCount == 0.",
      "The pre-existing check 'mHeader->stylesStart >= mHeader->header.size' is left in place. It is now mostly redundant with the new early check, except that it also rejects stylesStart == size when styleCount > 0.",
      "The fix is confined to setTo. ResStringPool::styleAt itself is not modified, and the material does not show how styleAt bounds-checks the per-style span data against mStylePoolSize. Later style-data reads therefore still rely on existing code not shown here.",
      "The early checks use '>', so stringsStart == mSize and stylesStart == mSize are accepted by those checks."
     ],
     "behavior_changes": [
      "ResStringPool::setTo now returns BAD_TYPE (with ALOGW) for any pool whose stringsStart or stylesStart exceeds header.size, even when stringCount is zero. Previously such pools could be accepted.",
      "Pools whose style offset array (styleCount * 4 bytes) does not fully fit within the buffer or extends past stringsStart are rejected with BAD_TYPE. styleCount values above SIZE_MAX / 4 are rejected with a new log message.",
      "Log message text for the 'extends past data size' failure changes (it now prints styleCount and size with %zu)."
     ],
     "preconditions": [
      "A crafted ResStringPool chunk is parsed by ResStringPool::setTo. The material does not show the source of that data.",
      "The chunk has stringCount == 0 (so stringsStart was not previously validated), styleCount > 0, and a stringsStart value large enough to defeat the old subtraction check. The later OOB read requires a subsequent call into ResStringPool::styleAt (per commit message)."
     ],
     "per_major": {
      "14": "same fix: platform/frameworks/base 1eb9fc572c86 in android-security-14.0.0_r29, identical hunks (both ResourceTypes.cpp and ResourceTypes_test.cpp) at shifted line numbers.",
      "15": "same fix: platform/frameworks/base ed4eedc69050 in android-security-15.0.0_r17, identical hunks at shifted line numbers.",
      "16": "same fix: platform/frameworks/base 01bff648b538 in android-security-16.0.0_r8, identical hunks at shifted line numbers.",
      "17": "same fix: platform/frameworks/base 7e42d365a0c2 in android-security-17.0.0_r1 (cherry-pick of 968cb0736ca6)."
     },
     "backport_notes": [
      "Single self-contained commit. The published hunks are identical apart from line offsets on Android 14 through 17. When merging into customized code, make sure the two old checks (the numeric_limits overflow check and 'styleCount * sizeof(uint32_t) > (stringsStart - styleOffsetsStart)') are removed, not duplicated."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of ResStringPool::styleAt and how it indexes mEntryStyles/mStyles is not shown, so the exact location of the OOB read is inferred from the commit message only.",
      "The code of the stringCount > 0 branch (where stringsStart was previously validated) is not shown in the material.",
      "The material does not show which callers or parsers (resources.arsc, binary XML, etc.) feed untrusted data into setTo.",
      "The per-major facts report 5/8 lines of the patched region present in each latest tag. The material does not explain this."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-55294": {
     "cve": "CVE-2026-55294",
     "summary": "Changes ihevcd_get_tu_data_size() in libhevc's decoder/ihevcd_utils.c to size the tu_map region with num_tu (luma plus chroma TUs) instead of num_luma_tu, so the TU data buffer has room for the tu_map entries that initialization writes for both luma and chroma.",
     "component": "platform/external/libhevc, HEVC decoder (libhevcdec), TU data buffer sizing in decoder/ihevcd_utils.c",
     "weakness": "Incorrect buffer size calculation (undersized allocation) leading to heap buffer overflow",
     "root_cause": "ihevcd_get_tu_data_size() computes the size of a combined TU data buffer. It derives num_luma_tu from num_luma_samples, sets num_chroma_tu = num_luma_tu >> 1, and sets num_tu = num_luma_tu + num_chroma_tu. The tu_t array, the coded-subblock/scan_idx bytes and the coeff data were all sized with num_tu. The tu_map region was the exception: it was sized with num_luma_tu * sizeof(UWORD8). According to the commit message, the initialization logic consumes tu_map space for both luma and chroma TUs, so the computed size was num_chroma_tu bytes too small. The commit describes the result as a heap buffer overflow.",
     "fix_analysis": [
      "decoder/ihevcd_utils.c:ihevcd_get_tu_data_size(): the tu map line `tu_data_size += num_luma_tu * sizeof(UWORD8);` becomes `tu_data_size += num_tu * sizeof(UWORD8);`. The tu_map budget now matches the other per-TU sub-regions, which already use num_tu (luma + chroma).",
      "The consumer/initialization side is not changed. The fix makes the allocation larger instead of limiting the writes.",
      "The diff also changes the file mode from 100755 to 100644. This is incidental and has no functional effect."
     ],
     "completeness": [
      "Covers the only sub-region of the TU data size computation that used num_luma_tu. After the fix, all five sub-regions (CTB start indices, tu_map, tu_t, subblock/scan_idx, coeff data) are sized with num_ctb or num_tu.",
      "Adds no bounds check where tu_map is written. Correctness now depends on the writer using at most num_tu tu_map entries. The commit message implies this, but the material does not show the code.",
      "Single-file, single-line fix. No other callers or allocation paths are changed in the material."
     ],
     "behavior_changes": [
      "For a given num_luma_samples, the size returned by ihevcd_get_tu_data_size() grows by num_chroma_tu bytes (num_luma_tu >> 1), so decoder memory use goes up slightly.",
      "No new error codes, validation, or changes to decode behavior."
     ],
     "preconditions": [
      "An HEVC bitstream is decoded by libhevcdec, so that the TU data buffer sized by ihevcd_get_tu_data_size() is allocated and then filled by the TU map initialization logic.",
      "Per index search, libhevc is used by the software Codec2 HEVC decoder C2SoftHevcDec (frameworks/av/media/codec2/components/hevc/C2SoftHevcDec.cpp) and by the emulator decoder GoldfishHevcHelper (device/generic/goldfish/codecs/c2/decoders/hevcdec/GoldfishHevcHelper.cpp).",
      "The material does not show which bitstream properties make tu_map writes go beyond num_luma_tu entries."
     ],
     "per_major": {
      "14": "same fix: platform/external/libhevc d9977ef8294a in android-security-14.0.0_r29, the same one-line change in the same hunk of decoder/ihevcd_utils.c.",
      "15": "same fix: platform/external/libhevc c43bbf882f97 in android-security-15.0.0_r17, the same one-line change.",
      "16": "same fix: platform/external/libhevc 5492b022e4d6 in android-security-16.0.0_r8, the same one-line change (same pre/post blob ids as Android 15).",
      "17": "same fix: platform/external/libhevc 9aa38f2733f4 in android-security-17.0.0_r1, the same one-line change."
     },
     "backport_notes": [
      "Self-contained one-line change with no dependencies. It applies wherever ihevcd_get_tu_data_size() still sizes the tu map with num_luma_tu. After applying, check that the line reads `num_tu * sizeof(UWORD8)`."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not include the tu_map initialization code that writes luma and chroma entries, so the exact overflow write site and how far it can exceed the old size are not shown.",
      "The material does not show the layout of sub-regions inside the TU data buffer, so it is unclear whether the overflow first reaches adjacent sub-regions or goes past the end of the allocation.",
      "The callers of ihevcd_get_tu_data_size() and the num_luma_samples values they pass are not shown.",
      "The specific bitstream trigger used by the PoC referenced in the bug is not shown."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 1,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2026-58822": {
     "cve": "CVE-2026-58822",
     "summary": "Adds size guards to FreeType's LCD rasterizers (ft_smooth_raster_lcd and ft_smooth_raster_lcdv in src/smooth/ftsmooth.c). They return Raster_Overflow when bitmap width/3 or rows/3 exceeds 0x7FFF, to address a 16-bit span x-coordinate truncation that the commit says leads to a heap underflow write.",
     "component": "platform/external/freetype, smooth (anti-aliased) rasterizer, LCD rendering paths (src/smooth/ftsmooth.c)",
     "weakness": "Missing bounds check leading to integer truncation and out-of-bounds (heap underflow) write",
     "root_cause": "ft_smooth_raster_lcd renders in direct mode with params.clip_box.xMax = bitmap->width. Its span callback, ft_smooth_lcd_spans, writes at dst = dst_line + spans->x * 3. Per the commit, FT_Span.x is 16-bit. For a wide enough target bitmap, a short truncation of the span x-coordinate makes dst point before the bitmap row, so the write *dst = spans->coverage lands before the heap buffer. Before the fix, nothing checked bitmap->width (or bitmap->rows in ft_smooth_raster_lcdv) before calling the raster. The commit message says ft_smooth_raster_overlap already has a similar guard.",
     "fix_analysis": [
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcd: before setting up FT_Raster_Params, returns FT_THROW( Raster_Overflow ) if bitmap->width / 3 > 0x7FFF. The aim is to keep span x values within the 16-bit FT_Span.x range used by ft_smooth_lcd_spans.",
      "src/smooth/ftsmooth.c:ft_smooth_raster_lcdv: before setting params.target/source/flags, returns FT_THROW( Raster_Overflow ) if bitmap->rows / 3 > 0x7FFF, rejecting outlines that are too high.",
      "Both checks only reject early. The span callback and the raster itself are unchanged. Single commit, 8 added lines."
     ],
     "completeness": [
      "Covers both LCD paths in ftsmooth.c: ft_smooth_raster_lcd (width guard) and ft_smooth_raster_lcdv (rows guard).",
      "The span writer ft_smooth_lcd_spans is unchanged and still has no per-span bounds check. Safety depends entirely on the new pre-check.",
      "ft_smooth_raster_lcd sets clip_box.xMax = bitmap->width, but the guard tests bitmap->width / 3. The material does not show the raster or ft_smooth_raster_overlap, so it cannot confirm that the /3 threshold matches the range of x values the raster can emit. The commit says the guard mirrors the existing overlap guard.",
      "The diff does not touch the plain gray (non-LCD) render path or ft_smooth_raster_overlap. The commit message states overlap already had a guard."
     ],
     "behavior_changes": [
      "LCD rendering into a bitmap with width/3 > 0x7FFF, or the lcdv path with rows/3 > 0x7FFF, now returns a Raster_Overflow error instead of rendering.",
      "No change for bitmaps within these limits."
     ],
     "preconditions": [
      "A glyph outline is rendered through the smooth renderer's LCD path (ft_smooth_raster_lcd) or LCD_V path (ft_smooth_raster_lcdv).",
      "The target bitmap is large enough that span coordinates go beyond the 16-bit FT_Span range.",
      "Per index search, Android 17 uses FreeType from frameworks/rs/rsFont.cpp (FT_New_Memory_Face/FT_New_Face in Font::init, FT_Init_FreeType in FontState::getLib) and from frameworks/minikin test code. The material does not show whether these callers request LCD render modes."
     ],
     "per_major": {
      "14": "same fix: platform/external/freetype 51c2994abfc5 in android-security-14.0.0_r29. Same 8 added lines, with hunks at lines 137/204 because of a small file offset. 8/8 lines present.",
      "15": "same fix: platform/external/freetype fc6e207660f9 in android-security-15.0.0_r17, identical diff. 8/8 lines present.",
      "16": "same fix: platform/external/freetype 6ca3925a3c97 in android-security-16.0.0_r8, identical diff. 8/8 lines present.",
      "17": "same fix: platform/external/freetype 4321c90fc79e in android-security-17.0.0_r1. 8/8 lines present."
     },
     "backport_notes": [
      "Single commit with no dependencies shown. The two guards use only bitmap->width, bitmap->rows and FT_THROW( Raster_Overflow ). Google applied the same 8 lines to 14 through 17, with only line offsets differing."
     ],
     "confidence": "medium",
     "unknowns": [
      "The existing guard in ft_smooth_raster_overlap that the commit refers to is not shown, so the threshold choice (width/3 against clip_box.xMax = bitmap->width) cannot be verified from the material.",
      "The rest of ft_smooth_raster_lcdv beyond the hunk, and how rows map to span coordinates on that path, is not shown.",
      "The material does not show which Android callers render in LCD/LCD_V mode, or how a large enough bitmap could be reached.",
      "The bulletin lists 16-qpr2 and 17-next as affected, but the material has no per-branch facts for them."
     ],
     "meta": {
      "generated": "2026-10-02",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  }
 },
 "bulletin": {
  "url": "https://source.android.com/docs/security/bulletin/2026/2026-09-01",
  "latest_version": "1.3",
  "latest_revision": {
   "version": "1.3",
   "date": "September 17, 2026",
   "notes": "Updated issue list"
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "September 8, 2026",
    "notes": "Bulletin Published"
   },
   {
    "version": "1.1",
    "date": "September 15, 2026",
    "notes": "Updated issue list and added AOSP links"
   },
   {
    "version": "1.2",
    "date": "September 16, 2026",
    "notes": "Updated issue list"
   },
   {
    "version": "1.3",
    "date": "September 17, 2026",
    "notes": "Updated issue list"
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [
   {
    "seen": "2026-10-13",
    "kind": "removed",
    "cve": "CVE-2026-28666",
    "bug_ids": [
     "501447778"
    ],
    "bulletin_version": "1.3"
   }
  ],
  "out_of_scope_rows": {
   "Arm components": 6,
   "Imagination Technologies": 25,
   "Kernel": 5,
   "Kernel components": 1,
   "MediaTek components": 21,
   "Qualcomm closed-source components": 11,
   "Qualcomm components": 5,
   "Unisoc components": 7
  },
  "note": "changes are only visible from `tracked_since`; earlier revisions of the bulletin cannot be reconstructed. Out-of-scope rows (vendor closed-source, kernel) have no public AOSP code to resolve."
 }
}