{
 "month": "2024-07",
 "generated": "2026-10-06T12:05+00:00",
 "majors": {
  "14": {
   "counts": {
    "announced": 6,
    "certain": 6,
    "probable": 0,
    "not_published": 0,
    "not_covered": 0,
    "awaiting": 0
   },
   "advisories": [
    {
     "cve": "CVE-2024-31331",
     "osv_id": "ASB-A-297517712",
     "source": "osv",
     "announced": "2024-07-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "PackageManagerService.setMimeGroup now sends an ACTION_PACKAGE_CHANGED broadcast for the package, once per user where it is installed and with dontKillApp=true. It does this whenever mComponentResolver.updateMimeGroup reports a change, in addition to clearing the package's preferred activities.",
      "confidence": "high",
      "backport_notes": [
       "Single-commit fix in PackageManagerService.java (Change-Id I271a3526ea4555249e3a2797605269257330e0e9, cherry-picked three times internally). On 14 it uses snapshotComputer(), PackageUserStateInternal, resolveUserIds and sendPackageChangedBroadcast as they exist in that file. The material has no diffs for 12, 12L or 13."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 8,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "352a84fa6c0f3a8df37df627ef0dbc0e08916e48",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-31332",
     "osv_id": "ASB-A-299931076",
     "source": "osv",
     "announced": "2024-07-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Settings now checks the DISALLOW_ADD_WIFI_CONFIG user restriction in WifiDppConfiguratorActivity (onCreate and handleIntent) and AddNetworkFragment (onCreate), and finishes the activity when the restriction is set, so a restricted user can no longer use these UI entry points to add Wi-Fi configurations.",
      "confidence": "high",
      "backport_notes": [
       "Single self-contained commit that only adds code (188 insertions). It depends on UserManager.DISALLOW_ADD_WIFI_CONFIG and hasUserRestriction, which exist on Android 13 and later (general platform knowledge). In handleIntent, the check goes right before the existing isGuestUser check. Forks that customized onCreate or handleIntent should insert the check before any intent or saved-state processing."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "change_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/apps/Settings",
       "sha": "20b312f86a7082351d9d2ab45a100d8e278277ec",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "change_id",
       "confidence": "certain",
       "evidence": "ref d1f9e61e4480",
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/apps/Settings"
     ]
    },
    {
     "cve": "CVE-2024-31339",
     "osv_id": "ASB-A-292160348",
     "source": "osv",
     "announced": "2024-07-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "MultiConditionTrigger no longer runs its trigger callback on a detached std::thread; the thread becomes a class member joined in the destructor, and StatsService gains a condition-variable based early-termination request so the delayed onStatsdInitCompleted handler exits promptly on shutdown, termination, destruction or StatsCompanionService death.",
      "confidence": "high",
      "backport_notes": [
       "Single commit. The StatsService.cpp hunk replaces a sleep_for(mInitEventDelaySecs) delay (comment references b/277958338), so the StatsService part must be adapted to whatever onStatsdInitCompleted does on the target branch. The new test uses getElapsedRealtimeNs/NanoToMillis from tests/statsd_test_util.h.",
       "StatsD is a mainline (APEX) module (the commit is flagged as a mainline module bug fix), so the fix can also ship through a module update."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 7,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/modules/StatsD",
       "sha": "cb201622f3d45df6de019e993012563a14d8c0aa",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/modules/StatsD"
     ]
    },
    {
     "cve": "CVE-2024-34720",
     "osv_id": "ASB-A-319081336",
     "source": "osv",
     "announced": "2024-07-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds a peer-UID check (must be Process.SYSTEM_UID) when a Zygote connection is created in Java, and reworks the native nativeForkRepeatedly loop so that newly accepted connections are UID-checked immediately and dropped on mismatch instead of being handed, unchecked, back to the Java command handler.",
      "confidence": "high",
      "backport_notes": [
       "Single commit, two files; the native hunk rewrites the whole inner for(;;) loop of nativeForkRepeatedly, so forks that modified that loop (dup2 fd-reuse logic, poll handling) must re-apply the valid_session_socket/accept-retry structure by hand. ZygoteSecurityException must already exist in the fork (it is referenced, not added, by the diff)."
      ],
      "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_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "7905ffaa89e7a921d5a97f86fc320e336f359c32",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    },
    {
     "cve": "CVE-2024-34721",
     "osv_id": "ASB-A-294406604",
     "source": "osv",
     "announced": "2024-07-01",
     "severity": "High",
     "types": [
      "ID"
     ],
     "status": "resolved",
     "brief": {
      "summary": "Adds an assertFileColumnsConsistent(match, uri, values) call in MediaProvider.java right after FileUtils.computeDataFromValues(). This covers the case where DATA is generated from values such as RELATIVE_PATH and DISPLAY_NAME, so files can no longer be inserted into another user's storage volume.",
      "confidence": "medium",
      "backport_notes": [
       "A single one-line functional change: insert assertFileColumnsConsistent(match, uri, values) right after FileUtils.computeDataFromValues(values, volumePath, isFuseThread()) in the generated-DATA block. Check that the helper exists in the target branch. Update testInsertionWithInvalidFilePath_throwsIllegalArgumentException as in the diff."
      ],
      "meta": {
       "generated": "2026-10-04",
       "author_model": "claude-fable-5-1",
       "review_model": "claude-opus-5-5",
       "review_corrections": 11,
       "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
      }
     },
     "method": [
      "bug_id"
     ],
     "confidence": "certain",
     "first_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/packages/providers/MediaProvider",
       "sha": "520edca27f2da9671ce78de4faf501bcdadf7b4c",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/packages/providers/MediaProvider"
     ]
    },
    {
     "cve": "CVE-2024-34723",
     "osv_id": "ASB-A-317048338",
     "source": "osv",
     "announced": "2024-07-01",
     "severity": "High",
     "types": [
      "EoP"
     ],
     "status": "resolved",
     "brief": {
      "summary": "ParcelableListBinder now takes the expected element Class and only adds deserialized Parcelables that are assignable to it, and MediaSessionRecord.getBinderForSetQueue passes QueueItem.class, so non-QueueItem Parcelable objects are no longer accepted into the media session queue list.",
      "confidence": "high",
      "backport_notes": [
       "Single commit with two files that must be applied together. The ParcelableListBinder constructor change breaks compilation of any instantiation that is not updated, so grep the target tree for 'new ParcelableListBinder'. The material only shows MediaSessionRecord.getBinderForSetQueue; the index search found no other callers but may be incomplete.",
       "Consider adding a null guard before object.getClass() when backporting if the target branch tolerated null list elements."
      ],
      "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_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "last_fix": {
      "tag": "android-security-14.0.0_r10",
      "date": "2024-07-01",
      "provenance": "security_tag"
     },
     "public_commits": [
      {
       "project": "platform/frameworks/base",
       "sha": "9c8c745df069b86bf81d586e0af910996809db51",
       "tag": "android-security-14.0.0_r10",
       "provenance": "security_tag",
       "method": "bug_id",
       "confidence": "certain",
       "evidence": null,
       "similarity": null
      }
     ],
     "n_public_commits": 1,
     "projects": [
      "platform/frameworks/base"
     ]
    }
   ],
   "tags": [
    "android-security-14.0.0_r10"
   ],
   "unannounced": [],
   "coverage": {
    "n_ingested_tags": 30,
    "first_ingested_tag": "android-security-14.0.0_r1",
    "latest_ingested_tag": "android-security-14.0.0_r30",
    "window_base_tag": "android-14.0.0_r1",
    "window_start": "2023-10-04",
    "partial_tags": []
   },
   "platform_update_tags": {},
   "briefs": {
    "CVE-2024-31331": {
     "cve": "CVE-2024-31331",
     "summary": "PackageManagerService.setMimeGroup now sends an ACTION_PACKAGE_CHANGED broadcast for the package, once per user where it is installed and with dontKillApp=true. It does this whenever mComponentResolver.updateMimeGroup reports a change, in addition to clearing the package's preferred activities.",
     "component": "platform/frameworks/base, PackageManagerService (package manager, MIME group / intent filter handling)",
     "weakness": "Missing state-change notification: a mimeGroup update that changed the component resolver's state was committed without sending ACTION_PACKAGE_CHANGED. The bulletin rates it EoP, High.",
     "root_cause": "In services/core/java/com/android/server/pm/PackageManagerService.java, setMimeGroup() commits the new MIME type set via commitPackageStateMutation() and then calls mComponentResolver.updateMimeGroup(). Before the fix, when that call returned true, the method only cleared the package's preferred activities (mPreferredActivityHelper.clearPackagePreferredActivities). It sent no ACTION_PACKAGE_CHANGED broadcast for the mimeGroup change.",
     "fix_analysis": [
      "PackageManagerService.java:setMimeGroup: the Binder.withCleanCallingIdentity lambda that ran clearPackagePreferredActivities(packageName, UserHandle.USER_ALL) is extended. After clearing preferred activities, it takes a fresh Computer via snapshotComputer() and builds a components list that holds only the package name. It reads appId from the packageState obtained before the mutation and resolves all user ids with resolveUserIds(UserHandle.USER_ALL).",
      "For each user id, it looks up packageState.getUserStates().get(userId). If that state exists and isInstalled(), it computes packageUid = UserHandle.getUid(userId, appId) and calls sendPackageChangedBroadcast(snapShot, packageName, true /* dontKillApp */, components, packageUid, \"The mimeGroup is changed\").",
      "The broadcast is sent only inside the existing `if (mComponentResolver.updateMimeGroup(...))` branch. The early return for identical MIME type sets and the 255-character and 500-entry limits are unchanged."
     ],
     "completeness": [
      "Covers only the setMimeGroup path, which is the single place in the diff where mimeGroups are changed. The broadcast is gated on updateMimeGroup() returning true, the same gate as the existing clearPackagePreferredActivities call, so a change to the stored MIME types that the resolver does not report sends no broadcast.",
      "For appId and per-user install state, the broadcast loop uses the packageState snapshot taken before commitPackageStateMutation. Users where the package is not installed get no broadcast.",
      "The broadcast uses dontKillApp=true. The material does not show whether any other code path that changes a package's intent filters also lacks this broadcast."
     ],
     "behavior_changes": [
      "When a mimeGroup change makes updateMimeGroup return true, ACTION_PACKAGE_CHANGED is now sent for the package to every user where it is installed. The call passes a components list holding the package name, dontKillApp=true and the reason \"The mimeGroup is changed\".",
      "The broadcast is sent under a cleared calling identity, in the same block as clearPackagePreferredActivities. No new error codes or exceptions are introduced."
     ],
     "preconditions": [
      "The caller reaches setMimeGroup(packageName, mimeGroup, mimeTypes) and passes enforceOwnerRights(snapshot, packageName, callingUid).",
      "The target package declares the mimeGroup (otherwise IllegalArgumentException), the new set differs from the existing one (otherwise early return), and mComponentResolver.updateMimeGroup returns true for the change."
     ],
     "per_major": {
      "14": "Same fix: commit 352a84fa6c0f in android-security-14.0.0_r10 (linked by bug_id). At android-security-14.0.0_r29 all 6/6 patched lines are present in setMimeGroup.",
      "15": "Not affected per the bulletin. At android-security-15.0.0_r17 the patched region is present 6/6.",
      "16": "Not affected per the bulletin. At android-security-16.0.0_r8 the patched region is present 6/6.",
      "17": "Not affected per the bulletin. At android-security-17.0.0_r1 the patched region is present 6/6."
     },
     "backport_notes": [
      "Single-commit fix in PackageManagerService.java (Change-Id I271a3526ea4555249e3a2797605269257330e0e9, cherry-picked three times internally). On 14 it uses snapshotComputer(), PackageUserStateInternal, resolveUserIds and sendPackageChangedBroadcast as they exist in that file. The material has no diffs for 12, 12L or 13."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which consumer of ACTION_PACKAGE_CHANGED was affected by the missing broadcast, or how that leads to the bulletin's EoP rating.",
      "Fix commits for Android 12, 12L and 13 (listed as affected) are not provided, so whether the change had to be adapted for those branches is unknown.",
      "Whether the CTS tests named in the commit message were updated in a separate change is not shown.",
      "How sendPackageChangedBroadcast turns the components list into intent extras is not shown."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 8,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-31332": {
     "cve": "CVE-2024-31332",
     "summary": "Settings now checks the DISALLOW_ADD_WIFI_CONFIG user restriction in WifiDppConfiguratorActivity (onCreate and handleIntent) and AddNetworkFragment (onCreate), and finishes the activity when the restriction is set, so a restricted user can no longer use these UI entry points to add Wi-Fi configurations.",
     "component": "platform/packages/apps/Settings, Wi-Fi (Wi-Fi DPP configurator and Add Network UI)",
     "weakness": "Missing authorization check (user restriction DISALLOW_ADD_WIFI_CONFIG not enforced)",
     "root_cause": "WifiDppConfiguratorActivity and AddNetworkFragment in Settings let users reach Wi-Fi network configuration flows (DPP/QR provisioning and the manual add-network form) without checking UserManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG). WifiDppConfiguratorActivity.handleIntent only rejected guest users (isGuestUser). As a result, a user with the admin-set DISALLOW_ADD_WIFI_CONFIG restriction could still reach these flows. The bulletin rates this as High severity EoP.",
     "fix_analysis": [
      "src/com/android/settings/wifi/AddNetworkFragment.java:onCreate - after super.onCreate, calls the new static helper isAddWifiConfigAllowed(getContext()). If it returns false, it calls getActivity().finish() and returns.",
      "src/com/android/settings/wifi/AddNetworkFragment.java:isAddWifiConfigAllowed - new @VisibleForTesting static helper. It gets UserManager from the context and returns false (with Log.e) when userManager != null and hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG) is true. Otherwise it returns true.",
      "src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java:onCreate - after super.onCreate, calls isAddWifiConfigAllowed(getApplicationContext()). If it returns false, it calls finish() and returns before the savedInstanceState restore block.",
      "src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java:handleIntent - same check at the top, before the existing isGuestUser check. If it returns false, it calls finish() and returns, so the check applies whenever handleIntent is invoked.",
      "src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java:isAddWifiConfigAllowed - new static helper, identical to the one in AddNetworkFragment.",
      "tests/unit/.../AddNetworkFragmentTest.java and tests/unit/.../WifiDppConfiguratorActivityTest.java - new unit tests for isAddWifiConfigAllowed with and without the restriction (mocked UserManager)."
     ],
     "completeness": [
      "Covers WifiDppConfiguratorActivity at onCreate and handleIntent, and AddNetworkFragment at onCreate only.",
      "The check is fail-open when UserManager is null (userManager != null && ...): a missing UserManager service counts as 'allowed'.",
      "AddNetworkFragment finishes its host activity from inside the fragment. The check runs once, at fragment creation, and is not repeated if the restriction changes while the UI is open.",
      "WifiDppConfiguratorActivity looks up UserManager through getApplicationContext(), while AddNetworkFragment uses getContext().",
      "The commit touches only these two classes. The material does not show whether other Settings entry points that create Wi-Fi configurations enforce the restriction."
     ],
     "behavior_changes": [
      "When DISALLOW_ADD_WIFI_CONFIG is set for the user, WifiDppConfiguratorActivity finishes on creation and whenever handleIntent is invoked, and AddNetworkFragment finishes its host activity on creation. Each path logs the error 'The user is not allowed to add Wi-Fi configuration.'",
      "The finish paths added by this fix set no result code (unlike the existing AddNetworkFragment cancel path, which sets RESULT_CANCELED).",
      "No behavior change for users without the restriction."
     ],
     "preconditions": [
      "The current user has the DISALLOW_ADD_WIFI_CONFIG user restriction (the commit was tested with TestDPC).",
      "WifiDppConfiguratorActivity is started, or AddNetworkFragment is shown in Settings, for that user.",
      "The material does not show whether these components are exported or which caller starts them."
     ],
     "per_major": {
      "13": "Listed as affected by the bulletin. The material includes no Android 13 commit, so how the fix was applied there is unknown.",
      "14": "Same fix: commit 20b312f86a70 ([RESTRICT AUTOMERGE] cherry-pick, Change-Id I34afe0f698e2dc43eba59b25f5f3f4f61e70166a) in android-security-14.0.0_r10. The patched region is fully present (8/8 lines) at android-security-14.0.0_r29.",
      "15": "Not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-15.0.0_r17.",
      "16": "Not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-16.0.0_r8.",
      "17": "Not affected (not listed by the bulletin). The patched region is present (8/8 lines) at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single self-contained commit that only adds code (188 insertions). It depends on UserManager.DISALLOW_ADD_WIFI_CONFIG and hasUserRestriction, which exist on Android 13 and later (general platform knowledge). In handleIntent, the check goes right before the existing isGuestUser check. Forks that customized onCreate or handleIntent should insert the check before any intent or saved-state processing."
     ],
     "confidence": "high",
     "unknowns": [
      "The Android 13 fix commit and how the fix was applied there.",
      "Whether other Settings Wi-Fi configuration entry points outside these two classes already enforce DISALLOW_ADD_WIFI_CONFIG.",
      "Which code paths call WifiDppConfiguratorActivity.handleIntent (for example from WifiDppBaseActivity or onNewIntent).",
      "The exact launch path (exported component, intent actions) used in the original report. Bug id 299931076 is referenced but not described."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-31339": {
     "cve": "CVE-2024-31339",
     "summary": "MultiConditionTrigger no longer runs its trigger callback on a detached std::thread; the thread becomes a class member joined in the destructor, and StatsService gains a condition-variable based early-termination request so the delayed onStatsdInitCompleted handler exits promptly on shutdown, termination, destruction or StatsCompanionService death.",
     "component": "platform/packages/modules/StatsD, statsd native daemon (StatsService / utils/MultiConditionTrigger)",
     "weakness": "Use-after-free caused by a detached thread outliving the object whose members it references (lifetime race)",
     "root_cause": "MultiConditionTrigger (constructor and markComplete) launched `std::thread executorThread([this] { mTrigger(); })` and immediately called detach(). The lambda captures `this` and calls mTrigger, a std::function member. Nothing tied the thread's lifetime to the object. If the MultiConditionTrigger was destroyed while the thread was still running, the thread could access freed memory. In practice the owner is StatsService, which holds it as mBootCompleteTrigger and whose trigger runs StatsService::onStatsdInitCompleted. The window could be long because onStatsdInitCompleted, inside a conditional block, slept for mInitEventDelaySecs (`std::this_thread::sleep_for`) before calling mProcessor->onStatsdInitCompleted.",
     "fix_analysis": [
      "statsd/src/utils/MultiConditionTrigger.h: adds `~MultiConditionTrigger()`, private `startExecutorThread()` and member `std::unique_ptr<std::thread> mExecutorThread`. It includes <thread>, and the class doc now says the thread is joined at destructor time.",
      "statsd/src/utils/MultiConditionTrigger.cpp: both thread-creation sites (the constructor when mCompleted is initially true, and markComplete when the last condition completes) now call startExecutorThread(). That function stores the thread in mExecutorThread instead of detaching it. The new destructor joins mExecutorThread if it is non-null and joinable, so the object cannot be destroyed while the callback is running.",
      "statsd/src/StatsService.h: adds `std::condition_variable mStatsdInitCompletedHandlerTerminationFlag`, `std::mutex mStatsdInitCompletedHandlerTerminationFlagMutex`, `bool mStatsdInitCompletedHandlerTerminationRequested = false` and the method `onStatsdInitCompletedHandlerTermination()`. The new members are declared immediately before mBootCompleteTrigger, so they are destroyed after it.",
      "statsd/src/StatsService.cpp: StatsService::onStatsdInitCompleted replaces sleep_for(mInitEventDelaySecs) with `wait_for` on the condition variable, using mStatsdInitCompletedHandlerTerminationRequested as the predicate. If termination is requested during the delay, the handler returns early without calling mProcessor->onStatsdInitCompleted.",
      "statsd/src/StatsService.cpp: the new StatsService::onStatsdInitCompletedHandlerTermination() sets the flag under the mutex and calls notify_all(). It is called from StatsService::~StatsService (as the first statement), StatsService::informDeviceShutdown, StatsService::Terminate and StatsService::statsCompanionServiceDiedImpl, so the join in ~MultiConditionTrigger does not block for the full delay.",
      "statsd/tests/utils/MultiConditionTrigger_test.cpp: adds TestTriggerHasSleep, where a dependency goes out of scope after the trigger and the test checks that the join prevents access to the destroyed object. It also adds TestTriggerHasSleepEarlyTermination, which checks that early termination finishes in about 100ms rather than 1s."
     ],
     "completeness": [
      "Both thread-creation paths in MultiConditionTrigger (the constructor and markComplete) go through startExecutorThread and are joined in the destructor. Only one thread can be created because markComplete returns early under the mutex once mCompleted is set, so a single unique_ptr slot is enough.",
      "The early-termination check only works while the handler is inside wait_for. If the delay has already elapsed, the thread goes on to mProcessor->onStatsdInitCompleted and the destructor waits for it to finish. Whether mProcessor is still alive at that point depends on member declaration order, which the material does not show.",
      "The diff never resets mStatsdInitCompletedHandlerTerminationRequested to false after statsCompanionServiceDiedImpl.",
      "The fix covers every MultiConditionTrigger user generically. The material shows only mBootCompleteTrigger in StatsService as a user."
     ],
     "behavior_changes": [
      "Destroying a MultiConditionTrigger now blocks until the trigger callback finishes (join) instead of returning immediately.",
      "On device shutdown (informDeviceShutdown), Terminate, StatsService destruction or StatsCompanionService death, a pending delayed boot-complete handler is cancelled. If the delay has not yet elapsed, mProcessor->onStatsdInitCompleted is not called and a VLOG message is emitted.",
      "The mInitEventDelaySecs delay now uses condition_variable::wait_for instead of sleep_for. Timing is otherwise unchanged."
     ],
     "preconditions": [
      "The boot-complete MultiConditionTrigger must have fired (all its conditions marked complete). This starts the executor thread that runs StatsService::onStatsdInitCompleted, which may wait mInitEventDelaySecs before calling StatsLogProcessor::onStatsdInitCompleted. Per index search, that call fans out to MetricsManager and the metric producers.",
      "The owning StatsService / MultiConditionTrigger must be destroyed or torn down while that thread is still running. StatsService::informDeviceShutdown is guarded by ENFORCE_UID(AID_SYSTEM). The material does not show a teardown path that a lower-privilege actor could influence."
     ],
     "per_major": {
      "14": "same fix: commit cb201622f3d4 in android-security-14.0.0_r10 (cherry-pick of internal commit 30775d2bb2e2, Change-Id I7036eb3d506e8ca88e4a5faa6275dc4cba8020ee). At android-security-14.0.0_r29 the patched region is fully present (8/8 lines).",
      "15": "not affected (not listed by the bulletin); the patched code is present at android-security-15.0.0_r17 (8/8 lines).",
      "16": "not affected (not listed by the bulletin); the patched code is present at android-security-16.0.0_r8 (8/8 lines).",
      "17": "not affected (not listed by the bulletin); the patched code is present at android-security-17.0.0_r1 (8/8 lines)."
     },
     "backport_notes": [
      "Single commit. The StatsService.cpp hunk replaces a sleep_for(mInitEventDelaySecs) delay (comment references b/277958338), so the StatsService part must be adapted to whatever onStatsdInitCompleted does on the target branch. The new test uses getElapsedRealtimeNs/NanoToMillis from tests/statsd_test_util.h.",
      "StatsD is a mainline (APEX) module (the commit is flagged as a mainline module bug fix), so the fix can also ship through a module update."
     ],
     "confidence": "high",
     "unknowns": [
      "How the vulnerable teardown is reached from a lower-privilege context (the bulletin rates it High EoP but the material does not show the trigger path).",
      "Whether and how the fix was applied to Android 12, 12L and 13 (listed as affected by the bulletin), and whether those branches have the init delay.",
      "The value and source of mInitEventDelaySecs, the condition that guards the delay, and the declaration order of StatsService members such as mProcessor relative to mBootCompleteTrigger."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 7,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-34720": {
     "cve": "CVE-2024-34720",
     "summary": "Adds a peer-UID check (must be Process.SYSTEM_UID) when a Zygote connection is created in Java, and reworks the native nativeForkRepeatedly loop so that newly accepted connections are UID-checked immediately and dropped on mismatch instead of being handed, unchecked, back to the Java command handler.",
     "component": "platform/frameworks/base, Zygote (core/java/com/android/internal/os/ZygoteConnection.java, core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp)",
     "weakness": "Missing/ineffective authorization check on socket peer credentials (missing permission check)",
     "root_cause": "The Java ZygoteConnection constructor read the peer credentials but did not verify the UID. For generic Zygotes SELinux policy also restricts access, but App Zygotes are not covered: per the commit message, preload code running in one app zygote could connect to another app zygote's (randomly named) socket. In the native layer, com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly did fetch SO_PEERCRED, but the comparison against expected_uid was only at the top of the do/while loop. When a new connection was accepted inside the inner for(;;) loop, credentials were re-read at the end of that loop but the UID was only compared if the next command was a simple fork command (loop condition isSimpleForkCommand); for a non-fork command the function returned to Java, which then processed the command with no UID check. The old code also unconditionally closed the existing session socket before accept(), so a bad new connection cost the existing one.",
     "fix_analysis": [
      "ZygoteConnection.java:ZygoteConnection(LocalSocket, String): after obtaining peer credentials via mSocket.getPeerCredentials(), throws ZygoteSecurityException(\"Only system UID is allowed to connect to Zygote.\") if peer.getUid() != Process.SYSTEM_UID.",
      "com_android_internal_os_ZygoteCommandBuffer.cpp: adds static helper getSocketPeerUid(int socket, fail_fn) that wraps getsockopt(SOL_SOCKET, SO_PEERCRED), validates cred_size, and returns credentials.uid; it replaces the two inline getsockopt blocks.",
      "com_android_internal_os_ZygoteCommandBuffer.cpp:nativeForkRepeatedly: the initial UID check is moved out of the do/while body to before the loop (peerUid = getSocketPeerUid(session_socket, fail_fn_1); return JNI_FALSE on mismatch), so it runs once for the initial connection.",
      "com_android_internal_os_ZygoteCommandBuffer.cpp:nativeForkRepeatedly inner for(;;): introduces valid_session_socket; the session socket is now closed only when the poll shows it disconnected (getCount == 0), not unconditionally before accept(). A new do/while accepts a connection, calls getSocketPeerUid(new_fd, ...) and compares to expected_uid: on mismatch it logs ALOGW(\"Dropping new connection with a mismatched uid\"), closes new_fd and sets new_fd = -1, looping again only if there is no valid session socket; on match it closes the old session socket if still valid. If new_fd == -1 after the loop (rejected connection, existing session still valid) it does `continue` to poll again instead of returning to Java. The trailing SO_PEERCRED re-read at the end of the loop is removed because the check now happens at accept time."
     ],
     "completeness": [
      "Covers both places visible in the diff: the Java ZygoteConnection constructor and the native nativeForkRepeatedly path that accepts replacement connections inside the native loop.",
      "The native check compares against the expected_uid parameter passed from Java, whose origin is not shown in the material.",
      "Only the UID is checked; no GID or SELinux-context check is added (SELinux coverage for generic zygotes is stated in the commit message, not in the diff).",
      "In the native path, a rejected new connection when the existing session is already gone loops accepting again; a rejected new connection when the session is still valid returns to polling. Nothing in the material shows other accept paths in this file."
     ],
     "behavior_changes": [
      "Connections from any UID other than Process.SYSTEM_UID now cause ZygoteSecurityException to be thrown from the ZygoteConnection constructor.",
      "nativeForkRepeatedly now returns JNI_FALSE before entering the loop if the initial session socket peer UID mismatches, and drops (closes, logs ALOGW) mismatched newly accepted connections while continuing to serve the existing connection if it is still open.",
      "The existing session socket is no longer closed before accept() unless it was detected as disconnected, so a rejected connection no longer tears down a valid session.",
      "Credential fetch errors now go through getSocketPeerUid with a different error message text (fail_fn_1 is used for both the initial and newly accepted sockets)."
     ],
     "preconditions": [
      "A process other than the system UID must be able to connect to a Zygote socket; per the commit message this applies to App Zygotes, where SELinux does not block it and the preload code in one app zygote could connect to another app zygote's socket if it had its random socket address.",
      "The native path requires the Zygote to be in ZygoteCommandBuffer.nativeForkRepeatedly and a new connection to arrive with a non-fork command so the loop exits to Java (per the commit message)."
     ],
     "per_major": {
      "14": "same fix: commit 7905ffaa89e7 in android-security-14.0.0_r10 (cherry-pick of 2ffc7cb220e4); at android-security-14.0.0_r29 the patched region is present (7/8 lines matched) and the Java UID check is visible in ZygoteConnection.java lines 96-98.",
      "15": "not affected per the bulletin; 7/8 lines of the patched region are present at android-security-15.0.0_r17.",
      "16": "not affected per the bulletin; 7/8 lines of the patched region are present at android-security-16.0.0_r8.",
      "17": "not affected per the bulletin; 7/8 lines of the patched region are present at android-security-17.0.0_r1."
     },
     "backport_notes": [
      "Single commit, two files; the native hunk rewrites the whole inner for(;;) loop of nativeForkRepeatedly, so forks that modified that loop (dup2 fd-reuse logic, poll handling) must re-apply the valid_session_socket/accept-retry structure by hand. ZygoteSecurityException must already exist in the fork (it is referenced, not added, by the diff)."
     ],
     "confidence": "high",
     "unknowns": [
      "How the ZygoteSecurityException thrown from the constructor is handled by the caller (ZygoteServer accept path) and whether it affects the zygote's own lifetime is not shown.",
      "Which code paths and Zygote types construct ZygoteConnection is not shown.",
      "Where expected_uid passed to nativeForkRepeatedly originates and whether it is always SYSTEM_UID is not shown.",
      "The 1 of 8 non-matching lines in the patched region at the latest tags is not identified in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 3,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-34721": {
     "cve": "CVE-2024-34721",
     "summary": "Adds an assertFileColumnsConsistent(match, uri, values) call in MediaProvider.java right after FileUtils.computeDataFromValues(). This covers the case where DATA is generated from values such as RELATIVE_PATH and DISPLAY_NAME, so files can no longer be inserted into another user's storage volume.",
     "component": "platform/packages/providers/MediaProvider, MediaProvider insert path generation (cross-user storage volume check)",
     "weakness": "Missing validation (check not applied when DATA is generated), leading to information disclosure",
     "root_cause": "In MediaProvider.java, the block commented 'Generate path when undefined' runs when ContentValues has no DATA. There, FileUtils.computeDataFromValues() computes DATA from the resolved volume path plus caller-supplied values such as RELATIVE_PATH and DISPLAY_NAME. Per the commit message, inserting into other users' storage volumes was already blocked when DATA was set explicitly, but not when DATA was generated this way. A generated path could therefore land in another user's storage volume. The same file would then exist in both users' MediaProvider databases, and MediaProvider would falsely assume that the user has access to the file.",
     "fix_analysis": [
      "src/com/android/providers/media/MediaProvider.java, in the block commented '// Generate path when undefined' (enclosing function name not shown in the material): after FileUtils.sanitizeValues(values, ...) and FileUtils.computeDataFromValues(values, volumePath, isFuseThread()), the fix adds assertFileColumnsConsistent(match, uri, values). The generated values are therefore checked before the result File is built and before the primary-directory checks.",
      "tests/src/com/android/providers/media/MediaProviderTest.java:testInsertionWithInvalidFilePath_throwsIllegalArgumentException: the test input changes. Before: a '../'-traversal DISPLAY_NAME under RELATIVE_PATH 'Android/media/com.example'. After: RELATIVE_PATH 'Android/media/com.example/' with DISPLAY_NAME 'data/media/test.txt'. The test still expects an IllegalArgumentException with the 'Primary directory Android not allowed' message."
     ],
     "completeness": [
      "Covers the code path where DATA is absent from the values and is generated from the volume path plus caller values. Per the commit message, the explicit-DATA path was already handled (that code is not shown in the material).",
      "The check runs before FileUtils.buildUniqueFile / buildNonUniqueFile, so the final filename those helpers produce is not checked again. The material does not show whether those helpers can change the directory component.",
      "Only one call site is modified. The material does not show other places that call FileUtils.computeDataFromValues() or otherwise derive DATA without the assertion."
     ],
     "behavior_changes": [
      "Inserts whose generated values fail assertFileColumnsConsistent() now throw instead of creating the file. This may also apply to renames of existing files that reach this block (the code handles a currentPath there). The exception type and message are not shown in the material.",
      "The input of the existing test testInsertionWithInvalidFilePath_throwsIllegalArgumentException was changed; it still expects the primary-directory IllegalArgumentException."
     ],
     "preconditions": [
      "The caller performs a MediaStore insert (or another operation that reaches the same path-generation block) without setting MediaColumns.DATA, so DATA is generated by FileUtils.computeDataFromValues() from values such as RELATIVE_PATH and DISPLAY_NAME.",
      "A device with more than one user (implied by the commit message's reference to other users' storage volumes)."
     ],
     "per_major": {
      "14": "Same fix: commit 520edca27f2d (a cherry-pick) landed in android-security-14.0.0_r10 (linked by bug_id). At android-security-14.0.0_r29 the assertFileColumnsConsistent call is present right after computeDataFromValues (7/8 lines of the patched region present).",
      "15": "Not affected (not listed by the bulletin); at android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "16": "Not affected (not listed by the bulletin); at android-security-16.0.0_r8, 7/8 lines of the patched region are present.",
      "17": "Not affected (not listed by the bulletin); at android-security-17.0.0_r1, 4/8 lines of the patched region are present."
     },
     "backport_notes": [
      "A single one-line functional change: insert assertFileColumnsConsistent(match, uri, values) right after FileUtils.computeDataFromValues(values, volumePath, isFuseThread()) in the generated-DATA block. Check that the helper exists in the target branch. Update testInsertionWithInvalidFilePath_throwsIllegalArgumentException as in the diff."
     ],
     "confidence": "medium",
     "unknowns": [
      "How assertFileColumnsConsistent is implemented (what it compares, which exception it throws) is not shown.",
      "The name of the function that contains the patched block is not shown.",
      "The existing check for explicitly set DATA, mentioned in the commit message, is not shown, nor whether it uses the same helper.",
      "Why the test input was changed is not stated in the material."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 11,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    },
    "CVE-2024-34723": {
     "cve": "CVE-2024-34723",
     "summary": "ParcelableListBinder now takes the expected element Class and only adds deserialized Parcelables that are assignable to it, and MediaSessionRecord.getBinderForSetQueue passes QueueItem.class, so non-QueueItem Parcelable objects are no longer accepted into the media session queue list.",
     "component": "platform/frameworks/base, media session (android.media.session.ParcelableListBinder, com.android.server.media.MediaSessionRecord)",
     "weakness": "Missing type validation of deserialized Parcelable input (generic type erasure lets any Parcelable into a List<T> received over Binder)",
     "root_cause": "ParcelableListBinder.onTransact read each list element with data.readParcelable(null) and added it to the List<T> without checking its runtime type. Because of generic erasure, any Parcelable class the caller wrote was accepted. MediaSessionRecord.getBinderForSetQueue returns such a binder, and its consumer stores the received list in mQueue, which is expected to hold QueueItem. A caller that transacts with this binder directly could therefore put non-QueueItem Parcelables into mQueue (the commit cites malicious apps calling it directly via reflection with incompatible items, b/317048338). The commit title describes the vulnerability as allowing apps to start from the background. The bulletin rates it High EoP.",
     "fix_analysis": [
      "media/java/android/media/session/ParcelableListBinder.java: the constructor ParcelableListBinder(Class<T> listElementsClass, Consumer<List<T>> consumer) stores the expected element class in the new field mListElementsClass.",
      "media/java/android/media/session/ParcelableListBinder.java:onTransact: each element is read into an Object via data.readParcelable(null). It is added to mList (cast to T) only if mListElementsClass.isAssignableFrom(object.getClass()). Items that do not match are skipped, but i is still incremented, so the mCount / END_OF_PARCEL framing is unchanged.",
      "services/core/java/com/android/server/media/MediaSessionRecord.java:getBinderForSetQueue: builds the binder with QueueItem.class, so only QueueItem (or subclasses) reach mQueue before MSG_UPDATE_QUEUE is posted."
     ],
     "completeness": [
      "Covers the single instantiation shown in the material (MediaSessionRecord.getBinderForSetQueue). Because the constructor signature changed, any other ParcelableListBinder user that was not updated would fail to compile rather than stay unchecked.",
      "The check runs after data.readParcelable(null) has already created whatever Parcelable class the caller named. The fix filters the result; it does not restrict which classes get deserialized.",
      "The check calls object.getClass() without a null guard. A null element, which was previously added to the list as null, would now throw NullPointerException inside onTransact.",
      "Only the element type is validated. This change does not inspect the contents of a QueueItem."
     ],
     "behavior_changes": [
      "Elements whose runtime class is not assignable to the declared element class are silently dropped from the delivered list.",
      "A null element in the parcel would now cause a NullPointerException in ParcelableListBinder.onTransact instead of being added to the list.",
      "The ParcelableListBinder constructor signature changed (@hide class): callers must pass Class<T>."
     ],
     "preconditions": [
      "The caller holds the ISession binder for a media session, which is given to the app that created the session (general platform knowledge), and calls getBinderForSetQueue to obtain the ParcelableListBinder.",
      "The caller transacts with that binder directly (reflection, per the commit message), writing Parcelables that are not QueueItem.",
      "The material does not show any special permission being required. The bulletin rates it High EoP."
     ],
     "per_major": {
      "14": "same fix: commit 9c8c745df069 in android-security-14.0.0_r10 (cherry-pick of c5fc8ea92c0a). At android-security-14.0.0_r29, 7/8 lines of the patched region are present.",
      "15": "not affected per the bulletin. At android-security-15.0.0_r17, 7/8 lines of the patched region are present.",
      "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": [
      "Single commit with two files that must be applied together. The ParcelableListBinder constructor change breaks compilation of any instantiation that is not updated, so grep the target tree for 'new ParcelableListBinder'. The material only shows MediaSessionRecord.getBinderForSetQueue; the index search found no other callers but may be incomplete.",
      "Consider adding a null guard before object.getClass() when backporting if the target branch tolerated null list elements."
     ],
     "confidence": "high",
     "unknowns": [
      "The material does not show which consumer of mQueue turns the injected Parcelable into the background-start EoP.",
      "The per-major facts do not show whether the fix was also applied to the 12/12L/13 branches listed as affected.",
      "The material does not show why 7/8 lines rather than 8/8 of the patched region are present at the latest tags."
     ],
     "meta": {
      "generated": "2026-10-04",
      "author_model": "claude-fable-5-1",
      "review_model": "claude-opus-5-5",
      "review_corrections": 5,
      "note": "AI-generated from the bulletin, the fixing diffs and their code context, then checked claim by claim by a second model. Statements marked '(general platform knowledge)' are not taken from the material."
     }
    }
   }
  },
  "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-07-01",
  "latest_version": "1.0",
  "latest_revision": {
   "version": "1.0",
   "date": "July 1, 2024",
   "notes": "Bulletin Published."
  },
  "revisions": [
   {
    "version": "1.0",
    "date": "July 1, 2024",
    "notes": "Bulletin Published."
   }
  ],
  "tracked_since": "2026-09-30",
  "changes_since_tracking": [],
  "out_of_scope_rows": {
   "Arm components": 2,
   "Imagination Technologies": 5,
   "Kernel": 1,
   "MediaTek components": 2,
   "Qualcomm closed-source components": 5,
   "Qualcomm components": 4
  },
  "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."
 }
}