{
 "month": "2024-04",
 "generated": "2026-10-06T12:05+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 8,
    "certain": 8,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2023-21267",
     "osv_id": "ASB-A-218495634",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Makes SystemUI's KeyguardViewMediator show (and refuse to hide) the keyguard when the current user is in lockdown, even if an app has externally disabled the keyguard. It also fixes LockPatternUtils.isUserInLockdown so that it still detects lockdown when other strong-auth flags are set.",
      "confidence": "medium",
      "backport_notes": [
       "Apply in order. If the target branch contains c851ef6d27bf, first apply the revert cecddcd865f7, then cb7e9c7549a2 (its base blob 0a38793cc2b5 is the post-revert state). Then apply e205cd30e91d to LockPatternUtils; without it, the lockdown checks fail whenever lockdown coincides with another strong-auth flag.",
       "Newer code uses mUpdateMonitor.isUserInLockdown (r29 context) and mSelectedUserInteractor.getSelectedUserId() (per index search, android-14.0.0_r75). On older branches, use KeyguardUpdateMonitor.getCurrentUser() and mLockPatternUtils as in the diff, and make sure the isUserInLockdown implementation used performs a bitmask test."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r3",
      "date": "2023-12-04",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "cb7e9c7549a2a076ec00db15e3da0d21b31b0b1c",
       "tag": "android-security-14.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "cecddcd865f72d76f7aacb1cf4479365847299f9",
       "tag": "android-security-14.0.0_r3",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      },
      {
       "project": "platform/frameworks/base",
       "sha": "e205cd30e91d8eeadf562140c34c306ebf7d6394",
       "tag": "android-security-14.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 3,
     "first_commit_before_announcement_days": 119,
     "multi_tag_fix": true,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-0022",
     "osv_id": "ASB-A-298635078",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "CompanionDeviceManagerService.requestNotificationAccess now passes the caller-supplied userId into checkCanCallNotificationApi/enforceCallerIsSystemOr instead of ignoring it. Per the commit message, this makes sure the calling user id matches the passed user id, so notification access can no longer be requested across users.",
      "confidence": "high",
      "backport_notes": [
       "This is a single, self-contained commit. It assumes the target tree has enforceCallerIsSystemOr(int userId, String callingPackage) and the one-argument checkCanCallNotificationApi(String) helper. If checkCanCallNotificationApi has other callers, pass getCallingUserId() where the caller has no userId parameter (as done for hasNotificationAccess), and pass the userId received over binder where there is one."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 6,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r4",
      "date": "2024-01-03",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r4",
      "date": "2024-01-03",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "bdf1cce569c9700965ff6baee8efd3fb1e8269e8",
       "tag": "android-security-14.0.0_r4",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 89,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-0026",
     "osv_id": "ASB-A-308414141",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SnoozeHelper.canSnooze now also counts the two persisted snoozed-notification maps against CONCURRENT_SNOOZE_LIMIT, and repostGroupSummary removes the reposted group summary from those persisted maps so they can no longer grow unbounded and cause a boot loop when serialized.",
      "confidence": "high",
      "backport_notes": [
       "The production change in SnoozeHelper.java adds 7 lines and removes 1 (canSnooze and repostGroupSummary) and depends only on the existing mPersistedSnoozedNotifications, mPersistedSnoozedNotificationsWithContext and getTrimmedString members; verify those names exist in the target branch. The commit message's API-privilege item is not in this diff, so look for a companion NotificationManagerService change when backporting the full bug fix.",
       "The new tests use TypedXmlSerializer/TypedXmlPullParser and Truth assertThat; older branches may need the test adapted if those helpers differ (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r5",
      "date": "2024-02-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r5",
      "date": "2024-02-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a8fb9fe93efdebc4145e00934f42c91742f328de",
       "tag": "android-security-14.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 56,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-0027",
     "osv_id": "ASB-A-307948424",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "SnoozeHelper.canSnooze now also counts the two persisted snoozed-notification maps against CONCURRENT_SNOOZE_LIMIT, and repostGroupSummary removes the group summary key from those persisted maps when the in-memory record is removed, bounding the snoozed-notification state that is serialized to disk.",
      "confidence": "high",
      "backport_notes": [
       "The SnoozeHelper change is self-contained: it needs only the two persisted maps, getTrimmedString, and CONCURRENT_SNOOZE_LIMIT, all of which appear in the Android 14 (android-security-14.0.0_r29) code shown. Check whether the separate NotificationManagerService hardening referenced in the commit message (restricting context/criterion snoozing by unprivileged callers) exists on the target branch; it is not included in this diff.",
       "The new test uses com.google.common.truth (assertThat) and TypedXmlSerializer/TypedXmlPullParser via Xml.newFastSerializer/newFastPullParser; older branches may need test adaptation if those helpers differ (general platform knowledge)."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 3,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r5",
      "date": "2024-02-05",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r5",
      "date": "2024-02-05",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "a8fb9fe93efdebc4145e00934f42c91742f328de",
       "tag": "android-security-14.0.0_r5",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "public_before_announcement_days": 56,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-23704",
     "osv_id": "ASB-A-299931761",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "WifiDialogActivity.onCreate now also finishes immediately when the user has the DISALLOW_ADD_WIFI_CONFIG user restriction, in addition to the existing DISALLOW_CONFIG_WIFI check.",
      "confidence": "high",
      "backport_notes": [
       "Self-contained two-file change. It depends only on the existing isConfigWifiAllowed() gate in onCreate and the UserManager.DISALLOW_ADD_WIFI_CONFIG constant. If a fork's WifiDialogActivity.onCreate differs, add the isAddWifiConfigAllowed() call to the early-exit condition that comes before dialog creation."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 2,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "2e90322bab7de1deaf3c82e207bf4404b92743d7",
       "tag": "android-security-14.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2024-23710",
     "osv_id": "ASB-A-311374917",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In InstallPackageHelper, two shared-user privilege checks (assertPackageWithSharedUserIdIsPrivileged and a second scan-time block guarded by SCAN_AS_PRIVILEGED and skipVendorPrivilegeScan) now also require !pkg.isLeavingSharedUser(), so packages leaving their shared user are excluded from both.",
      "confidence": "medium",
      "backport_notes": [
       "Both hunks only add a predicate. They depend on AndroidPackage exposing isLeavingSharedUser().",
       "The bulletin lists Android 13 as affected, but the material has no Android 13 commit. Locate the equivalent conditions by the `!AndroidPackageUtils.isPrivileged(pkg) && pkg.getSharedUserId() != null` pattern."
      ],
      "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_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "c7c1193ca7554fc3182ca9ab0f711bd118bdeef2",
       "tag": "android-security-14.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-23712",
     "osv_id": "ASB-A-304983146",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "DoS"
     ],
     "status": "resolved",
     "brief": {
      "summary": "AppOpsService now validates the proxy attribution tag on the note and start operation paths (nulling tags not declared in the proxy or target package), and ParsedAttributionImpl.MAX_NUM_ATTRIBUTIONS is reduced from 10000 to 1000.",
      "confidence": "medium",
      "backport_notes": [
       "Single commit. It relies on existing AppOpsService.isAttributionInPackage(AndroidPackage, String) and PackageManagerInternal.getPackage, which the commit does not add. ParsedAttributionImpl's codegen comment references an older core/java/android/content/pm/parsing/component path, so check where the class lives in the target tree.",
       "RESTRICT AUTOMERGE: each branch needs its own cherry-pick. The note hunk anchors before synchronized(this)/getOpsLocked; the start hunk anchors before 'startType = START_TYPE_FAILED'."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 9,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "6beb68ca17d1220f3f09a53cf0a0c541db4ead62",
       "tag": "android-security-14.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-23713",
     "osv_id": "ASB-A-305926929",
     "source": "osv",
     "announced": "2024-04-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "In NotificationManagerService.migrateNotificationFilter, a VersionedPackage is now added to the listener's NotificationListenerFilter only when the resolved uid is not INVALID_UID, so packages whose uid cannot be resolved for a user are no longer stored in the filter.",
      "confidence": "high",
      "backport_notes": [
       "Single small commit (4 lines added, 2 removed) inside migrateNotificationFilter. The diff uses INVALID_UID without adding an import, so the target file must already import it (android.os.Process.INVALID_UID in AOSP, general platform knowledge) or the import must be added."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 4,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r7",
      "date": "2024-04-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "ffd616dc3b919fe7705dbc7a25868483ae45466b",
       "tag": "android-security-14.0.0_r7",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r7"
   ],
   "unannounced": [],
   "coverage": {
    "n_ingested_tags": 30,
    "first_ingested_tag": "android-security-14.0.0_r1",
    "latest_ingested_tag": "android-security-14.0.0_r30",
    "window_base_tag": "android-14.0.0_r1",
    "window_start": "2023-10-04",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {
    "CVE-2023-21267": {
     "cve": "CVE-2023-21267",
     "summary": "Makes SystemUI's KeyguardViewMediator show (and refuse to hide) the keyguard when the current user is in lockdown, even if an app has externally disabled the keyguard. It also fixes LockPatternUtils.isUserInLockdown so that it still detects lockdown when other strong-auth flags are set.",
     "component": "platform/frameworks/base, SystemUI keyguard (KeyguardViewMediator) and core LockPatternUtils",
     "weakness": "Missing state check: external keyguard disablement (mExternallyEnabled == false) was honored unconditionally, overriding user lockdown. A follow-up fixes an incorrect flag comparison (equality instead of bitmask) in isUserInLockdown.",
     "root_cause": "In packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java, doKeyguardLocked() returned early (\"not showing because externally disabled\") whenever mExternallyEnabled was false. setKeyguardEnabled(false) also hid an already showing keyguard. Neither path checked whether the user had triggered lockdown. The mediator also did not react to strong-auth state changes. The commit's manual test (app pinning with \"Ask for PIN before unpinning\" disabled, then Lockdown from the power menu) expects the keyguard to appear and require primary authentication. Separately, core/java/com/android/internal/widget/LockPatternUtils.java isUserInLockdown() compared getStrongAuthForUser(userId) for equality with STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN. It therefore returned false whenever another strong-auth flag (e.g. STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) was also set, which defeated the new lockdown checks in those states.",
     "fix_analysis": [
      "Commit cecddcd865f7 (android-security-14.0.0_r3) reverts the earlier attempt c851ef6d27bf ('On device lockdown, always show the keyguard') because of regression b/300463732. It removes the onStrongAuthStateChanged override in KeyguardViewMediator.java, restores doKeyguardLocked() to 'if (!mExternallyEnabled)', and deletes the test onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally and its captor from KeyguardViewMediatorTest.java.",
      "Commit cb7e9c7549a2 (android-security-14.0.0_r3), KeyguardViewMediator.java, KeyguardUpdateMonitorCallback.onStrongAuthStateChanged(int userId): a new override calls doKeyguardLocked(null) when mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser()) is true.",
      "Same commit, KeyguardViewMediator.java, setKeyguardEnabled(boolean enabled): in the '!enabled && mShowing' branch, if the current user is in lockdown, it logs 'keyguardEnabled(false) overridden by user lockdown' and returns before hiding the keyguard. mExternallyEnabled has already been set to false at that point.",
      "Same commit, KeyguardViewMediator.java, doKeyguardLocked(Bundle options): the early return for an externally disabled keyguard now requires '!mExternallyEnabled && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())'.",
      "Commit e205cd30e91d (android-security-14.0.0_r7), core/java/com/android/internal/widget/LockPatternUtils.java, isUserInLockdown(int userId): changes '== STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN' to '(flags & STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) != 0'. It adds LockPatternUtilsTest.isUserInLockDown, which covers NOT_REQUIRED, LOCKDOWN alone, and LOCKDOWN | LOCKOUT."
     ],
     "completeness": [
      "Covers three sites in KeyguardViewMediator: the show decision in doKeyguardLocked, the hide in setKeyguardEnabled(false) while showing, and a new trigger on strong-auth change (onStrongAuthStateChanged).",
      "cb7e9c7549a2 relies on LockPatternUtils.isUserInLockdown, which used an equality compare. Lockdown combined with another strong-auth flag (e.g. lockout) would therefore not have been detected by these checks until e205cd30e91d (14.0.0_r7).",
      "onStrongAuthStateChanged ignores its userId parameter and checks lockdown for KeyguardUpdateMonitor.getCurrentUser() on every strong-auth change.",
      "The diffstat of cb7e9c7549a2 shows only KeyguardViewMediator.java changed. The regression test removed by the revert is not re-added in that commit, even though the commit message cites atest KeyguardViewMediatorTest. At android-security-14.0.0_r29 the test file declares mKeyguardUpdateMonitorCallbackCaptor again, but the commit that restored it is not in the material.",
      "The material does not show whether other keyguard-suppression paths exist beyond the three sites changed."
     ],
     "behavior_changes": [
      "While the current user is in lockdown, the keyguard is shown even if an app has disabled it (e.g. app pinning without PIN-before-unpinning). Per the commit's test steps, the pinned state persists after a correct credential and the keyguard stays after an incorrect one.",
      "setKeyguardEnabled(false) called while the keyguard is showing and the user is in lockdown no longer hides it. A 'keyguardEnabled(false) overridden by user lockdown' log is emitted.",
      "A strong-auth state change now triggers doKeyguardLocked(null) when the current user is in lockdown.",
      "LockPatternUtils.isUserInLockdown now returns true for any strong-auth value containing STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN. This affects every caller of that method, not only KeyguardViewMediator."
     ],
     "preconditions": [
      "The keyguard has been externally disabled, so mExternallyEnabled is false in KeyguardViewMediator. The commit's manual test reaches this through app pinning with 'Ask for PIN before unpinning' disabled.",
      "The user then triggers Lockdown from the power menu, and isUserInLockdown reports the current user as in lockdown.",
      "For the gap closed by e205cd30e91d: another strong-auth flag (e.g. STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) is set together with the lockdown flag.",
      "Per index search, doKeyguardLocked is also called from doKeyguardLaterLocked and onKeyguardVisibilityChanged (pending PIN lock), in addition to the new onStrongAuthStateChanged callback."
     ],
     "per_major": {
      "14": "Same fix: revert cecddcd865f7 plus updated fix cb7e9c7549a2 in android-security-14.0.0_r3, then the LockPatternUtils bitmask fix e205cd30e91d in android-security-14.0.0_r7. At android-security-14.0.0_r29, 6/8 lines of the patched region are present. The r29 context shows onStrongAuthStateChanged calling mUpdateMonitor.isUserInLockdown(...) instead of mLockPatternUtils.isUserInLockdown(...), and LockPatternUtils.isUserInLockdown in the bitmask form. The material does not identify exactly which lines differ.",
      "15": "Not affected per the bulletin. Patched region present 8/8 at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin. Patched region present 8/8 at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin. Patched region present 8/8 at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Apply in order. If the target branch contains c851ef6d27bf, first apply the revert cecddcd865f7, then cb7e9c7549a2 (its base blob 0a38793cc2b5 is the post-revert state). Then apply e205cd30e91d to LockPatternUtils; without it, the lockdown checks fail whenever lockdown coincides with another strong-auth flag.",
      "Newer code uses mUpdateMonitor.isUserInLockdown (r29 context) and mSelectedUserInteractor.getSelectedUserId() (per index search, android-14.0.0_r75). On older branches, use KeyguardUpdateMonitor.getCurrentUser() and mLockPatternUtils as in the diff, and make sure the isUserInLockdown implementation used performs a bitmask test."
     ],
     "confidence": "medium",
     "unknowns": [
      "The original commit c851ef6d27bf and the nature of regression b/300463732 are not in the material. Only the revert diff shows what c851ef6d27bf contained.",
      "The exact disclosure scenario beyond the commit's manual test and the bulletin's ID rating is not stated.",
      "Whether a KeyguardViewMediatorTest regression test was re-added, and in which commit.",
      "The implementation of KeyguardUpdateMonitor.isUserInLockdown used at android-security-14.0.0_r29 is not shown.",
      "Which lines of the patched region account for the 6/8 match at android-security-14.0.0_r29.",
      "What power-menu Lockdown sets in the strong-auth state, and whether setKeyguardEnabled's skipped block sets mNeedToReshowWhenReenabled."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-0022": {
     "cve": "CVE-2024-0022",
     "summary": "CompanionDeviceManagerService.requestNotificationAccess now passes the caller-supplied userId into checkCanCallNotificationApi/enforceCallerIsSystemOr instead of ignoring it. Per the commit message, this makes sure the calling user id matches the passed user id, so notification access can no longer be requested across users.",
     "component": "platform/frameworks/base, CompanionDeviceManagerService (system_server companion service)",
     "weakness": "Missing authorization check on a caller-controlled user id (cross-user confused deputy); bulletin type ID",
     "root_cause": "The binder method requestNotificationAccess(ComponentName component, int userId) took a userId from the caller. However, its permission check checkCanCallNotificationApi(callingPackage) worked out its own userId from getCallingUserId() and never compared it with the passed one (the code carried a \"TODO: check userId\" comment). The passed userId was still used to build the NotificationAccessConfirmationActivityContract launcher intent and the PendingIntent via getActivityAsUser(..., new UserHandle(userId)), with the calling identity cleared. So the caller checks ran against the calling user while the resulting action targeted the user id the caller supplied.",
     "fix_analysis": [
      "services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java:checkCanCallNotificationApi: the signature changes from (String callingPackage) to (String callingPackage, int userId), and the local `final int userId = getCallingUserId()` is removed. enforceCallerIsSystemOr(userId, callingPackage) now runs on the userId passed in by the API's caller; per the commit message, this enforces that the calling user matches it. The remaining checks (the SYSTEM_UID early return and what follows) are unchanged.",
      "CompanionDeviceManagerService.java:requestNotificationAccess: now calls checkCanCallNotificationApi(callingPackage, userId) with the userId received over binder, and the TODO comment is removed. The PendingIntent construction that follows still uses that userId but is only reached after the check passes.",
      "CompanionDeviceManagerService.java:hasNotificationAccess (deprecated, takes no userId): now calls checkCanCallNotificationApi(component.getPackageName(), getCallingUserId()), which keeps the previous behavior for this path."
     ],
     "completeness": [
      "The fix covers requestNotificationAccess, the only method in the diff that takes a caller-supplied userId for notification access. The userId used for the launcher intent and the PendingIntent is now the one that was checked.",
      "hasNotificationAccess has no userId parameter, so its effect is unchanged (it is still checked against the calling user).",
      "How strong the check is depends on enforceCallerIsSystemOr(userId, callingPackage), whose body is not in the material.",
      "The length check on component.flattenToString() and the rest of the method are untouched. No other userId-taking methods of the service are shown, so the material does not show whether sibling paths are covered."
     ],
     "behavior_changes": [
      "Non-system callers of ICompanionDeviceManager.requestNotificationAccess that pass a userId other than their own calling user should now be rejected by the userId-based check. The exact exception type is not shown in the material.",
      "Callers that pass their own userId should see no change. The public CompanionDeviceManager.requestNotificationAccess wrapper passes mContext.getUserId() (per index search).",
      "hasNotificationAccess behavior is unchanged."
     ],
     "preconditions": [
      "The caller can invoke ICompanionDeviceManager.requestNotificationAccess with a chosen userId. The public SDK wrapper CompanionDeviceManager.requestNotificationAccess always passes mContext.getUserId() (per index search).",
      "The SDK wrapper returns early unless checkFeaturePresent() succeeds (per index search).",
      "The caller must pass whatever other checks remain in checkCanCallNotificationApi after enforceCallerIsSystemOr. These checks are not shown in the material; only the SYSTEM_UID early return is visible."
     ],
     "per_major": {
      "14": "same fix: commit bdf1cce569c9 in android-security-14.0.0_r4 (cherry-pick of internal 12bb4ed9ab46). At android-security-14.0.0_r29, 6/8 lines of the patched region are present, and the code context shows checkCanCallNotificationApi(callingPackage, userId).",
      "15": "not affected per the bulletin; 6/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin; 6/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; 6/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "This is a single, self-contained commit. It assumes the target tree has enforceCallerIsSystemOr(int userId, String callingPackage) and the one-argument checkCanCallNotificationApi(String) helper. If checkCanCallNotificationApi has other callers, pass getCallingUserId() where the caller has no userId parameter (as done for hasNotificationAccess), and pass the userId received over binder where there is one."
     ],
     "confidence": "high",
     "unknowns": [
      "The body of enforceCallerIsSystemOr and the remaining checks in checkCanCallNotificationApi (which exception is thrown, and whether an association is required).",
      "Which Android 13 commit carries the fix (the bulletin lists 13 as affected, but the material only shows the 14 commit).",
      "Why the resolver reports 6/8 rather than 8/8 lines of the patched region in the security tags."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-0026": {
     "cve": "CVE-2024-0026",
     "summary": "SnoozeHelper.canSnooze now also counts the two persisted snoozed-notification maps against CONCURRENT_SNOOZE_LIMIT, and repostGroupSummary removes the reposted group summary from those persisted maps so they can no longer grow unbounded and cause a boot loop when serialized.",
     "component": "platform/frameworks/base, system_server notification service (com.android.server.notification.SnoozeHelper)",
     "weakness": "Missing resource limit on persisted state (uncontrolled resource consumption) combined with incomplete cleanup of persisted entries",
     "root_cause": "SnoozeHelper keeps three maps: the in-memory mSnoozedNotifications (key to NotificationRecord) and two persisted maps, mPersistedSnoozedNotifications (key to trigger time) and mPersistedSnoozedNotificationsWithContext (key to context id), which survive reboots via XML. canSnooze(numberToSnooze) only compared mSnoozedNotifications.size() + numberToSnooze against CONCURRENT_SNOOZE_LIMIT, so the persisted maps were never bounded. In addition, repostGroupSummary removed the group summary only from mSnoozedNotifications and left its entries in the persisted maps, so persisted entries could accumulate while the in-memory count stayed under the limit. Per the commit message, serializing a huge number of persisted snoozed notifications caused a boot loop (DoS).",
     "fix_analysis": [
      "services/core/java/com/android/server/notification/SnoozeHelper.java:canSnooze - the limit check is extended with a second condition: if mPersistedSnoozedNotifications.size() + mPersistedSnoozedNotificationsWithContext.size() + numberToSnooze exceeds CONCURRENT_SNOOZE_LIMIT, canSnooze returns false. Both checks are evaluated under mLock.",
      "services/core/java/com/android/server/notification/SnoozeHelper.java:repostGroupSummary - after mSnoozedNotifications.remove(groupSummaryKey), the key is normalized with getTrimmedString(groupSummaryKey) and removed from both mPersistedSnoozedNotificationsWithContext and mPersistedSnoozedNotifications, keeping persisted state consistent with in-memory state.",
      "services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java - adds testSnoozeLimit_maximumPersisted (loads CONCURRENT_SNOOZE_LIMIT entries via readXml and asserts canSnooze(1) is false while persisted lookups still work), extends repostGroupSummary_repostsSummary and adds snoozeWithContext_repostGroupSummary_removesPersisted to assert persisted entries are cleared on repost."
     ],
     "completeness": [
      "Covers: the admission check (canSnooze) now bounds both persisted maps in aggregate, and the repostGroupSummary path now cleans persisted entries for the summary key.",
      "The persisted maps are only bounded at snooze admission time; readXml itself (used in the new test to load entries) is not shown to enforce any limit on what it deserializes, so an already-oversized persisted file is not truncated by this diff.",
      "The commit message lists a third item, 'Prevent unpriviledged API calls that allow 3P apps to snooze notifications with context/criterion', but the diff touches only SnoozeHelper.java and its test; that change is not in this commit and is not shown in the material.",
      "Other removal paths in SnoozeHelper (cancel, repost of non-summary notifications, user/package cleanup) are not visible in the material, so whether they already clean the persisted maps cannot be judged from the diff."
     ],
     "behavior_changes": [
      "canSnooze returns false when the combined count of persisted timed and context-snoozed notifications plus the requested number would exceed CONCURRENT_SNOOZE_LIMIT, even if the in-memory map is small; snooze requests are then refused.",
      "After repostGroupSummary, getSnoozeTimeForUnpostedNotification returns 0 and getSnoozeContextForUnpostedNotification returns null for the summary key (previously the stale persisted values remained)."
     ],
     "preconditions": [
      "Ability to cause notifications to be snoozed (timed or with context) so that SnoozeHelper entries are created; the commit message indicates 3P apps could reach snooze-with-context via unprivileged API calls, but the relevant code is not in the material.",
      "Reaching the state where persisted entries outnumber in-memory ones, for example via repostGroupSummary cycles that previously left persisted entries behind.",
      "Persisted snooze state is serialized to XML and read back across reboots (readXml), which is where the commit message places the boot-loop effect."
     ],
     "per_major": {
      "14": "same fix: commit a8fb9fe93efd in android-security-14.0.0_r5 (cherry-pick of 965ff2d3c548, Change-Id I3571fa9207b778def652130d3ca840183a9a8414); at android-security-14.0.0_r29 7/8 lines of the patched region are present.",
      "15": "not affected (the bulletin does not list Android 15); 7/8 lines of the patched region present at android-security-15.0.0_r17.",
      "16": "not affected (the bulletin does not list Android 16); 7/8 lines of the patched region present at android-security-16.0.0_r8.",
      "17": "not affected (the bulletin does not list Android 17); 7/8 lines of the patched region present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "The production change in SnoozeHelper.java adds 7 lines and removes 1 (canSnooze and repostGroupSummary) and depends only on the existing mPersistedSnoozedNotifications, mPersistedSnoozedNotificationsWithContext and getTrimmedString members; verify those names exist in the target branch. The commit message's API-privilege item is not in this diff, so look for a companion NotificationManagerService change when backporting the full bug fix.",
      "The new tests use TypedXmlSerializer/TypedXmlPullParser and Truth assertThat; older branches may need the test adapted if those helpers differ (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The companion change for 'Prevent unpriviledged API calls that allow 3P apps to snooze notifications with context/criterion' referenced in the commit message is not in the material.",
      "The exact mechanism by which oversized persisted state causes a boot loop (where serialization/deserialization fails) is only stated in the commit message, not shown in code.",
      "Whether and how the fix was applied to Android 12, 12L and 13, which the bulletin lists as affected, is not in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-0027": {
     "cve": "CVE-2024-0027",
     "summary": "SnoozeHelper.canSnooze now also counts the two persisted snoozed-notification maps against CONCURRENT_SNOOZE_LIMIT, and repostGroupSummary removes the group summary key from those persisted maps when the in-memory record is removed, bounding the snoozed-notification state that is serialized to disk.",
     "component": "platform/frameworks/base, NotificationManagerService / SnoozeHelper (services/core/java/com/android/server/notification)",
     "weakness": "Missing resource limit enforcement (unbounded growth of persisted state) plus a leak of persisted entries; DoS per bulletin",
     "root_cause": "SnoozeHelper keeps three maps: in-memory mSnoozedNotifications (key -> NotificationRecord) and two persisted maps, mPersistedSnoozedNotifications (key -> time) and mPersistedSnoozedNotificationsWithContext (key -> context id), which survive reboots via XML. canSnooze(int) only compared mSnoozedNotifications.size() + numberToSnooze against CONCURRENT_SNOOZE_LIMIT, so the persisted maps were never bounded. In addition, repostGroupSummary removed the group summary record from mSnoozedNotifications but left its entry in both persisted maps, so persisted entries could accumulate independently of the in-memory count. Per the commit message, serializing a huge number of snoozed notifications caused a boot loop.",
     "fix_analysis": [
      "services/core/java/com/android/server/notification/SnoozeHelper.java:canSnooze - the limit check now also returns false when mPersistedSnoozedNotifications.size() + mPersistedSnoozedNotificationsWithContext.size() + numberToSnooze exceeds CONCURRENT_SNOOZE_LIMIT, under mLock, in addition to the existing in-memory check.",
      "services/core/java/com/android/server/notification/SnoozeHelper.java:repostGroupSummary - after mSnoozedNotifications.remove(groupSummaryKey), the fix computes getTrimmedString(groupSummaryKey) and removes that trimmed key from mPersistedSnoozedNotificationsWithContext and mPersistedSnoozedNotifications, so the persisted state tracks the in-memory removal.",
      "services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java - adds testSnoozeLimit_maximumPersisted (loads CONCURRENT_SNOOZE_LIMIT persisted entries via readXml, asserts canSnooze(1) is false while isSnoozed is false), extends repostGroupSummary_repostsSummary and adds snoozeWithContext_repostGroupSummary_removesPersisted to assert the persisted entry for the summary is cleared."
     ],
     "completeness": [
      "Covers: the persisted maps are now counted in canSnooze, and the group-summary repost path cleans up both persisted maps.",
      "The commit message lists a third item, 'Prevent unpriviledged API calls that allow 3P apps to snooze notifications with context/criterion', and a NotificationManagerServiceTest test target, but the diff touches only SnoozeHelper.java and SnoozeHelperTest.java; that NotificationManagerService change is not in this commit as shown, so it is either a separate change or not part of this cherry-pick.",
      "The persisted-map check in canSnooze uses the combined size of both persisted maps against the same single CONCURRENT_SNOOZE_LIMIT; the in-memory and persisted checks are independent, so either one reaching the limit blocks new snoozes.",
      "Only repostGroupSummary gained persisted-map cleanup in this diff; other removal paths in SnoozeHelper are not shown in the material, so whether they already clear the persisted maps cannot be judged from what is provided."
     ],
     "behavior_changes": [
      "canSnooze returns false (new snoozes refused) when the persisted snoozed-notification entries, including ones loaded from XML after a reboot, reach CONCURRENT_SNOOZE_LIMIT, even if the in-memory map is small.",
      "After repostGroupSummary, getSnoozeTimeForUnpostedNotification returns 0 and getSnoozeContextForUnpostedNotification returns null for the reposted summary key, where previously stale persisted values remained."
     ],
     "preconditions": [
      "Ability to drive SnoozeHelper.snooze repeatedly (snooze by duration or by context/criterion) so that persisted entries accumulate; the commit message indicates 3P apps could reach the context/criterion snooze path through unprivileged API calls, but that caller-side code is not in the provided diff.",
      "Persisted snooze state is written to and read from XML (readXml), so accumulated entries survive reboot and are reprocessed at boot."
     ],
     "per_major": {
      "14": "same fix: commit a8fb9fe93efd in android-security-14.0.0_r5 (cherry-pick of 965ff2d3c548, Change-Id I3571fa9207b778def652130d3ca840183a9a8414); at android-security-14.0.0_r29 the patched region is present (7/8 lines matched).",
      "15": "not affected per the bulletin; the patched region is present at android-security-15.0.0_r17 (7/8 lines).",
      "16": "not affected per the bulletin; patched region present at android-security-16.0.0_r8 (7/8 lines).",
      "17": "not affected per the bulletin; patched region present at android-security-17.0.0_r1 (7/8 lines)."
     },
     "backport_notes": [
      "The SnoozeHelper change is self-contained: it needs only the two persisted maps, getTrimmedString, and CONCURRENT_SNOOZE_LIMIT, all of which appear in the Android 14 (android-security-14.0.0_r29) code shown. Check whether the separate NotificationManagerService hardening referenced in the commit message (restricting context/criterion snoozing by unprivileged callers) exists on the target branch; it is not included in this diff.",
      "The new test uses com.google.common.truth (assertThat) and TypedXmlSerializer/TypedXmlPullParser via Xml.newFastSerializer/newFastPullParser; older branches may need test adaptation if those helpers differ (general platform knowledge)."
     ],
     "confidence": "high",
     "unknowns": [
      "The commit for the third item in the message (preventing unprivileged 3P snooze-with-context API calls, tested by NotificationManagerServiceTest) is not in the material; its content and branch coverage are unknown.",
      "The exact value of CONCURRENT_SNOOZE_LIMIT and the full set of SnoozeHelper paths that add or remove persisted entries are not shown.",
      "Why 1 of 8 lines of the patched region differs on the latest tags is not shown.",
      "How the patched region came to be present on Android 15, 16 and 17 is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-23704": {
     "cve": "CVE-2024-23704",
     "summary": "WifiDialogActivity.onCreate now also finishes immediately when the user has the DISALLOW_ADD_WIFI_CONFIG user restriction, in addition to the existing DISALLOW_CONFIG_WIFI check.",
     "component": "platform/packages/apps/Settings, Wi-Fi settings (WifiDialogActivity)",
     "weakness": "Missing authorization check (user restriction not enforced)",
     "root_cause": "WifiDialogActivity.onCreate gated the activity only on isConfigWifiAllowed(), which checks the DISALLOW_CONFIG_WIFI user restriction. The separate DISALLOW_ADD_WIFI_CONFIG restriction was never consulted, so a user subject to that restriction could still be shown the Wi-Fi configuration dialog this activity hosts. That bypassed the device policy meant to prevent adding Wi-Fi configurations.",
     "fix_analysis": [
      "src/com/android/settings/wifi/WifiDialogActivity.java:isAddWifiConfigAllowed (new, @VisibleForTesting): gets UserManager via getSystemService. It returns false (and logs with Log.e) if userManager != null and hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG) is true. Otherwise it returns true.",
      "src/com/android/settings/wifi/WifiDialogActivity.java:onCreate: the early-exit condition after super.onCreate changes from `if (!isConfigWifiAllowed())` to `if (!isConfigWifiAllowed() || !isAddWifiConfigAllowed())`. When either restriction is set, the activity calls finish() and returns.",
      "tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java: adds two Robolectric tests, isAddWifiConfigAllowed_hasNoUserRestriction_returnTrue and isAddWifiConfigAllowed_hasUserRestriction_returnFalse. Both mock mUserManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)."
     ],
     "completeness": [
      "Adds the current user's DISALLOW_ADD_WIFI_CONFIG check to the early exit in WifiDialogActivity.onCreate. The material does not show whether other code paths in the activity (for example a re-delivered intent) can show the dialog without going through onCreate.",
      "If UserManager is null, the new method returns true (allowed), which is a permissive fallback. The material does not show how isConfigWifiAllowed handles that case.",
      "Only this activity is changed. The material does not show whether other Settings entry points that create Wi-Fi configurations enforce DISALLOW_ADD_WIFI_CONFIG, so coverage beyond WifiDialogActivity is unknown."
     ],
     "behavior_changes": [
      "WifiDialogActivity finishes immediately in onCreate for users with DISALLOW_ADD_WIFI_CONFIG, and callers get no dialog.",
      "New error log line: \"The user is not allowed to add Wi-Fi configuration.\""
     ],
     "preconditions": [
      "The DISALLOW_ADD_WIFI_CONFIG user restriction is set for the user (for example by a device policy controller; the commit was tested with TestDPC) while DISALLOW_CONFIG_WIFI is not set.",
      "WifiDialogActivity in Settings is launched. How it is reached from outside Settings is not shown in the material."
     ],
     "per_major": {
      "14": "same fix: commit 2e90322bab7d in android-security-14.0.0_r7 (cherry-pick of 51fa3d798ad0, RESTRICT AUTOMERGE). At android-security-14.0.0_r29, 7/8 lines of the patched region are present, and the DISALLOW_ADD_WIFI_CONFIG import is visible in the code context.",
      "15": "not affected per the bulletin. At android-security-15.0.0_r17, 7/8 lines of the patched region are present. The line that differs is not identified.",
      "16": "not affected per the bulletin. At android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "not affected per the bulletin. At android-security-17.0.0_r1, 7/8 lines of the patched region are present."
     },
     "backport_notes": [
      "Self-contained two-file change. It depends only on the existing isConfigWifiAllowed() gate in onCreate and the UserManager.DISALLOW_ADD_WIFI_CONFIG constant. If a fork's WifiDialogActivity.onCreate differs, add the isAddWifiConfigAllowed() call to the early-exit condition that comes before dialog creation."
     ],
     "confidence": "high",
     "unknowns": [
      "Whether WifiDialogActivity is exported, and how an unprivileged caller reaches it (not shown in the material).",
      "Which of the 8 patched lines differs in the latest security tags (the per-major facts only report 7/8 present).",
      "Whether a matching fix for Android 13 exists. The bulletin lists 13 as affected, but no 13 commit is in the material.",
      "Whether the dialog can be shown through activity paths other than onCreate."
     ],
     "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-2024-23710": {
     "cve": "CVE-2024-23710",
     "summary": "In InstallPackageHelper, two shared-user privilege checks (assertPackageWithSharedUserIdIsPrivileged and a second scan-time block guarded by SCAN_AS_PRIVILEGED and skipVendorPrivilegeScan) now also require !pkg.isLeavingSharedUser(), so packages leaving their shared user are excluded from both.",
     "component": "platform/frameworks/base, PackageManagerService install/scan (services/core/java/com/android/server/pm/InstallPackageHelper.java)",
     "weakness": "Incorrect privilege assignment: shared-user privilege handling keyed off a declared sharedUserId without accounting for pkg.isLeavingSharedUser()",
     "root_cause": "Two conditions in InstallPackageHelper decided shared-user privilege handling from `!AndroidPackageUtils.isPrivileged(pkg) && pkg.getSharedUserId() != null` (the second also requires `(scanFlags & SCAN_AS_PRIVILEGED) == 0` and `!skipVendorPrivilegeScan`). They did not exclude packages for which pkg.isLeavingSharedUser() is true. The commit title says the fix stops marking apps as privileged when they are not signed properly. The diff shows the change itself is the added isLeavingSharedUser() exclusion.",
     "fix_analysis": [
      "InstallPackageHelper.assertPackageWithSharedUserIdIsPrivileged: the guard becomes `!AndroidPackageUtils.isPrivileged(pkg) && pkg.getSharedUserId() != null && !pkg.isLeavingSharedUser()`. As a result, the SharedUserSetting lookup, the isPrivileged() test and the platform-signature comparison (which throws INTERNAL_ERROR_NOT_PRIV_SHARED_USER) are skipped for a package that is leaving its shared user.",
      "InstallPackageHelper, second hunk (around line 4783; the method name is not shown in the diff): `&& !pkg.isLeavingSharedUser()` is added to the guard `(scanFlags & SCAN_AS_PRIVILEGED) == 0 && !isPrivileged(pkg) && getSharedUserId() != null && !skipVendorPrivilegeScan`. The body of this block is not in the material.",
      "Single commit with two hunks; the same predicate is added to both."
     ],
     "completeness": [
      "Covers the two patched conditions in InstallPackageHelper.java.",
      "The fix only adds a predicate. It relies on pkg.isLeavingSharedUser() being computed correctly, which the material does not show.",
      "The material does not show whether other code paths derive privilege from getSharedUserId() without checking isLeavingSharedUser(). Per index search (which may be incomplete), no other caller of assertPackageWithSharedUserIdIsPrivileged was found."
     ],
     "behavior_changes": [
      "A non-privileged package that declares a sharedUserId but is leaving it is no longer rejected with INTERNAL_ERROR_NOT_PRIV_SHARED_USER when that shared user is privileged and the package is not platform-signed.",
      "Such a package no longer enters the second guarded block (around line 4783). The effect of that block is not shown in the material.",
      "No API or error-code changes are visible; two install-time conditions are narrowed."
     ],
     "preconditions": [
      "Package install or scan handled by InstallPackageHelper for a package where AndroidPackageUtils.isPrivileged(pkg) is false.",
      "The package declares a sharedUserId and pkg.isLeavingSharedUser() is true. For the first site, the SharedUserSetting exists and isPrivileged() is true.",
      "For the second site, SCAN_AS_PRIVILEGED is not set in scanFlags and skipVendorPrivilegeScan is false.",
      "The bulletin rates it High EoP. The material does not describe the attacker position further."
     ],
     "per_major": {
      "14": "same fix: commit c7c1193ca755 in android-security-14.0.0_r7 (cherry-pick of 3ee5dfdcba04 / 06775341ad7d, Change-Id I5b5b81cf43b06837a22c8dfd170a112106dd64c1). At android-security-14.0.0_r29 the assertPackageWithSharedUserIdIsPrivileged hunk is present, and 7/8 lines of the patched region are present.",
      "15": "not affected per the bulletin; 6/8 lines of the patched region present at android-security-15.0.0_r17; exact code not shown.",
      "16": "not affected per the bulletin; 6/8 lines of the patched region present at android-security-16.0.0_r8; exact code not shown.",
      "17": "not affected per the bulletin; 6/8 lines of the patched region present at android-security-17.0.0_r1; exact code not shown."
     },
     "backport_notes": [
      "Both hunks only add a predicate. They depend on AndroidPackage exposing isLeavingSharedUser().",
      "The bulletin lists Android 13 as affected, but the material has no Android 13 commit. Locate the equivalent conditions by the `!AndroidPackageUtils.isPrivileged(pkg) && pkg.getSharedUserId() != null` pattern."
     ],
     "confidence": "medium",
     "unknowns": [
      "Name and body of the second patched method (the hunk around line 4783).",
      "The precise link between the commit title ('not signed properly') and the leaving-shared-user condition.",
      "How isLeavingSharedUser() is computed, and whether an installer-controlled manifest can set it.",
      "Android 13 code paths, and whether the Android 15+ trees contain the identical predicate (only line-match counts are given)."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 6,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-23712": {
     "cve": "CVE-2024-23712",
     "summary": "AppOpsService now validates the proxy attribution tag on the note and start operation paths (nulling tags not declared in the proxy or target package), and ParsedAttributionImpl.MAX_NUM_ATTRIBUTIONS is reduced from 10000 to 1000.",
     "component": "platform/frameworks/base, AppOps (services/core AppOpsService) and package parsing (ParsedAttributionImpl)",
     "weakness": "Missing input validation of the proxy attribution tag (bulletin type: DoS)",
     "root_cause": "In AppOpsService the proxied attributionTag was already validated through verifyAndGetBypass (pvr.isAttributionTagValid nulls it when invalid, visible in the r29 context). The proxyAttributionTag supplied on the note and start operation paths was used without any check that it is declared by the proxy package or the target package. Separately, the per-package attribution limit ParsedAttributionImpl.MAX_NUM_ATTRIBUTIONS was 10000. The bulletin classifies the issue as a High severity DoS; the exact exhaustion mechanism is not shown in the material.",
     "fix_analysis": [
      "services/core/java/com/android/server/appop/AppOpsService.java: adds private helper isAttributionTagDefined(packageName, proxyPackageName, attributionTag). It returns false if packageName is null and true if attributionTag is null. Otherwise it gets PackageManagerInternal. If proxyPackageName is non-null and its AndroidPackage is found, it returns true when isAttributionInPackage(proxyPkg, attributionTag). Otherwise it returns isAttributionInPackage(pmInt.getPackage(packageName), attributionTag).",
      "AppOpsService.java, note path (hunk after an early MODE_ERRORED SyncNotedAppOp return and before synchronized(this)/getOpsLocked, likely noteOperationUnchecked): if proxyAttributionTag != null and !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag), proxyAttributionTag is set to null.",
      "AppOpsService.java, start path (hunk after an early MODE_ERRORED return and before 'boolean isRestricted = false; int startType = START_TYPE_FAILED;'): the same nulling of an undefined proxyAttributionTag.",
      "services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java: MAX_NUM_ATTRIBUTIONS lowered from 10000 to 1000."
     ],
     "completeness": [
      "Covers proxyAttributionTag on the note and start paths only; the proxied attributionTag was already handled by existing verifyAndGetBypass/isAttributionTagValid logic (r29 context).",
      "Validation is lenient: an undefined proxy tag is replaced with null and the operation continues; no error is returned.",
      "isAttributionTagDefined accepts the tag if it is declared in either the proxy package or the target package, so a tag declared only in the target package is still accepted as a proxy tag.",
      "The helper may call isAttributionInPackage with a null package when the target package is not found; the material does not show how that case is handled.",
      "The attribution cap is reduced, not removed."
     ],
     "behavior_changes": [
      "Proxy attribution tags not declared in the proxy or target package are dropped (set to null) on the proxy note/start flows; no new error code is returned.",
      "The per-package attribution limit constant drops from 10000 to 1000; where it is enforced is not shown in the material.",
      "Each note/start with a non-null proxyAttributionTag now performs PackageManagerInternal.getPackage lookups."
     ],
     "preconditions": [
      "A caller reaching the AppOps note or start operation path with a non-null proxyAttributionTag, i.e. the proxy flow that passes proxyUid/proxyPackageName/proxyAttributionTag into noteOperationUnchecked (visible in the r29 context), and the corresponding start path.",
      "For the cap: a package declaring a large number of attributions."
     ],
     "per_major": {
      "14": "Same fix: commit 6beb68ca17d1 (RESTRICT AUTOMERGE cherry-pick of 2806d263c0b7) in android-security-14.0.0_r7; 8/8 lines of the patched region present at android-security-14.0.0_r29.",
      "15": "Not affected per the bulletin (not listed); patched region present (8/8 lines) at android-security-15.0.0_r17.",
      "16": "Not affected per the bulletin (not listed); patched region present (8/8 lines) at android-security-16.0.0_r8.",
      "17": "Not affected per the bulletin (not listed); patched region present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit. It relies on existing AppOpsService.isAttributionInPackage(AndroidPackage, String) and PackageManagerInternal.getPackage, which the commit does not add. ParsedAttributionImpl's codegen comment references an older core/java/android/content/pm/parsing/component path, so check where the class lives in the target tree.",
      "RESTRICT AUTOMERGE: each branch needs its own cherry-pick. The note hunk anchors before synchronized(this)/getOpsLocked; the start hunk anchors before 'startType = START_TYPE_FAILED'."
     ],
     "confidence": "medium",
     "unknowns": [
      "The exact DoS mechanism (what is exhausted) is not shown in the material.",
      "Whether isAttributionInPackage tolerates a null AndroidPackage.",
      "The enclosing method names of both hunks are not shown directly.",
      "How MAX_NUM_ATTRIBUTIONS is enforced during parsing.",
      "Whether other AppOps entry points (e.g. finish paths or async noting) consume a proxy attribution tag without this validation."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 9,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-23713": {
     "cve": "CVE-2024-23713",
     "summary": "In NotificationManagerService.migrateNotificationFilter, a VersionedPackage is now added to the listener's NotificationListenerFilter only when the resolved uid is not INVALID_UID, so packages whose uid cannot be resolved for a user are no longer stored in the filter.",
     "component": "platform/frameworks/base, NotificationManagerService (notification listener filter migration)",
     "weakness": "Missing validation of a resolved uid (improper input validation): package names supplied by a listener that could not be resolved to a uid were still stored, with the unresolved uid, in the listener filter.",
     "root_cause": "migrateNotificationFilter(INotificationListener token, int defaultTypes, List<String> disallowedApps) loops over the caller-supplied disallowedApps. For each profile of the listener's user it calls getUidForPackageAndUser(pkg, UserHandle.of(userId)), always wraps the result in new VersionedPackage(pkg, uid) and calls nlf.addPackage(vp). Only the exception path was treated as \"pkg doesn't exist on that user; skip\". When getUidForPackageAndUser returns INVALID_UID instead of throwing, the package was still stored in the NotificationListenerFilter and saved via mListeners.setNotificationListenerFilter(key, nlf). The new test reaches this case by making getPackageUid throw RemoteException for \"apples\", which suggests the helper turns that exception into INVALID_UID. The commit message's test step ('view policy xml file') suggests the filter ends up in the policy xml file.",
     "fix_analysis": [
      "services/core/java/com/android/server/notification/NotificationManagerService.java, migrateNotificationFilter: the VersionedPackage creation and nlf.addPackage(vp) are now wrapped in `if (uid != INVALID_UID)`, so a package whose uid cannot be resolved for a given user is skipped for that user. The existing try/catch that skips on exceptions is unchanged.",
      "services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java: adds testMigrateNotificationFilter_invalidPackage. The test makes getPackageUid throw for \"apples\" on every user and resolves \"bananas\"/\"cherries\" to 9000/9001. It then asserts that the captured filter disallows the valid pairs and contains no entry named \"apples\". The test file also imports org.junit.Assert.assertNotEquals."
     ],
     "completeness": [
      "Covers the only place in migrateNotificationFilter where packages are added: the per-user loop now rejects INVALID_UID results as well as skipping on exceptions as before.",
      "Valid entries are unchanged: packages that resolve to a real uid are still added, and the defaultTypes handling (nlf.setTypes when areAllTypesAllowed) is untouched.",
      "The fix checks only the result of uid resolution. In the patched region it does not limit the size of the caller-supplied disallowedApps list or validate the package name strings themselves.",
      "The guard applies only to this migration path. Other code that fills or reads a NotificationListenerFilter is not in the material, so it is not shown whether that code rejects INVALID_UID entries on its own."
     ],
     "behavior_changes": [
      "Packages in disallowedApps that do not resolve to a uid for a given profile are silently dropped for that profile instead of being stored with INVALID_UID. No error is returned to the caller.",
      "The saved listener filter (the policy xml, per the commit message's test step) no longer gets VersionedPackage entries with an invalid uid from this call."
     ],
     "preconditions": [
      "mListeners.checkServiceTokenLocked(token) must succeed for the caller's notification listener token.",
      "The listener's existing NotificationListenerFilter must have an empty disallowed-package set (nlf.getDisallowedPackages().isEmpty()) and disallowedApps must be non-null for the loop to run.",
      "Uid resolution via getUidForPackageAndUser must fail without throwing (returning INVALID_UID) for the previously vulnerable branch to be reached."
     ],
     "per_major": {
      "14": "same fix: commit ffd616dc3b91 in android-security-14.0.0_r7 (cherry-pick of bfa04e208995, Change-Id Ib7fcb733edd2cf2cbac0a7699763a5fe993b467e). At android-security-14.0.0_r29 all 8/8 lines of the patched region are present.",
      "15": "not affected per the bulletin. The fixed code (INVALID_UID guard) is present at android-security-15.0.0_r17 (8/8 lines).",
      "16": "not affected per the bulletin. The fixed code is present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "not affected per the bulletin. The fixed code is present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "Single small commit (4 lines added, 2 removed) inside migrateNotificationFilter. The diff uses INVALID_UID without adding an import, so the target file must already import it (android.os.Process.INVALID_UID in AOSP, general platform knowledge) or the import must be added."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show the body of getUidForPackageAndUser, so when it returns INVALID_UID rather than throwing is inferred only from the test setup.",
      "What happens downstream with a saved INVALID_UID VersionedPackage (when the policy xml is read back or the filter is evaluated) is not shown. The bulletin only rates the issue as High EoP.",
      "Whether other writers of NotificationListenerFilter perform an equivalent uid check is not shown.",
      "The body of checkServiceTokenLocked, and so exactly which callers pass the token check, is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 4,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "15": {
   "counts": {
    "announced": 0,
    "certain": 0,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [],
   "tags": [],
   "unannounced": [],
   "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": {}
  },
  "16": {
   "counts": {
    "announced": 0,
    "certain": 0,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [],
   "tags": [],
   "unannounced": [],
   "coverage": {
    "n_ingested_tags": 9,
    "first_ingested_tag": "android-security-16.0.0_r1",
    "latest_ingested_tag": "android-security-16.0.0_r9",
    "window_base_tag": "android-16.0.0_r2",
    "window_start": "2025-06-10",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {}
  },
  "17": {
   "counts": {
    "announced": 0,
    "certain": 0,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [],
   "tags": [],
   "unannounced": [],
   "coverage": {
    "n_ingested_tags": 2,
    "first_ingested_tag": "android-security-17.0.0_r1",
    "latest_ingested_tag": "android-security-17.0.0_r2",
    "window_base_tag": "android-17.0.0_r1",
    "window_start": "2026-06-16",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {}
  }
 },
 "bulletin": {
  "url": "https://source.android.com/docs/security/bulletin/2024-04-01",
  "latest_version": "1.0",
  "latest_revision": {
   "version": "1.0",
   "date": "April 1, 2024",
   "notes": "Bulletin published"
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "April 1, 2024",
    "notes": "Bulletin published"
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [],
  "out_of_scope_rows": {
   "MediaTek components": 3,
   "Qualcomm closed-source components": 14,
   "Qualcomm components": 2,
   "Widevine": 1
  },
  "note": "changes are only visible from `tracked_since`; earlier revisions of the bulletin cannot be reconstructed. Out-of-scope rows (vendor closed-source, kernel) have no public AOSP code to resolve."
 }
}